Packages
sidereon
0.36.2
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.1
0.32.0
0.31.2
0.30.0
0.29.2
0.29.1
0.29.0
0.28.1
0.28.0
0.27.1
0.27.0
0.26.1
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.0
0.16.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.1
0.11.0
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.0
retired
Satellite toolkit for Elixir with SGP4 propagation, coordinate transforms, GNSS positioning, orbit determination, conjunction assessment, pass prediction, and a Rust NIF backend.
Current section
Files
Jump to
Current section
Files
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).