Mix.install([
  {:sidereon, "~> 0.9"}
])

Load a precise ephemeris

A precise SP3 product (from an IGS or MGEX analysis center) gives each satellite's position and clock on a fixed time grid. Here it is embedded as a string so this notebook runs offline; in practice you would download one from a public archive and load it with Sidereon.GNSS.SP3.load/1. This trimmed product holds six GPS satellites on 2020-06-24.

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)

Query satellite positions

Sidereon.GNSS.SP3.position/3 interpolates the grid to any epoch. Positions are ITRF/IGS ECEF meters.

{:ok, state} = Sidereon.GNSS.SP3.position(sp3, "G08", ~N[2020-06-24 12:00:00])

radius_km =
  :math.sqrt(state.x_m ** 2 + state.y_m ** 2 + state.z_m ** 2) / 1000.0

%{
  position_m: {state.x_m, state.y_m, state.z_m},
  clock_s: state.clock_s,
  radius_km: Float.round(radius_km, 1)
}

The radius lands near the ~26,560 km GPS orbit. Sweep an arc to watch the satellite move:

base = ~N[2020-06-24 12:00:00]

for minutes <- 0..60//15 do
  epoch = NaiveDateTime.add(base, minutes * 60, :second)
  {:ok, s} = Sidereon.GNSS.SP3.position(sp3, "G08", epoch)

  {epoch, Float.round(s.x_m / 1000.0, 1), Float.round(s.y_m / 1000.0, 1),
   Float.round(s.z_m / 1000.0, 1)}
end

Single-point positioning

Sidereon.GNSS.Positioning.solve/4 recovers a receiver position from one epoch of pseudoranges against the SP3 product (or a parsed broadcast navigation source). A mixed-constellation observation set is solved together with one receiver clock per system; the ionosphere and troposphere corrections, the elevation mask, and satellite rejection are all configurable.

# GPS L1 pseudoranges (meters), one per satellite in view.
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}
]

{:ok, sol} =
  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: sol.position,
  geodetic: sol.geodetic,
  rx_clock_s: sol.rx_clock_s,
  pdop: Float.round(sol.dop.pdop, 2),
  used_sats: sol.used_sats
}

See Sidereon.GNSS.Positioning for the full option list and the solution fields, and Sidereon.GNSS.RTK, Sidereon.GNSS.PrecisePositioning, and Sidereon.GNSS.DGNSS for the RTK, PPP, and DGNSS solvers.