Current section

Files

Jump to
sidereon sidereon.livemd
Raw

sidereon.livemd

# sidereon: GNSS and astrodynamics in Elixir
```elixir
Mix.install([
{:sidereon, "~> 0.9"}
])
```
## About
[sidereon](https://hex.pm/packages/sidereon) is the Elixir interface to a GNSS
and astrodynamics engine written in Rust. The numerics run in that engine and
ship as a precompiled NIF, so there is nothing to compile: this notebook runs as
written. Every cell below is self-contained and uses only data embedded here.
## Propagate a satellite and take a look angle
Parse a two-line element set (these are real elements for the ISS), run SGP4 to
get the sub-satellite point, and compute the azimuth, elevation, and range from a
ground station.
```elixir
line1 = "1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9009"
line2 = "2 25544 51.6400 208.8657 0002644 250.3037 109.7782 15.49560812999990"
{:ok, iss} = Sidereon.parse_tle(line1, line2)
epoch = ~U[2024-01-01 12:00:00Z]
{:ok, geo} = Sidereon.geodetic(iss, epoch)
%{
catalog_number: iss.catalog_number,
sub_lat_deg: Float.round(geo.latitude, 3),
sub_lon_deg: Float.round(geo.longitude, 3),
altitude_km: Float.round(geo.altitude_km, 1)
}
```
```elixir
# London ground station: latitude and longitude in degrees, altitude in meters.
station = %{latitude: 51.5, longitude: -0.1, altitude_m: 10.0}
{:ok, look} = Sidereon.look_angle(iss, epoch, station)
%{
azimuth_deg: Float.round(look.azimuth, 2),
elevation_deg: Float.round(look.elevation, 2),
range_km: Float.round(look.range_km, 1)
}
```
## Predict passes over a station
`Sidereon.predict_passes/5` returns every pass that rises above a minimum
elevation in the window, each with rise and set times, peak elevation, and
duration.
```elixir
window_start = ~U[2024-01-01 00:00:00Z]
window_end = DateTime.add(window_start, 1, :day)
{:ok, passes} =
Sidereon.predict_passes(iss, station, window_start, window_end, min_elevation: 10.0)
for p <- passes do
%{
rise: Calendar.strftime(p.rise, "%H:%M:%S"),
set: Calendar.strftime(p.set, "%H:%M:%S"),
peak_elevation_deg: Float.round(p.max_elevation, 1),
duration_min: Float.round(p.duration_seconds / 60, 1)
}
end
```
## Solve a position from pseudoranges
Single-point positioning. Feed `Sidereon.GNSS.Positioning.solve/4` a set of
pseudoranges and a precise SP3 ephemeris and it returns a least-squares fix:
ECEF and geodetic position, the receiver clock bias, and geometry diagnostics.
The SP3 product below is embedded so the cell runs offline; in practice you would
load one with `Sidereon.GNSS.SP3.load/1`.
```elixir
# GPS L1 pseudoranges (meters) for the satellites in view at the epoch.
observations = [
{"G08", 23_825_519.8},
{"G10", 22_717_690.1},
{"G16", 20_478_653.4},
{"G18", 21_768_335.2},
{"G20", 21_248_327.7},
{"G21", 20_808_709.8}
]
sp3_data = """
#cP2020 6 24 9 45 0.00000000 19 TRACK IGb14 FIT GRGS
## 2111 259200.00000000 900.00000000 59024 0.0000000000000
+ 6 G08G10G16G18G20G21 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
++ 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0
++ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
++ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
++ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
++ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
%c M cc GPS ccc cccc cccc cccc cccc ccccc ccccc ccccc ccccc
%c cc cc ccc ccc cccc cccc cccc cccc ccccc ccccc ccccc ccccc
%f 0.0000000 0.000000000 0.00000000000 0.000000000000000
%f 0.0000000 0.000000000 0.00000000000 0.000000000000000
%i 0 0 0 0 0 0 0 0 0
%i 0 0 0 0 0 0 0 0 0
/* CNES/CLS/GRGS - TOULOUSE,FRANCE - Contact : igs-ac@cls.fr
/* PCV:IGS14_2108 OL/AL:FES2012 NONE NN ORB:CoN CLK:CoN
/* CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
/* CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
* 2020 6 24 9 45 0.00000000
PG08 4643.904912 -24162.558516 -9650.049610 -38.635183
PG10 17886.671789 3039.006353 -19391.393793 -380.478330
PG16 3673.608258 -18933.969815 17839.814705 -174.352223
PG18 23700.127530 6507.753313 10093.688366 228.808891
PG20 21015.602926 12381.822675 -10856.524893 527.448122
PG21 26299.269767 -2460.995007 716.844563 15.508264
* 2020 6 24 10 0 0.00000000
PG08 5254.320976 -24937.345008 -6980.386897 -38.636691
PG10 19010.269319 4866.851618 -17921.084337 -380.488198
PG16 4860.112779 -17100.303698 19364.987730 -174.356395
PG18 22418.061092 6785.447511 12538.206018 228.818132
PG20 21540.808052 13373.630254 -8364.466576 527.447968
PG21 26177.802655 -2146.941378 3560.974533 15.512752
* 2020 6 24 10 15 0.00000000
PG08 5710.564170 -25453.105280 -4188.721104 -38.638443
PG10 20115.189466 6485.476927 -16142.894217 -380.498119
PG16 6226.321564 -15204.115225 20546.504507 -174.360500
PG18 20889.343380 7160.242478 14767.111628 228.827365
PG20 21908.611100 14137.388789 -5730.669927 527.447926
PG21 25760.514626 -1769.431822 6343.514786 15.516981
* 2020 6 24 10 30 0.00000000
PG08 6042.284075 -25684.525952 -1323.814227 -38.640412
PG10 21159.408012 7879.460815 -14087.964262 -380.507982
PG16 7759.098529 -13294.312785 21364.232984 -174.364502
PG18 19148.071571 7657.452624 16742.023312 228.836711
PG20 22085.133601 14686.270178 -2999.737823 527.447449
PG21 25067.475736 -1296.107146 9017.301047 15.521531
* 2020 6 24 10 45 0.00000000
PG08 6283.596097 -25614.361768 1564.242656 -38.641756
PG10 22100.268611 9041.937175 -11792.020438 -380.517890
PG16 9436.967444 -11417.672716 21804.746560 -174.368582
PG18 17234.002467 8295.952772 18428.910848 228.845896
PG20 22041.002117 15040.256156 -217.934016 527.447614
PG21 24126.033756 -698.817607 11537.888708 15.525474
* 2020 6 24 11 0 0.00000000
PG08 6471.529324 -25234.155183 4424.947554 -38.642177
PG10 22896.131923 9974.623387 -9294.735744 -380.527755
PG16 11230.846890 -9617.105625 21861.506015 -174.372582
PG18 15191.136796 9087.282030 19798.684014 228.855179
PG20 21752.562074 15225.214120 2567.564373 527.447459
PG21 22969.651026 45.186775 13864.197409 15.529722
* 2020 6 24 11 15 0.00000000
PG08 6644.360327 -24544.616731 7208.313715 -38.643219
PG10 23507.988002 10687.523653 -6639.037631 -380.537732
PG16 13105.095965 -7930.108701 21534.895433 -174.377072
PG18 13066.149231 10035.038521 20827.696867 228.864441
PG20 21202.891348 15271.756854 5309.432624 527.447520
PG21 21636.563260 952.305394 15959.053118 15.534059
* 2020 6 24 11 30 0.00000000
PG08 6839.902243 -23555.650675 9865.783600 -38.644720
PG10 23900.971736 11198.322998 -3870.373323 -380.547626
PG16 15018.822256 -6387.464812 20832.117593 -174.381097
PG18 10906.724082 11134.589308 21498.159124 228.873724
PG20 20382.574992 15213.927788 7960.972253 527.447536
PG21 20168.317061 2031.899012 17789.631146 15.538108
* 2020 6 24 11 45 0.00000000
PG08 7093.820244 -22286.025531 12351.111011 -38.645281
PG10 24045.725638 11531.498375 -1035.945281 -380.557547
PG16 16927.393599 -5012.236439 19766.958053 -174.385107
PG18 8759.860313 12373.107746 21798.447374 228.883076
PG20 19290.210666 15087.759974 10476.882192 527.447233
PG21 18608.242309 3285.876753 19327.806335 15.542220
* 2020 6 24 12 0 0.00000000
PG08 7438.042916 -20762.704119 14621.192800 -38.647002
PG10 23919.560682 11717.183156 1816.071269 -380.567404
PG16 18784.088401 -3819.088286 18359.430195 -174.389524
PG18 6670.210753 13729.937789 21723.310519 228.892428
PG20 17932.623680 14929.762015 12814.016152 527.447354
PG21 16999.913180 4708.560403 20550.418405 15.546299
* 2020 6 24 12 15 0.00000000
PG08 7899.334503 -19019.862023 16636.833344 -38.647763
PG10 23507.373783 11789.829543 4637.317275 -380.577286
PG16 20541.815806 -2813.957336 16635.315598 -174.393761
PG18 4678.519090 15177.272097 21273.965621 228.901739
PG20 16324.780780 14775.387488 14932.114622 527.446948
PG21 15385.647811 6286.819595 21439.461249 15.550671
* 2020 6 24 12 30 0.00000000
PG08 8498.085463 -17097.635989 18363.427105 -38.649562
PG10 22802.289648 11786.720063 7380.034276 -380.587160
PG16 22154.836702 -1994.074300 14625.615492 -174.397842
PG18 2820.214554 16681.118608 20458.082166 228.911022
PG20 14489.402694 14657.545765 16794.499670 527.447051
PG21 13805.092069 8000.463976 21982.204956 15.554459
* 2020 6 24 12 45 0.00000000
PG08 9247.369015 -15040.654467 19771.547156 -38.650484
PG10 21806.004803 11746.384064 9997.833858 -380.596890
PG16 23580.419142 -1348.326156 12365.929519 -174.402246
PG18 1124.218120 18202.518914 19289.654667 228.920337
PG20 12456.286563 14605.211454 18368.720568 527.447006
PG21 12293.927824 9822.872282 22171.258698 15.558791
* 2020 6 24 13 0 0.00000000
PG08 10152.299669 -12896.410588 20837.430270 -38.651742
PG10 20528.822194 11706.977946 12446.442268 -380.606741
PG16 24780.366877 -857.936737 9895.777935 -174.406704
PG18 -387.994051 19698.971935 17788.765433 228.929616
PG20 10261.360393 14642.186916 19627.138333 527.447150
PG21 10882.740798 11721.833118 22004.581485 15.563408
* 2020 6 24 13 15 0.00000000
PG08 11209.716830 -10713.543221 21543.351971 -38.652897
PG10 18989.375629 11704.688739 14684.410767 -380.616599
PG16 25722.366833 -497.431201 7257.882649 -174.411115
PG18 -1703.034135 21126.008215 15981.241263 228.938925
PG20 7945.502154 14786.067388 20547.437547 527.446927
PG21 9596.077666 13660.568119 21485.446350 15.567441
* 2020 6 24 13 30 0.00000000
PG08 12408.203090 -8540.094231 21877.887705 -38.653740
PG10 17214.054103 11772.219589 16673.782838 -380.626446
PG16 26381.110092 -235.841125 4497.421430 -174.415665
PG18 -2815.928687 22438.854161 13898.209721 228.948171
PG20 5553.165699 15047.451338 21113.054383 527.446839
PG21 8451.716682 15598.904800 20622.361836 15.572148
* 2020 6 24 13 45 0.00000000
PG08 13728.433306 -6421.809657 21836.058871 -38.653666
PG10 15236.146433 11937.412720 18380.709769 -380.636302
PG16 26739.150669 -38.100050 1661.268176 -174.419882
PG18 -3730.115104 23594.121838 11575.562442 228.957546
PG20 3130.863904 15429.429881 21313.510668 527.447015
PG21 7460.170730 17494.563735 19428.952908 15.577088
* 2020 6 24 14 0 0.00000000
PG08 15143.837640 -4400.549449 21419.364822 -38.654444
PG10 13094.736367 12222.060725 19776.006518 -380.646060
PG16 26787.476793 133.425531 -1202.769329 -174.424084
PG18 -4457.109220 24551.458779 9053.334699 228.966797
PG20 725.565911 15927.378707 21144.644989 527.446993
PG21 6624.436243 19304.522499 17923.801627 15.580392
* 2020 6 24 14 15 0.00000000
PG08 16621.549904 -2512.864913 20635.703989 -38.655549
PG10 10833.387063 12640.950613 20835.640171 -380.655943
PG16 26525.779980 317.327470 -4046.704331 -174.428345
PG18 -5015.857646 25275.093632 6375.012002 228.975990
PG20 -1616.930248 16529.064324 20608.733434 527.446900
PG21 5939.995958 20986.417129 16130.246267 15.584692
EOF
"""
{:ok, sp3} = Sidereon.GNSS.SP3.parse(sp3_data)
{:ok, solution} =
Sidereon.GNSS.Positioning.solve(sp3, observations, ~N[2020-06-24 12:00:00],
initial_guess: [4_500_000.0, 500_000.0, 4_500_000.0, 0.0]
)
%{
position_ecef_m: solution.position,
rx_clock_s: solution.rx_clock_s,
pdop: Float.round(solution.dop.pdop, 2),
used_sats: solution.used_sats
}
```
## Screen a conjunction and compute collision probability
Parse a CCSDS Conjunction Data Message (CDM), convert it to collision
parameters, and compute the probability of collision (Pc) with the Foster method.
```elixir
cdm_kvn = """
CCSDS_CDM_VERS = 1.0
CREATION_DATE = 2010-03-12T22:31:12.000
ORIGINATOR = JSPOC
MESSAGE_ID = 201113719185
TCA = 2010-03-13T22:37:52.618
MISS_DISTANCE = 715 [m]
RELATIVE_SPEED = 14762 [m/s]
OBJECT = OBJECT1
OBJECT_DESIGNATOR = 12345
OBJECT_NAME = SATELLITE A
REF_FRAME = EME2000
X = 2570.097065 [km]
Y = 2244.654904 [km]
Z = 6281.497978 [km]
X_DOT = 4.418769571 [km/s]
Y_DOT = 4.833547743 [km/s]
Z_DOT = -3.526774282 [km/s]
CR_R = 4.142E+01 [m**2]
CT_R = -8.579E+00 [m**2]
CT_T = 2.533E+03 [m**2]
CN_R = -2.313E+01 [m**2]
CN_T = 1.336E+01 [m**2]
CN_N = 7.098E+01 [m**2]
OBJECT = OBJECT2
OBJECT_DESIGNATOR = 30337
OBJECT_NAME = FENGYUN 1C DEB
REF_FRAME = EME2000
X = 2569.540800 [km]
Y = 2245.093614 [km]
Z = 6281.599946 [km]
X_DOT = -2.888612500 [km/s]
Y_DOT = -6.007247516 [km/s]
Z_DOT = 3.328770172 [km/s]
CR_R = 1.337E+03 [m**2]
CT_R = -4.806E+04 [m**2]
CT_T = 2.492E+06 [m**2]
CN_R = -3.298E+01 [m**2]
CN_T = -7.5888E+02 [m**2]
CN_N = 7.105E+01 [m**2]
"""
{:ok, cdm} = Sidereon.CCSDS.CDM.parse(cdm_kvn)
# Combined hard-body radius of 20 meters.
cdm = %{cdm | hard_body_radius_m: 20.0}
params = Sidereon.CCSDS.CDM.to_collision_params(cdm)
{:ok, equal_area} = Sidereon.Collision.probability(params, method: :equal_area)
{:ok, numerical} = Sidereon.Collision.probability(params, method: :numerical)
%{
miss_km: Float.round(equal_area.miss_km, 4),
pc_equal_area: equal_area.pc,
pc_numerical: numerical.pc
}
```
## RF link budget
Size a UHF amateur-radio link to the satellite at the look angle computed above:
free-space path loss, EIRP, and the resulting link margin.
```elixir
fspl_db = Sidereon.RF.fspl(look.range_km, 437.0)
eirp_dbw = Sidereon.RF.eirp(30.0, 10.0)
margin_db =
Sidereon.RF.link_margin(%{
eirp_dbw: eirp_dbw,
fspl_db: fspl_db,
receiver_gt_dbk: -20.0,
other_losses_db: 3.0,
required_cn0_dbhz: 30.0
})
%{
fspl_db: Float.round(fspl_db, 1),
eirp_dbw: Float.round(eirp_dbw, 1),
link_margin_db: Float.round(margin_db, 1)
}
```
## Where to next
Full signatures live on [HexDocs](https://hexdocs.pm/sidereon). More notebooks,
including a ground-track map and a GNSS positioning walkthrough, live under
[`examples/`](https://github.com/neilberkman/sidereon-ex/tree/main/examples).