@neilberkman/sidereon 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +530 -0
- package/README.md +64 -8
- package/THIRD-PARTY-NOTICES.md +3 -3
- package/package.json +5 -5
- package/pkg/sidereon.d.ts +11031 -2692
- package/pkg/sidereon.js +8934 -627
- package/pkg/sidereon_bg.wasm +0 -0
- package/pkg/sidereon_bg.wasm.d.ts +2515 -1949
- package/pkg-node/sidereon.d.ts +8291 -518
- package/pkg-node/sidereon.js +9040 -627
- package/pkg-node/sidereon_bg.wasm +0 -0
- package/pkg-node/sidereon_bg.wasm.d.ts +2515 -1949
- package/third_party_source/{sidereon-core-0.36.3 → sidereon-core-3.0.0}/tides/mod.rs +508 -89
- package/third_party_source/{sidereon-core-0.36.3 → sidereon-core-3.0.0}/tides/ocean.rs +391 -86
- package/types/sidereon-extra.d.ts +312 -1
- /package/third_party_source/{sidereon-core-0.36.3 → sidereon-core-3.0.0}/tides/pole.rs +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,535 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
Engine update: sidereon / sidereon-core 3.0.0 (pending release). Every entry below
|
|
6
|
+
restates an engine change as it reaches the JavaScript API.
|
|
7
|
+
|
|
8
|
+
### Labels
|
|
9
|
+
|
|
10
|
+
- **Breaking.** A variant a later engine adds to an enum this binding labels
|
|
11
|
+
crosses under its own name, the engine variant's name in the case of the
|
|
12
|
+
labels around it (`lowerCamelCase`, or `UPPER_SNAKE_CASE` for TDM input
|
|
13
|
+
error kinds), where 2.1.1 gave `"unknown"` or `"UNKNOWN"`. This covers the
|
|
14
|
+
almanac season, Moon phase, planet, planetary event, culmination and eclipse
|
|
15
|
+
kinds; the RINEX clock header and record readings; the TDM input error
|
|
16
|
+
kinds; and the new PPP unplaced reasons, Bias lookup statuses, RTCM and SBAS
|
|
17
|
+
departures and SBAS refused-line reasons. Where the declarations state these
|
|
18
|
+
as unions of literals, the unions end in
|
|
19
|
+
`| (string & {})`.
|
|
20
|
+
|
|
21
|
+
### Positioning
|
|
22
|
+
|
|
23
|
+
- **Breaking.** SPP, static, DGNSS, PPP and tight-fusion solves place each
|
|
24
|
+
satellite at the transmission epoch of its measured pseudorange,
|
|
25
|
+
`t_tx = (t_rx - P / c) - dts`, as RTKLIB `satposs` places it. This moves
|
|
26
|
+
solutions by the receiver clock times each satellite's range rate: nothing
|
|
27
|
+
measurable on a steered receiver clock, decimetres of range on a receiver
|
|
28
|
+
clock of half a millisecond.
|
|
29
|
+
- **Breaking.** The SPP, DGNSS and tight-fusion code models apply to a precise
|
|
30
|
+
(SP3 or interpolated precise) satellite clock the relativistic term
|
|
31
|
+
`-2 r.v / c^2`, as RTKLIB `peph2pos` does; they applied none. Their
|
|
32
|
+
solutions on precise sources move by metres to tens of metres. PPP rows
|
|
33
|
+
already applied the term and do not change for it.
|
|
34
|
+
- **Breaking.** `buildRinexRtkArc` and `buildDualFrequencyRinexRtkArc` place
|
|
35
|
+
each receiver's satellites from that receiver's own pseudoranges, as RTKLIB
|
|
36
|
+
`satposs` places them, where they used the reception epoch less the
|
|
37
|
+
pseudorange over `c` rounded to whole microseconds with no satellite clock.
|
|
38
|
+
A satellite is skipped for an epoch where the source has no clock for it or
|
|
39
|
+
its pseudorange is not a positive distance. A satellite whose phase
|
|
40
|
+
observable has no carrier no longer fails the arc: its measurement is formed
|
|
41
|
+
from the first configured pair whose carriers resolve, and a satellite no
|
|
42
|
+
pair resolves for is left out of that epoch and listed in the new
|
|
43
|
+
`unresolvedCarriers` as `{ receiver, epochIndex, satelliteId,
|
|
44
|
+
observableCode }`.
|
|
45
|
+
- **Breaking.** A PPP observation whose code is zero or negative is no longer
|
|
46
|
+
solved at a meaningless transmission epoch. It is left out and reported in
|
|
47
|
+
the new `unplacedObservations` on `PppFloatSolution` and
|
|
48
|
+
`PppFixedSolution`, each `{ epochIndex, satelliteId, ambiguityId, reason }`
|
|
49
|
+
with `reason` `"codeNotPositive"`.
|
|
50
|
+
- **Breaking.** An ionosphere-corrected SPP or static solve no longer fails a
|
|
51
|
+
whole epoch because one satellite has no resolvable carrier (a GLONASS
|
|
52
|
+
observation without a valid `glonassChannels` entry). The satellite is left
|
|
53
|
+
out and reported in the new `rejectedSats` of `SppSolution` and
|
|
54
|
+
`StaticSolution`, each `{ satelliteId, reason }`, with `reason` one of
|
|
55
|
+
`"noEphemeris"`, `"lowElevation"`, `"sbasWithdrawn"`, `"sbasIonoUncovered"`
|
|
56
|
+
and `"ionosphereCarrierUnresolved"`.
|
|
57
|
+
- **Breaking.** A broadcast satellite clock no longer includes the
|
|
58
|
+
single-frequency group delay. SPP, FDE and DGNSS requests take
|
|
59
|
+
`pseudorangeCode`, `"singleFrequency"` (the default) or `"ionosphereFree"`;
|
|
60
|
+
the broadcast group delay (GPS and QZSS TGD, Galileo BGD, BeiDou TGD1) applies
|
|
61
|
+
to single-frequency code only, as RTKLIB `prange` applies it.
|
|
62
|
+
- PPP observations take `signals: { code1, code2, phase1, phase2 }`, the RINEX
|
|
63
|
+
3 tracking codes the ionosphere-free code and phase were formed from. SSR and
|
|
64
|
+
HAS biases apply only to an observation that states the signals they are for.
|
|
65
|
+
- PPP residuals carry `ambiguityId`. `PppFloatSolution` gains
|
|
66
|
+
`solvedEpochIndices`, `residualScreen`, `residualScreenRemovals` and
|
|
67
|
+
`solveOptions` (the iteration cap and tolerances the solve ran with);
|
|
68
|
+
`PppFixedSolution` gains `solvedEpochIndices`.
|
|
69
|
+
- `solveStaticReferenceStationRinex` reports a reference or rover epoch outside
|
|
70
|
+
the UT1 table as a mode error with `kind` `"ut1OutsideCoverage"`, and the
|
|
71
|
+
code, carrier and station solutions carry `ut1Degraded`.
|
|
72
|
+
`sbasProtectionLevels` refuses such an epoch with the error label
|
|
73
|
+
`"Ut1OutsideCoverage"`, and DGNSS solves throw an `Error` naming it.
|
|
74
|
+
|
|
75
|
+
- **Breaking.** SPP and the static solve select, mask and weight the
|
|
76
|
+
satellites at every iterate, as RTKLIB `estpos` re-runs `rescode`, instead of
|
|
77
|
+
once at the initial guess. A pass whose selection repeats the previous one
|
|
78
|
+
takes RTKLIB's least-squares step, and the solve ends with the first such
|
|
79
|
+
step below 1e-4 m. Solutions move wherever the selection or the weights at
|
|
80
|
+
the solution differ from those at the initial guess; a coarse cold start that
|
|
81
|
+
failed now solves. A solve whose selection does not settle throws an `Error`
|
|
82
|
+
naming the passes it ran.
|
|
83
|
+
- **Breaking.** `StaticSolution.metadata.status` is `"SelectionSettled"` for a
|
|
84
|
+
converged solve and `"OuterBudgetExhausted"` for a robust solve that spent
|
|
85
|
+
its outer budget first, and `converged` describes how the whole solve ended:
|
|
86
|
+
a spent robust budget reports `converged: false`. The status is typed as the
|
|
87
|
+
new `SolveStatus` union, and the metadata as `StaticSolveMetadata`.
|
|
88
|
+
- `SppSolution.metadata` reports how the solve ran and ended, `{ iterations,
|
|
89
|
+
converged, status, outerIterations, finalRobustScaleM, ionosphereApplied,
|
|
90
|
+
troposphereApplied, usedCount }` (`SppSolveMetadata`).
|
|
91
|
+
- The SPP carrier check applies only when a frequency-dependent term is
|
|
92
|
+
applied, so a solve without the ionosphere correction no longer leaves out a
|
|
93
|
+
satellite for want of a carrier. The PPP auto-init SPP seed passes each
|
|
94
|
+
GLONASS observation's channel.
|
|
95
|
+
|
|
96
|
+
- **Breaking.** Every SPP-family failure throws an `Error` named
|
|
97
|
+
`PositioningError` whose `detail` is a typed `PositioningErrorDetail`: a
|
|
98
|
+
union on `kind` with one member per engine variant and the fields it
|
|
99
|
+
carries. This covers `solveSpp`, `solveSppWithDopplerVelocity`,
|
|
100
|
+
`solveSppBatch`, `solveSppFromRinexObs`, `solveBroadcast`,
|
|
101
|
+
`solveWithFallback`, `fde`, `solveSppSbas`, `solveStatic` and the DGNSS
|
|
102
|
+
solves, including the new `SELECTION_UNSETTLED { passes }` and
|
|
103
|
+
`UT1_OUTSIDE_COVERAGE { reason }`. A failure that wraps another nests it as
|
|
104
|
+
`cause` (`EPOCH_INPUT`, `PRECISE_SOLVE_FAILED`, `BROADCAST_SOLVE_FAILED`); a
|
|
105
|
+
rejected solution names its validation failure (`SOLUTION_REJECTED` with
|
|
106
|
+
`validation.kind` such as `DEGENERATE_GEOMETRY_PDOP { pdop }`); a RINEX SPP
|
|
107
|
+
variant a later engine adds crosses as `OTHER` with its own name in
|
|
108
|
+
`variant`. They were plain `Error`s carrying only a message, and a
|
|
109
|
+
fallback failure was named `PreciseSolveError` or `BroadcastSolveError`.
|
|
110
|
+
`SppBatchSolution.error(i)` and `RinexSppSolutionBatch.error(i)` return the
|
|
111
|
+
detail instead of a message string, and `broadcastReason.preciseError` is a
|
|
112
|
+
detail. A non-finite DGNSS base position throws a `PositioningError`
|
|
113
|
+
(`DGNSS_INVALID_INPUT`) instead of a `RangeError`.
|
|
114
|
+
|
|
115
|
+
### UT1 coverage
|
|
116
|
+
|
|
117
|
+
- **Breaking.** Every function that reads UT1 (sidereal times, GCRS/ITRS
|
|
118
|
+
transforms, look angles, passes, ground tracks, visibility, observe, Sun and
|
|
119
|
+
Moon az/el and illumination, rise/set and meridian transits, coverage grids,
|
|
120
|
+
PPP corrections, SSR-corrected states) refuses an instant outside the UT1
|
|
121
|
+
table by default and throws. It previously held TT-UT1 at the table edge; a
|
|
122
|
+
pass search scored the failed look angle as -90 degrees and returned no
|
|
123
|
+
passes. Where an instant outside the table is accepted, the engine follows
|
|
124
|
+
the Skyfield 1.54 delta-T curve.
|
|
125
|
+
- `WithValidity` variants take an optional UT1 policy, `"strict"` (the
|
|
126
|
+
default) or `"permissive"`, and return `{ value, ut1Degraded }`, where
|
|
127
|
+
`ut1Degraded` is `"beforeCoverage"`, `"afterCoverage"` or `null`:
|
|
128
|
+
`gmstRadiansWithValidity`, `gastRadiansWithValidity`,
|
|
129
|
+
`gcrsToItrsWithValidity`, `itrsToGcrsWithValidity`, `sunAzElWithValidity`,
|
|
130
|
+
`moonAzElWithValidity`, `moonIlluminationWithValidity`,
|
|
131
|
+
`findMoonElevationCrossingsWithValidity`, `findMoonTransitsWithValidity`,
|
|
132
|
+
`meridianTransitsWithValidity`, `meridianTransitsSpkWithValidity`,
|
|
133
|
+
`observeWithValidity`, `observeSpkBodyWithValidity`,
|
|
134
|
+
`Tle.lookAnglesWithValidity`, `Tle.findPassesWithValidity`,
|
|
135
|
+
`Tle.groundTrackWithValidity` and `visibleFromSatellitesWithValidity`.
|
|
136
|
+
- `Instant.ut1Degraded`, `SppSolution.ut1Degraded` and
|
|
137
|
+
`CoverageGrid.ut1Degraded` report the departure a permissive policy
|
|
138
|
+
accepted. `coverageLookAngles` and `pppCorrections` (option `ut1Validity`)
|
|
139
|
+
take the policy; `pppCorrections` also returns `ut1Degraded` and `warnings`.
|
|
140
|
+
`ssrCorrectedState` takes an optional UT1 policy and returns `ut1Degraded`.
|
|
141
|
+
|
|
142
|
+
### TLE and SGP4
|
|
143
|
+
|
|
144
|
+
- `Sgp4Satellite.fromOmm(omm)` initializes a reusable SGP4 propagator through
|
|
145
|
+
the engine's canonical OMM bridge. Its `propagate(epochs)` returns the same
|
|
146
|
+
TEME arrays as the matching TLE, while incompatible metadata and missing
|
|
147
|
+
`MEAN_MOTION` or `BSTAR` remain typed `OmmError` failures.
|
|
148
|
+
- **Breaking.** `new Tle(line1, line2, opsMode?, policy?)` and
|
|
149
|
+
`parseTleFile(text, opsMode?, policy?)` take a checksum policy. `"strict"`,
|
|
150
|
+
the default, refuses a column-69 digit that disagrees with the computed
|
|
151
|
+
checksum and a column 69 that is not a digit; 2.1.1 read both. `"lenient"`
|
|
152
|
+
reads them and reports each in `checksumWarnings`, now
|
|
153
|
+
`{ kind, expected?, found?, computed, message }`.
|
|
154
|
+
- **Breaking.** `Tle.revNumber`, `elementSetNumber` and `ephemerisType` are
|
|
155
|
+
`undefined` when the field is blank, instead of `0`.
|
|
156
|
+
- **Breaking.** `parseTleFile` returns `rejected`, one
|
|
157
|
+
`{ lineNumber, name, issue, message }` per record it refused, and `skipped`
|
|
158
|
+
is its length. Each `NamedTle` carries `lineNumber` and `checksumWarnings`.
|
|
159
|
+
|
|
160
|
+
- **Breaking.** `Tle.toLines` spells B\* and the second mean-motion derivative
|
|
161
|
+
as python-sgp4's `export_tle` does: the five significant digits of
|
|
162
|
+
`value * 10`, correctly rounded with ties to even (3.21675e-9 is now
|
|
163
|
+
`" 32168-8"`, where it was `" 32167-8"`). A zero B\* is `" 00000+0"`, a
|
|
164
|
+
negative zero `"-00000+0"`, and a zero second derivative `" 00000-0"`.
|
|
165
|
+
- **Breaking.** An OMM epoch of whole microseconds takes the SGP4 epoch
|
|
166
|
+
python-sgp4 2.22 gives it, bit for bit, and a satellite built from such an
|
|
167
|
+
OMM is initialised as python-sgp4's `sgp4.omm.initialize` does, with B\* and
|
|
168
|
+
the second derivative as stated. Epochs of the NAVSTAR 43 and GALAXY 15
|
|
169
|
+
fixtures move by about 0.3 microseconds. Other OMM element sets quantize B\*
|
|
170
|
+
and the second derivative onto the TLE grid at exponent -9, as the TLE writer
|
|
171
|
+
rounds them.
|
|
172
|
+
- **Breaking.** Passes and look angles propagate SGP4 at the split Julian date
|
|
173
|
+
Skyfield 1.54 uses for the same UTC instant, bit for bit.
|
|
174
|
+
|
|
175
|
+
### SPK
|
|
176
|
+
|
|
177
|
+
- **Breaking.** SPK states always carry a velocity. Type 2 segments return
|
|
178
|
+
the derivative of the position polynomial, where the velocity was
|
|
179
|
+
`undefined`.
|
|
180
|
+
- `SpkKernels` holds several kernels and resolves a body chain across them
|
|
181
|
+
(`new SpkKernels()`, `push`, `pushBytes`, `length`, `state`,
|
|
182
|
+
`stateInFrame`). `Spk.stateInFrame`, `spkInertialFrameName` and
|
|
183
|
+
`spkInertialFrameRotation` expose the kernel's inertial frames.
|
|
184
|
+
|
|
185
|
+
### Time
|
|
186
|
+
|
|
187
|
+
- **Breaking.** The embedded leap-second table dated the 1997 and 1999 leap
|
|
188
|
+
seconds to 1997-01-01 and 1998-01-01. TAI - UTC became 31 s on 1997-07-01
|
|
189
|
+
and 32 s on 1999-01-01, so every UTC conversion from 1997-01-01 to 1997-06-30
|
|
190
|
+
and from 1998-01-01 to 1998-12-31 moves by one second, and `23:59:60` is
|
|
191
|
+
accepted on 1997-06-30 and 1998-12-31 instead of 1996-12-31 and 1997-12-31.
|
|
192
|
+
- NMEA sentence seconds are read as the decimal stated, rounded once (`07.56`
|
|
193
|
+
was 7.5600000000000005).
|
|
194
|
+
|
|
195
|
+
### Space weather
|
|
196
|
+
|
|
197
|
+
- **Breaking.** `sampleAt(epoch, policy?)` under the default policy refuses a
|
|
198
|
+
monthly predicted row, which states no Ap, instead of substituting the quiet
|
|
199
|
+
Ap of 4. `"lenient"`, or a policy object with `requireGeomagnetic: false`,
|
|
200
|
+
substitutes it and reports it. Rows whose flux qualifier states no
|
|
201
|
+
observation have the new class `"notObserved"`, refused unless the policy
|
|
202
|
+
sets `allowNotObserved`. Unknown policy keys and names throw a `TypeError`.
|
|
203
|
+
- `apHistoryAt(epoch, policy?)` returns the seven-element NRLMSISE-00 Ap
|
|
204
|
+
history `{ ap, class, apDefaulted, binsFromDailyAp }`. Decay requests take
|
|
205
|
+
`policy`.
|
|
206
|
+
|
|
207
|
+
### CCSDS OMM, OPM, OEM and CDM
|
|
208
|
+
|
|
209
|
+
- **Breaking.** Reader and writer failures throw typed errors named
|
|
210
|
+
`OmmError`, `OpmError`, `OemError` and `CdmError`, whose `detail` is an
|
|
211
|
+
`NdmErrorDetail` (`kind`, `message` and the fields that variant carries).
|
|
212
|
+
Writers (`toKvnString`, `toXmlString`, `toJsonString` and the new CSV
|
|
213
|
+
writers) throw instead of writing text that would not read back.
|
|
214
|
+
- **Breaking.** A value a message does not carry is `undefined`, not a
|
|
215
|
+
fabricated default: an OMM read from JSON without `CCSDS_OMM_VERS` has no
|
|
216
|
+
version, and `new Omm(...)` takes `meanMotion` and `noradCatId` as optional.
|
|
217
|
+
- **Breaking.** OPM and OEM covariances are built from the 21-value lower
|
|
218
|
+
triangle (`lowerTriangle`) or a symmetric 36-value matrix; an asymmetric
|
|
219
|
+
matrix is refused. `matrix` returns the stored values unvalidated and
|
|
220
|
+
`toValidatedMatrix()` checks positive semidefiniteness.
|
|
221
|
+
- **Breaking.** `OemSegment(metadata, states, covariances, dataComments?,
|
|
222
|
+
covarianceComments?)` throws on invalid input. `skippedStates` is an array of
|
|
223
|
+
`{ line, segment, text, reason, itemCount, field, issue }`, with the count in
|
|
224
|
+
`skippedStateCount`.
|
|
225
|
+
- Every record the messages define is retained: OMM `classification`,
|
|
226
|
+
`messageId`, `refFrameEpoch`, `semiMajorAxisKm`, `gmKm3S2`, spacecraft
|
|
227
|
+
parameters, `btermM2Kg`, `agomM2Kg`, covariance, user-defined parameters and
|
|
228
|
+
comments; OPM and OEM `classification`, `messageId`, `refFrameEpoch`,
|
|
229
|
+
user-defined parameters and comments per block; CDM `ccsdsCdmVers`,
|
|
230
|
+
`messageFor`, relative state, screening volume and period, comments, drag,
|
|
231
|
+
SRP and thrust covariance rows, OD parameters and additional parameters,
|
|
232
|
+
with `toCovarianceRtn()`.
|
|
233
|
+
- `parseOmmCsv`, `parseOmmXmlAll`, `parseOmmJsonArray` and `parseOmmCsvArray`
|
|
234
|
+
read CSV and multi-record OMM, returning `{ omms, skipped }` for the array
|
|
235
|
+
readers. `Omm.toCsvString`, `toCsvStringDiscardingComments` and
|
|
236
|
+
`toJsonStringDiscardingComments` write them.
|
|
237
|
+
|
|
238
|
+
### Bias-SINEX and CODE DCB
|
|
239
|
+
|
|
240
|
+
- **Breaking.** `loadBiasSinex(bytes, policy?)` and
|
|
241
|
+
`loadCodeDcb(bytes, options, policy?)` are strict by default: a file that
|
|
242
|
+
departs from Bias-SINEX 1.00, or a DCB title whose time-system label is
|
|
243
|
+
unknown when no options are given, is refused. `"lenient"` reads it and
|
|
244
|
+
reports each departure in `notices`.
|
|
245
|
+
- **Breaking.** Bias lookups (`codeOsbSeconds`, `phaseOsbCycles`,
|
|
246
|
+
`codeDsbSeconds`, `codeBiasModelM`) return a `BiasLookup`,
|
|
247
|
+
`{ status, value, records, overridden, productScale, queryScale,
|
|
248
|
+
observable }`, instead of a number or `undefined`, so an absent, ambiguous or
|
|
249
|
+
out-of-scale lookup is distinct from a value. `phaseOsbCycles` takes
|
|
250
|
+
`timeScale?` and `carrierHz?`.
|
|
251
|
+
- Bias records carry `family`, `unit`, `svn`, `validFrom`, `validUntil`,
|
|
252
|
+
`slopeSigma` and `line`. `BiasSet` gains `mode`, `timeScale`,
|
|
253
|
+
`timeSystemLabel`, `notices` and `lineCounts`, and writes with
|
|
254
|
+
`toBiasSinex()` and `toCodeDcb()`.
|
|
255
|
+
|
|
256
|
+
### RINEX navigation
|
|
257
|
+
|
|
258
|
+
- **Breaking.** `BroadcastRecord.issue`, `issueMessage` and `svAccuracyM` are
|
|
259
|
+
`undefined` where the message states none (a CNAV record has no IODE).
|
|
260
|
+
`toRinexString` and `encodeRinexNav` throw instead of writing a record that
|
|
261
|
+
would not read back.
|
|
262
|
+
- **Breaking.** `issueMessage` gains `"navic_lnav"` and
|
|
263
|
+
`"galileo_unclassified"`. A Galileo record's message is read from its
|
|
264
|
+
data-source word by RINEX 3.05 Table A8; a word that names no single message
|
|
265
|
+
is `"galileo_unclassified"`, where it was read as I/NAV or F/NAV.
|
|
266
|
+
- Records expose the fields their message states: `iodc`, `l2Codes`,
|
|
267
|
+
`l2pDataFlag`, `galileoDataSources`, `beidouAodc`, `transmissionTimeSow`,
|
|
268
|
+
GLONASS `statedFreqChannel`, `ageDays`, `statusFlags`, `healthFlags`, group
|
|
269
|
+
delays and flag words, `isHealthy`, `singleFrequencyGroupDelayS` and
|
|
270
|
+
`clockBiasAtS`. Ionosphere corrections add QZSS, NavIC, Galileo NeQuick,
|
|
271
|
+
Galileo disturbance flags and BeiDou BDGIM, with
|
|
272
|
+
`BroadcastEphemeris.ionoCorrectionsAt`.
|
|
273
|
+
- `BroadcastEphemeris` and the NAV parse result report `skipped` blocks with
|
|
274
|
+
their `line`, `departures` and `other` blocks. `parseRinexNavFile` returns a
|
|
275
|
+
`RinexNavFile` that keeps every entry and writes it back.
|
|
276
|
+
|
|
277
|
+
### RTCM
|
|
278
|
+
|
|
279
|
+
- **Breaking.** `decodeRtcm` reads a buffer in full or throws: a stray byte, a
|
|
280
|
+
CRC-24Q failure, a trailing partial frame, or a body that does not decode or
|
|
281
|
+
departs from RTCM 3. It returned the frames it could read. Use
|
|
282
|
+
`decodeRtcmStream` for noisy input.
|
|
283
|
+
- **Breaking.** `decodeRtcmStream(bytes, policy?)`, `decodeRtcmFrame(bytes,
|
|
284
|
+
policy?)`, `encodeRtcm(message, policy?)` and `encodeRtcmFrame(message,
|
|
285
|
+
policy?, reserved?)` take a policy, `"strict"` (the default) or
|
|
286
|
+
`"lenient"`. Messages carry `trailingBits`, MSM messages `signalMask` and
|
|
287
|
+
GLONASS fields `negativeZero`, so a lenient decode re-encodes byte for byte.
|
|
288
|
+
Stream diagnostics gain `crcFailures` and `departures`, and skipped frames
|
|
289
|
+
the reason `"departure"`. `decodeRtcmFrame` returns `reserved` and
|
|
290
|
+
`departures`; `FrameScanner` gains `resyncBytes`, `crcFailures` and
|
|
291
|
+
`reserved`.
|
|
292
|
+
|
|
293
|
+
- The decoded message IR is typed: `decodeRtcm`, `decodeRtcmMessage`,
|
|
294
|
+
`decodeRtcmStream`, `decodeRtcmFrame` and `FrameScanner.next()` return
|
|
295
|
+
`RtcmMessage` (a union on `type`), `RtcmStream`, `RtcmFrame` and
|
|
296
|
+
`RtcmScannedFrame`, with `RtcmDeparture` and `RtcmFrameSkip` for the
|
|
297
|
+
diagnostics; `encodeRtcm` and `encodeRtcmFrame` take `RtcmMessageInput`.
|
|
298
|
+
Raw 64-bit fields are `bigint`.
|
|
299
|
+
- `encodeRtcm` and `encodeRtcmFrame` encode SSR messages (`type: "ssr"`), so
|
|
300
|
+
every message type the decoder returns re-encodes.
|
|
301
|
+
- The encoder reads the GNSS system as the decoder writes it (`"GPS"`,
|
|
302
|
+
`"GLONASS"`, `"Galileo"`, `"BeiDou"`, `"QZSS"`, `"NavIC"`, `"SBAS"`) as well
|
|
303
|
+
as in lower case. A decoded MSM or SSR message was refused with
|
|
304
|
+
`invalid GNSS system label "GPS"`.
|
|
305
|
+
|
|
306
|
+
### SBAS
|
|
307
|
+
|
|
308
|
+
- **Breaking.** `decodeSbasMessage(bytes, form?, policy?)` refuses by default
|
|
309
|
+
a preamble other than `0x53`, `0x9A` and `0xC6`. `"lenient"` reads it and
|
|
310
|
+
reports it in `departures`; the result carries `padBits`.
|
|
311
|
+
- `parseSbasEmsLog` and `parseSbasRtklibLog(text, { policy, referenceWeek })`
|
|
312
|
+
return an `SbasLog` with `blocks`, `skippedLines`, `refusedLines` and
|
|
313
|
+
`departures`. Log blocks carry `declaredMessageType` and `messageType` and
|
|
314
|
+
decode with `decode(policy?)`. The ionospheric grid reports
|
|
315
|
+
`unavailableIgps`.
|
|
316
|
+
|
|
317
|
+
### SSR
|
|
318
|
+
|
|
319
|
+
- SSR orbit and clock corrections carry `navMessage` (`"rtcm"` or `"has"`),
|
|
320
|
+
`hasNavMessageIndex` and `transmittedEpochJ2000S`.
|
|
321
|
+
|
|
322
|
+
### IONEX
|
|
323
|
+
|
|
324
|
+
- **Breaking.** A grid node the file gives as `9999` is now `null`. At the default exponent it used to read as 999.9 TECU, and `slantDelay` interpolated it. `Ionex.tecGridSamples()` returns `tecMaps` and `rmsMaps` as `(number | null)[][][]`, `tecSamples()` returns `vtecTecu` and `rmsTecu` as `number | null`, and `ionexFromSamples` / `ionexFromNodeSamples` accept `null` at those positions.
|
|
325
|
+
- **Breaking.** `tecGridSamples().rmsMaps` is `null` for a product that carries no RMS maps, where it was `[]`. A declared RMS stack whose nodes are all `null` is kept, and it stays distinct from an absent stack. The result also carries `heightMaps` (`null` when absent) and `header`, and each `tecSamples()` row carries `heightOffsetKm`.
|
|
326
|
+
- `START OF HEIGHT MAP` blocks are now read (a height map's `END OF HEIGHT MAP` record used to fail the parse). They are exposed as `Ionex.heightMaps`, in km added to `HGT1`. New getters: `Ionex.tecMaps`, `rmsMaps`, `heightMaps` (each `IonexMapCube`, or `null` when absent), `hasRms`, `hasHeight` and `skippedRecords`.
|
|
327
|
+
- **Breaking.** `toIonexString()` throws an `IonexWriterError` whose `detail` is `{ kind: "UNWRITABLE", message }` when its IONEX field cannot state a value, axis or header field exactly. It used to round values to the header exponent, write a 0.25 degree step as 0.2 and overrun columns. The writer now uses the IONEX 1 layouts (`2X,3F6.1` axes, `2X,5F6.1` band records, `16I5` values) and picks an exponent that states every value exactly. It writes back the header records the product carries, plus `EPOCH OF FIRST MAP`, `EPOCH OF LAST MAP`, `# OF MAPS IN FILE`, `MAP DIMENSION` and `END OF FILE`, so its text differs from 2.1.1.
|
|
328
|
+
- **Breaking.** `Ionex.slantDelay`, `IonexSelection.slantDelay` and the new top-level `ionexSlantDelay` now throw a named error with a typed `.detail` for an engine refusal, where they threw a plain `Error`. The names are `IonexCoverageError`, `IonexMissingNodesError`, `IonexVaryingHeightsError`, `IonexHeightNotAvailableError`, `IonexMappingFunctionError`, `IonexInvalidInputError`, and `IonexSlantError` for a variant the binding does not name. A query whose interpolation weights a `null` node is refused (`MISSING_NODES`). On a product with height maps, the delay uses `HGT1` plus the common height, or is refused with `VARYING_HEIGHTS` / `HEIGHT_NOT_AVAILABLE`.
|
|
329
|
+
- **Breaking.** Every J2000-second input (`epochJ2000S` on the slant entries and batch requests, `mapEpochsJ2000S` on `ionexFromSamples`, `epochJ2000S` on `ionexFromNodeSamples` rows) must be an integer within ±9007199254740991. Values between 2^53 and the `i64` range used to be accepted and could wrap.
|
|
330
|
+
- **Breaking.** `ionexFromSamples` and `ionexFromNodeSamples` throw a `TypeError` for an own string property outside the accepted names, enumerable or not (for example `heightMap` or `rmsTec`), where the property used to be ignored. `ionexFromNodeSamples` requires an array, walks it in order and names the index of the first malformed sample. A throwing accessor on `header`, on the header's mapping records or on a policy axis re-raises the caller's own value unchanged.
|
|
331
|
+
- `Ionex.header` returns the descriptive header records as an `IonexHeader`: `version`, `satelliteSystem`, `program`, `runBy`, `date`, `descriptions`, `comments`, `intervalS`, `mappingFunction`, `mappingDeclaration`, `elevationCutoffDeg`, `observablesUsed`, `stationCount`, `satelliteCount` and `mapsInFile`. `Ionex.mappingFunction` and `Ionex.mappingDeclaration` read the same two records. `ionexFromSamples` accepts `header`, and `ionexFromNodeSamples` takes an optional fifth `header` argument. A supplied header whose `mappingFunction` and `mappingDeclaration` contradict each other is refused, as is a known tag with another code (`{ kind: "COSZ", code: "MOD" }`).
|
|
332
|
+
- New `IonexSlantPolicy` with three axes: `coverage` (`"strict"` / `"hold"`), `missingNodes` (`"strict"` / `"renormalize"`) and `mapping` (`"declared"` / `"singleLayer"`). It has static constructors and `with*` modifiers, and a plain `{ coverage?, missingNodes?, mapping? }` object is accepted in its place. It is taken by `Ionex.slantDelayWithPolicy`, `Ionex.slantDelaysBatchResults`, `ionexSlantDelayWithPolicy` and `ionexSlantDelayResults`. The results are `IonexSlantDelayEvaluation` objects whose `status` carries `held`, `degraded`, `assumedMapping`, `isValid` and `isNominal`, and a batch returns one `{ index, isOk, evaluation, refusal }` row per request.
|
|
333
|
+
- A product declaring a `MAPPING FUNCTION` other than `COSZ` is still mapped with the single-layer `1/cos(z')`, and the value is now flagged in `status.assumedMapping`. It stays `isValid`. `mapping: "declared"` refuses such a product with `MAPPING_FUNCTION`.
|
|
334
|
+
- `loadIonexWithWarnings`, `Ionex.parseWithWarnings` and `Ionex.parseStrWithWarnings` return `{ ionex, value, warnings }` with seven warning kinds: `MISSING_RECORD`, `VERSION_RECORD_NOT_FIRST`, `EPOCH_MISMATCH`, `MAP_COUNT_MISMATCH`, `NOT_A_NUMBER_VALUE`, `INTERVAL_MISMATCH` and `EXPONENT_CARRIED_INTO_MAP`. `Ionex.parse` and `Ionex.parseStr` are new static constructors.
|
|
335
|
+
- The reader accepts files it used to refuse:
|
|
336
|
+
- an axis running in either direction, with `latNodesDeg` / `lonNodesDeg` now in file order (an ascending latitude axis was refused)
|
|
337
|
+
- an epoch at hour 24 with zero minute and second
|
|
338
|
+
- a `nan` value field, read as `null` with a `NOT_A_NUMBER_VALUE` warning
|
|
339
|
+
- a map inheriting an earlier map's `EXPONENT`, reported as `EXPONENT_CARRIED_INTO_MAP`
|
|
340
|
+
|
|
341
|
+
`slantDelay` also interpolates across the longitude seam of a grid that closes the circle, and returns a value along a line of sight through a pole.
|
|
342
|
+
- **Breaking.** The reader refuses what it used to misplace or pass over:
|
|
343
|
+
- a `MAP DIMENSION 3` product, or an `HGT1 / HGT2 / DHGT` giving several heights
|
|
344
|
+
- a band whose latitude, longitude or height is not a grid node
|
|
345
|
+
- a map that gives a node twice or leaves one without a value
|
|
346
|
+
- `START OF TEC MAP` numbers out of order
|
|
347
|
+
- an RMS or height map that does not name a TEC map
|
|
348
|
+
- a data record holding a non-ASCII character, or one outside a band
|
|
349
|
+
- a `16I5` record with an empty field
|
|
350
|
+
- an unclosed `AUX DATA` block
|
|
351
|
+
- a file type other than `I`
|
|
352
|
+
- two records of one header label that read as different values
|
|
353
|
+
- Values scale as `field * 10^EXPONENT` from an exact power of ten, and each band is placed by its own `LAT/LON1/LON2/DLON/H` record. An `EXPONENT` record inside a map applies to the data blocks after it. Previously, bands were placed in arrival order and an in-map `EXPONENT` before the first band was ignored.
|
|
354
|
+
- New standalone regular-grid surface: `TecGrid`, whose `values` are `(number | null)[]`, with `vtecAtPiercePoint` and `vtecAtPiercePointWithPolicy`. Alongside it are `TecGridEpoch` (exact `bigint` `unixNanos`), `TecGridShellGeometry` and `TecGridEvalOptions`, and the top-level `ionoDelayXyz`, `ionoDelayXyzWithPolicy`, `tecXyz` and `tecXyzWithPolicy` with an optional `ecefToLla` callback whose thrown value is re-raised unchanged. Failures throw a `TecGridError` whose `.detail` is a `TecGridErrorDetail`.
|
|
355
|
+
|
|
356
|
+
### TDM
|
|
357
|
+
|
|
358
|
+
- **Breaking.** `Tdm.comments`, `TdmMetadata.comments` and `TdmDataSection.comments` return `TdmComment[]` (`{ text, beforeRecord }`) instead of `string[]`. `toKvnString()` writes each comment back at its position rather than gathering comments to the top of their block, and comment text keeps its leading indentation.
|
|
359
|
+
- **Breaking.** `parseTdmKvn` throws a `TdmParseError` and `Tdm.toKvnString()` a `TdmWriteError`, where both threw a plain `Error`. Each carries a `detail` that is a `TdmErrorDetail` union on `kind`, with a one-based `line` that is `null` for a failure raised with no input line.
|
|
360
|
+
- **Breaking.** `parseTdmKvn` reads under the strict CCSDS 503.0-B-2 rules and now refuses:
|
|
361
|
+
- a last line with no terminator (`UNTERMINATED_FINAL_LINE`)
|
|
362
|
+
- a tab, control or non-ASCII character (`NON_PRINTABLE_CHARACTER`)
|
|
363
|
+
- a line over 254 characters (`LINE_TOO_LONG`)
|
|
364
|
+
- a missing `CREATION_DATE`, `ORIGINATOR`, `TIME_SYSTEM` or `PARTICIPANT_n` (`MISSING_KEYWORD`)
|
|
365
|
+
- a data section with no records (`EMPTY_DATA_SECTION`), or `KEY =` with no value (`EMPTY_VALUE`)
|
|
366
|
+
- a version outside `x.y` (`INVALID_VERSION`)
|
|
367
|
+
- a timetag outside the 4.3.9 forms, one with `Z` under a time system other than UTC, one with second 60 other than at `23:59` UTC or `02:59` GLONASS, or one with more than 38 fraction digits (`MALFORMED_EPOCH`)
|
|
368
|
+
- records out of chronological order (`RECORDS_OUT_OF_ORDER`), or a repeated keyword and timetag pair (`DUPLICATE_RECORD`)
|
|
369
|
+
- keywords out of table order, or comments out of place (`KEYWORD_OUT_OF_ORDER`)
|
|
370
|
+
- a `PATH` naming an undefined participant (`UNDEFINED_PARTICIPANT`)
|
|
371
|
+
- a keyword outside its section's table (`UNDEFINED_KEYWORD`)
|
|
372
|
+
- `PARTICIPANT_0`, `PARTICIPANT_6` and up, padded indices such as `PARTICIPANT_01`, or `PATH_3` (`INVALID_FIELD`)
|
|
373
|
+
- a keyword repeated with another value (`CONFLICTING_KEYWORD`)
|
|
374
|
+
- `COMMENT=value` or `META_START = 1` (`MALFORMED_LINE`)
|
|
375
|
+
- The reader ends a line at CR, LF, CRLF or LFCR, so a file written with carriage returns reads. It used to read as one line and was refused.
|
|
376
|
+
- **Breaking.** `Tdm.toKvnString()` applies the reader's rules to what it writes. It refuses under the strict write policy each departure listed above. It also refuses a keyword written twice with one value (`REPEATED_KEYWORD`), and a field or comment the KVN form cannot carry, such as an empty or padded key, a key holding `=`, or a line break (`UNWRITABLE`, `KEYWORD_NOT_ASSIGNABLE`). It now terminates the last line, which it did not.
|
|
377
|
+
- `parseTdmKvnWithPolicy(text, policy?)` returns `{ tdm, value, warnings }`. `policy` is `"strict"`, `"lenient"`, or an object setting `nonPrintable`, `missingKeywords`, `longLines`, `emptyDataSections`, `recordOrder`, `duplicateRecords`, `keywordOrder` or `finalTerminator` to `"strict"` or `"forgive"`. A misspelled axis is a `TypeError`.
|
|
378
|
+
- `Tdm.toKvnStringWithPolicy(policy?)` returns `{ text, value, departures }`. It takes the reader's axes plus `repeatedKeywords`, and reports each departure it emitted as a `TdmDeparture`.
|
|
379
|
+
- `TdmMetadata.fromRaw(fields, comments?)`, `TdmMetadata.fromRawWithPolicy`, `metadata.replaceRaw`, `metadata.replaceRawWithPolicy`, `new TdmField(key, value)` and `tdm.setSegmentMetadata(segmentIndex, metadata)` build and replace a metadata block from ordered raw fields and positioned comments. `participants`, `paths`, `mode`, `timetagRef`, `timeSystem` and `rangeUnits` are derived from those fields. A refused replacement throws a `TdmValidationError` and leaves the block unchanged.
|
|
380
|
+
|
|
381
|
+
- `TdmMetadata.setField(key, value)` sets one keyword: a stated keyword takes
|
|
382
|
+
the value in place, an absent one is inserted where the keyword order of
|
|
383
|
+
tables 3-2 and 3-3 puts it, and every comment keeps the field it precedes.
|
|
384
|
+
`TdmMetadata.removeField(key)` removes every occurrence. Both validate as
|
|
385
|
+
`replaceRaw` does and are atomic.
|
|
386
|
+
|
|
387
|
+
### RINEX observations
|
|
388
|
+
|
|
389
|
+
- **Breaking.** `RinexObs.toRinexString()` returns text only when reading it back gives the product. Otherwise it throws a `RinexObsWriteError` whose `detail` is a `RinexObsWriteErrorDetail` naming the first field that would change; it used to drop, round, wrap or truncate and return the text anyway. A version 2 product is written as a RINEX 2 file, where it used to be written with version 3 `>` epoch records under a version 2 header. A version 2 product holding `SYS / SCALE FACTOR` records is refused (`SCALE_FACTORS_IN_VERSION_TWO`), and so is picoseconds in a product below 4.02 (`EPOCH_PICOSECONDS_NOT_IN_VERSION`).
|
|
390
|
+
- **Breaking.** `RinexObsRepair.repairedText` now throws that same `RinexObsWriteError`, where it was a plain `string` getter, and `RinexObsRepair.toCrinexString()` throws it for a writer refusal. `repairRinexObs` returns the repair whether or not its product can be written.
|
|
391
|
+
- **Breaking.** `ObsEpoch.epoch` is `ObsEpochTime | undefined`. It is `undefined` for an event whose epoch fields are blank, which RINEX 2.11 and 3.05 allow and which was refused.
|
|
392
|
+
- **Breaking.** `ObsPhaseShift.code` is `string | undefined`: a record naming only its constellation (RINEX 3.05 section 5.2.12) is read, where it used to be refused. `ObsPhaseShift.correctionCycles` is `number | undefined` for a blank correction. New getters: `unrepresentableSatellites` (designators such as `R28`, kept and written back), `coversEverySatellite` and `satelliteCount`. Satellite lists continue past ten satellites.
|
|
393
|
+
- **Breaking.** `carrierPhaseRows(i, filter?)` reads epoch `i` with the header in effect at it. `phaseShiftCycles` holds `NaN` where no single correction applies, beside the new `phaseShiftAvailable`, `phaseShiftStatus` (`available` / `unknown` / `ambiguous`) and `phaseShiftCorrections`. From RINEX 4.00, every row is an available 0.
|
|
394
|
+
- **Breaking.** Header records an event epoch (flags 2 to 5) carries now take effect for the epochs after it; they used to be kept as text and never applied. `ObsHeader.obsCodes(system)` is the union of every list the file declares, and each epoch's `observations` and `cycleSlips` are index-aligned to it. The new `declaredObsCodes(system)` gives the list in effect. `RinexObs.headerAt(i)` and `RinexObs.headerTimeline()` (`ObsHeaderTimeline`, `ObsHeaderSegment`) give the header in effect at each epoch.
|
|
395
|
+
- **Breaking.** Every epoch-index argument (`epoch`, `headerAt`, `observationValues`, `carrierPhaseRows`, `pseudoranges`, `ObsHeaderTimeline.at`, `ObsHeaderTimeline.segmentIndex`) throws a `RangeError` for a negative, fractional, non-finite or out-of-range index. Previously a fractional index was truncated to another epoch.
|
|
396
|
+
- **Breaking.** One header block now refuses two records that read as different values for the same label. This applies to:
|
|
397
|
+
- `INTERVAL`, `MARKER NAME/NUMBER/TYPE`, `APPROX POSITION XYZ`, `ANTENNA: DELTA H/E/N`, `ANT # / TYPE`, `REC # / TYPE / VERS`, `OBSERVER / AGENCY` and `SIGNAL STRENGTH UNIT`
|
|
398
|
+
- in the file header only: `RINEX VERSION / TYPE`, `TIME OF FIRST OBS`, `TIME OF LAST OBS`, `LEAP SECONDS` and `# OF SATELLITES`
|
|
399
|
+
|
|
400
|
+
Two scale factors for one code and two channels for one GLONASS slot are refused too. The later record used to win. Conflicting phase shifts or GLONASS biases are kept and read as `ambiguous` instead of being refused.
|
|
401
|
+
- Flag 6 epochs are read as cycle slips into `ObsEpoch.cycleSlips` and `cycleSlipSatellites`, with `satellites` and `observations` empty, and `rinexObsCycleSlipFlag()` returns the flag. New `ObsEpoch` getters: `observations`, `rcvClockOffsetS`, `epochPicoseconds`, `declaredRecordCount` and `specialRecords` (the records an event carried, verbatim; they used to be discarded).
|
|
402
|
+
- New `ObsHeader` getters: `rinex2Types`, `rinex2System`, `programRunByDate`, `comments`, `markerNumber`, `markerType`, `observer`, `agency`, `receiver`, `antenna`, `timeOfLastObsEpoch`, `timeOfLastObsScale`, `declaredSatelliteCount`, `prnObsCounts`, `scaleFactors`, `signalStrengthUnit` and `unretainedHeaderLabels`. `leapSeconds` is an `ObsLeapSeconds` with `bigint` fields and a `timeSystem` that keeps blank and `GPS` distinct. `RinexObs.skippedRecords` is also new.
|
|
403
|
+
- `ObsHeader.glonassCodPhsBis` returns the record as written, with `biasM: null` for a blank bias. A header with a blank bias used to be refused. `glonassCodePhaseBias(code)` returns `available`, `none`, `unknown` or `ambiguous`.
|
|
404
|
+
- `RinexObs.downgradeToRinex2(version)` returns `{ obs, value, changes }` with every `ObsDowngradeChange`. It throws a `RinexObsWriteError` for what version 2 cannot state: `NOT_VERSION_TWO`, a carrier version 2 has no name for such as BeiDou B1C (`OBSERVABLE_NOT_REPRESENTABLE`), and `LEAP_SECONDS_TIME_SYSTEM_NOT_IN_VERSION`.
|
|
405
|
+
- **Breaking.** Version 2 observation codes are read as the signals they name. GPS `C2` below 2.12 reads as L2C rather than `C2C`, BeiDou digits name the band their frequency slot gives, and 2.12 lettered names read as their signals. Galileo and BeiDou are no longer given `P` codes, and a Galileo `C2` is no longer read as E5a.
|
|
406
|
+
- The reader takes fixed-column records from their columns. An epoch of 100 or more satellites, a position component of -10,000,000 m and a `PRN / # OF OBS` record laid out as `3X,A1,I2,9I6` now read. A file declaring version 2 while carrying `>` epoch records also reads.
|
|
407
|
+
- **Breaking.** The reader refuses values its own fields cannot write back:
|
|
408
|
+
- an `INTERVAL`, position, antenna delta or first/last-observation second below its field's resolution or too wide for its columns
|
|
409
|
+
- a version 2 value wider than `F14.3` or with more than three decimals
|
|
410
|
+
- a version 2 type code wider than two characters
|
|
411
|
+
- a `GLONASS COD/PHS/BIS` code longer than three characters
|
|
412
|
+
- a type-list continuation that continues no list
|
|
413
|
+
- blank epoch fields on an observation or cycle slip record
|
|
414
|
+
- `lintRinexObs` no longer reports `OBS-B11` and reports `OBS-B10` for an event whose header records do not read. `ObservationQcReport.notes` gains the `eventHeaderRecordsUnread` kind.
|
|
415
|
+
|
|
416
|
+
- **Breaking.** A lint finding's `detail` is a typed `RinexLintFindingDetail`,
|
|
417
|
+
a union on `kind` (`OBS_UNRETAINED_HEADER { label }`,
|
|
418
|
+
`OBS_EPOCH_GAP { gapS, intervalS }` and so on, one member per engine
|
|
419
|
+
variant), where it was the engine's debug text. `lintRinexObs`,
|
|
420
|
+
`lintRinexNav` and the repairs' `remaining` are typed `RinexLintReport`.
|
|
421
|
+
- `ObservationQcReport` gains `header` (marker, receiver, antenna,
|
|
422
|
+
approximate position, antenna delta, first and last observation times and
|
|
423
|
+
duration) and `systems` (per-system completeness), and `toJson()` includes
|
|
424
|
+
both.
|
|
425
|
+
- `simulateScenarioSet(scenario)` keeps the simulated observation set as a
|
|
426
|
+
`ScenarioSimulation`, with `arrays`, `determinismFingerprintHex`,
|
|
427
|
+
`toRinexObservationFile()`, `toRinexString()` and
|
|
428
|
+
`sppObservationsForEpoch(i)`.
|
|
429
|
+
|
|
430
|
+
### RINEX clock
|
|
431
|
+
|
|
432
|
+
- **Breaking.** A parsed `RinexClock` keeps every line of its input. `toRinexString()` on an unedited product restates the input byte for byte, line terminators included. It used to write a three-line header, drop every other header record and every non-`AS` record, and write a `GLO` time system as `UTC`.
|
|
433
|
+
- **Breaking.** `parseRinexClock` / `loadRinexClock` read `AR`, `CR`, `DR` and `MS` records and refuse one with an unreadable epoch, bias or sigma or a blank name, where such records were skipped. The first line that does not read throws a `RinexClockParseError` whose `detail` is a `RinexClockErrorDetail`, where it threw a plain `Error`. A value beyond a record's declared count is kept in `surplusValues` instead of refused. `parseRinexClockLossy` keeps unread lines verbatim and reports each in `diagnostics`.
|
|
434
|
+
- **Breaking.** `TIME SYSTEM ID` reads `GPS`, `GLO`, `GAL`, `QZS`, `BDS`, `BDT`, `IRN`, `UTC` and `TAI`, with `GLO` read as UTC. A file without the record takes the RINEX clock 3.00 default and reports it in `notices`. An `IRN`, unrecognised or conflicting label leaves the product without a time scale, and `clockS` then throws a `RinexClockQueryError`.
|
|
435
|
+
- **Breaking.** `ClockEpoch` is a civil label read in the time scale of the product it is queried against. The constructor accepts `23:59:60` on a day that ends with a positive leap second, and `gpsSeconds` is `number | undefined`, `undefined` for such a label. The second is read as the shortest decimal of the number given with every digit kept, so `59.9999996` no longer rounds into the next minute. Record epochs keep every digit their seconds field states.
|
|
436
|
+
- **Breaking.** `series`, `seriesFor` and `sampleCount` include samples in every time scale. `ClockSeries.gpsSeconds` holds `NaN` for a sample off the GPS timeline, where such samples used to be dropped. New `ClockSeries` getters: `hasGpsSeconds`, `jdWhole`, `jdFraction`, `epochs` (`RinexClockInstant[]`), `additionalValues` and `timeScale`.
|
|
437
|
+
- `clockSAtGpsSeconds` answers QZSST series and throws a `RinexClockQueryError` outside civil years 1 through 9999. On a UTC product, interpolation across a leap second uses elapsed time.
|
|
438
|
+
- **Breaking.** `toRinexString()` refuses rather than rounds, throwing a `RinexClockWriteError`. It refuses a value no 19-column field states exactly, an epoch no microsecond seconds text states exactly, and a scale no RINEX clock time system names, GLONASS system time among them (`UNSUPPORTED_TIME_SCALE`). `toRinexStringWithPolicy(policy?)` returns `{ text, value, departures }`, and `{ nearestMicrosecondEpochs: "allow" }` writes an off-grid epoch at the nearest microsecond and reports it as `EPOCH_AT_NEAREST_MICROSECOND`.
|
|
439
|
+
- New views: `version`, `layout` (`"v300"` / `"v304"`), `satelliteSystem`, `timeSystem`, `timeSystemStatus`, `timeScale`, `headerRecords()`, `records()`, `recordCount`, `sourceLine(n)`, `skippedRecords`, `diagnostics` and `notices`.
|
|
440
|
+
- New edits: `setTimeSystem`, `setRecordValues`, `insertRecord`, `removeRecord`, `retainRecords` and `editRecords`. Each validates the whole change first; a refused edit throws a `RinexClockEditError` and leaves the product unchanged.
|
|
441
|
+
- New builders: `RinexClock.fromClockPoints(timeScale, rows)` and `RinexClock.fromSeriesRows(rows)`, which throw a `RinexClockBuildError`.
|
|
442
|
+
|
|
443
|
+
- **Breaking.** `ClockEpoch.gpsSeconds` and each sample's `gpsSeconds` are the
|
|
444
|
+
double nearest the GPS second count the civil tag states, rounded once. They
|
|
445
|
+
were rounded at each step, so 2026-05-13 00:00:59.9999996 came out one unit
|
|
446
|
+
in the last place high.
|
|
447
|
+
|
|
448
|
+
### ANTEX
|
|
449
|
+
|
|
450
|
+
- **Breaking.** `Antenna.daziDeg`, `zenithStartDeg`, `zenithEndDeg` and `zenithStepDeg` are `number | undefined`, `undefined` when the block has no such record. A blank or non-numeric `DAZI` or `ZEN1 / ZEN2 / DZEN` value is refused as `INVALID_FIELD`, where it used to read as `0`.
|
|
451
|
+
- **Breaking.** `Antenna.frequencies` lists labels in file order, once per section. 2.1.1 listed each label once, sorted, with a repeated label's last section winning. `pco`, `pcv` and the new `frequency(label)` throw an `AntexLookupError` with `AMBIGUOUS_FREQUENCY` for a label whose sections differ, and `UNKNOWN_FREQUENCY` for an absent label. `pcv` also refuses a zenith outside the block's grid and an empty grid.
|
|
452
|
+
- **Breaking.** `loadAntex` throws an `AntexParseError` whose `detail` is an `AntexErrorDetail`, where it threw a plain `Error`.
|
|
453
|
+
- **Breaking.** `VALID FROM` / `VALID UNTIL` are read by their `5I6,F13.7` columns with an exact fraction of the second, so `59.9999999` no longer becomes 59. The following are refused as `INVALID_FIELD`: a blank, short or malformed field, and a second of 60, which GPS time does not have. `new AntexDateTime(...)` throws a `RangeError` for a second of 60 and takes an optional seventh `fractionDigits` argument. New getters `fractionDigits` and `nanosecond`; `validAt` compares every digit.
|
|
454
|
+
- **Breaking.** Millimetre fields convert to metres as `mm * 1e-3`, the arithmetic of RTKLIB `readantex`, so about one PCO or PCV value in seven moves by one unit in the last place.
|
|
455
|
+
- **Breaking.** A PCV row that would put two values on one zenith is refused with `DEGENERATE_GRID`, and a once-per-block record repeated with different content with `REPEATED_RECORD`. Lines outside any record, a `# OF FREQUENCIES` count that disagrees with the sections, and unclosed blocks or sections are counted in the new `Antex.skippedRecords`. Blank PCV cells are kept at their declared zenith positions.
|
|
456
|
+
- **Breaking.** `Antex.toAntexString()` throws an `AntexWriteError` whose `detail` is `{ kind: "UNWRITABLE", field, reason, message }`. It throws for field overflow, precision loss, validity seconds no 13-column form with a decimal point states, a malformed frequency label, sample coordinates the reader could not rebuild, and antenna fields that disagree with the retained validity intervals. It writes every record from a retained value and adds none the source did not carry, apart from block and section start and end records.
|
|
457
|
+
- New records and lookups:
|
|
458
|
+
- `Antex.header` (`version`, and `pcvType` with `referenceAntenna`), `outerComments`, `antennaBlocks()`, `blockCount`, `antennaIntervals(id)` and `antennaAt(id, epoch)`
|
|
459
|
+
- `Antenna.leadingComments`, `comments`, `calibrations`, `hasFrequencyCount`, `frequencySections()` and `frequency(label)`
|
|
460
|
+
|
|
461
|
+
`Antex.antenna(id)` returns the id's latest block.
|
|
462
|
+
|
|
463
|
+
### SP3
|
|
464
|
+
|
|
465
|
+
- **Breaking.** `Sp3.toSp3String()` throws an `Sp3WriteError` whose `detail` is an `Sp3WriteErrorDetail` union on `kind`, where it always returned text. It writes a field only when reading its columns back gives the stored value bit for bit. It writes an epoch record only when that record restates the stored instant.
|
|
466
|
+
- A value that would read back as an absence sentinel is refused (`RECORD_READS_AS_ABSENT`).
|
|
467
|
+
- An integer-nanosecond epoch is refused (`EPOCH_REPRESENTATION_UNSUPPORTED`).
|
|
468
|
+
- A satellite with no `01`..`99` token is refused (`SATELLITE_NOT_REPRESENTABLE`), where a PRN of 0 used to be written as `G00`.
|
|
469
|
+
- `Sp3WriteErrorDetail` carries each engine `u64` / `i64` as an exact decimal string beside a `number` that is `null` when not exactly representable: `value` / `valueNumber`, `year` / `yearNumber`, `declared` / `declaredNumber`. The writer restates the data-used and file-type descriptors and `%f` bases the product was read with.
|
|
470
|
+
- **Breaking.** `loadSp3` now fails on a non-finite value in the first `%f` line's base fields. A `V` record with no matching `P` record is counted in `skippedRecords` rather than dropped silently. Clock-only records survive reading, writing and merging without creating orbit samples.
|
|
471
|
+
- **Breaking.** `Sp3AgreementMetric.positionRmsM` and `positionMaxM` are `number | undefined`, `undefined` for a merged cell that carries a clock and no position. `positionMembers` is 0 for such a cell.
|
|
472
|
+
- `Sp3MergeReport` states what the merge did not write: `droppedInputEpochs` (input epochs that took no part, each `Sp3DroppedInputEpoch` with `source`, `epochIndex`, `epochJ2000Seconds` and `reason` `"off_target_grid"` or `"not_on_tick_axis"`), `omittedEpochsJ2000Seconds` (union-grid epochs with no accepted cell, which are not written), `arcWithheld` (cells whose preferred source carried no position, as `Sp3MergeFlag`s) and `clockOmissions` (each source clock left out, each `Sp3ClockOmission` with `epochJ2000Seconds`, `satellite`, `source`, `reason` `"datum_not_observable"`, `"preferred_source_without_clock"` or `"no_consensus"`, `preferred` and `cellHasClock`), with `droppedInputEpochCount`, `arcWithheldCount` and `clockOmissionCount`. The epochs of these lists and of every other merge report entry, of `Sp3.predictionSummary()` and of `Sp3ClockReferenceOffset` are reduced to seconds as the engine reduces an SP3 instant. The binding reduced a Julian date with one more rounding, which lands a whole-second epoch of many days a unit in the last place off its second, and gave `NaN` for an integer-nanosecond epoch.
|
|
473
|
+
- `Sp3MergeReport.continuity` returns the merge continuity report, `null` when `verifyContinuity` was not requested: `{ attested, defects, pairsChecked, residualsChecked, residualsSkipped, violations, splices }`. Only window verdicts reached it before, and they carried the splices, not the violations inside one contributor's arc.
|
|
474
|
+
- A merge continuity violation carries `sources` and `cells`, each `{ epochJ2000S, role, selection }` with `role` `"held_out"`, `"interpolation_node"`, `"pair_end"` or `"repeated_epoch"` and `selection` a `CellSelection` (`{ kind: "single_source", source }`, `{ kind: "precedence", source, members }` or `{ kind: "combined", rule, members }`) or `undefined`. A `ContinuityDefect` carries every field of its kind under the engine's name beside the summary fields: `epochJ2000S` and `occurrences`; `intervalS`, `displacementM`, `impliedSpeedMS` and `boundMS`; or `epochJ2000S`, `precedingJ2000S`, `residualM`, `toleranceM` and `nodeEpochsJ2000S`.
|
|
475
|
+
- `Sp3.selectedNodes(satellite, fromJ2000S, throughJ2000S)` and `Sp3MergeReport.continuitySelectedNodes(satellite, fromJ2000S, throughJ2000S)` return the position nodes the interpolations of a satellite in a window select, for a product and for the merged product a merge continuity report holds (`undefined` when `verifyContinuity` was not requested): the nodes window verdicts read.
|
|
476
|
+
- `Sp3MergeReport` carries the engine's whole-product and per-epoch agreement: `positionAgreementRmsM`, `positionAgreementMaxM`, `clockAgreementRmsS`, `clockAgreementMaxS`, `singleSourceFraction` and `perEpochAgreement` (`Sp3EpochAgreement` with `epochJ2000Seconds`, `satellites`, `positionRmsM`, `positionMaxM`, `clockRmsS` and `clockMaxS`).
|
|
477
|
+
- `mergeSp3` takes `provenance: "summary" | "full"` and `Sp3MergeReport.provenance` returns the per-epoch provenance the engine recorded, `{ mode, cells, transitions, coverage }`, or `null` when it was not requested.
|
|
478
|
+
- **Breaking.** `mergeSp3` refuses an option field it does not know with a `TypeError`, as `sp3MergeInputIdentity` does, where it ignored one.
|
|
479
|
+
- **Breaking.** `mergeSp3` takes any `targetEpochIntervalS` that is a whole number of the 10-nanosecond ticks an SP3 interval states, as the engine's merge does, where it threw a `RangeError` for an interval that was not a whole number of seconds. The engine refuses any other interval, and the binding throws its refusal as an `Error`. `sp3MergeInputIdentity` still binds whole seconds only; its refusal of any other interval is now the engine's `Error` ("invalid merged-SP3 policy: target epoch interval") where it was a `RangeError`. A zero, negative or non-finite interval is still a `RangeError`.
|
|
480
|
+
- **Breaking.** `Sp3MergeReport.continuityVerdict(fromJ2000S, throughJ2000S)` takes the window alone, as the engine's merge verdict does: the report holds the merged product's interpolation nodes, and a violation influences a window when the nodes its interpolations select include the violation's held-out, repeated or pair-end record or straddle a handover between its records. It took the merged `Sp3` first and read its stencil extent; in a merge whose source takes over twelve epochs before its run ends, single-epoch windows were refused from epoch 51 where the selected nodes accept them through epoch 67.
|
|
481
|
+
|
|
482
|
+
### Terrain and tides
|
|
483
|
+
|
|
484
|
+
- **Breaking.** A DTED posting holding the null value is now an unknown elevation. Before, a terrain store returned it as a -32767 m height. A `DtedTerrain` or `MmapTerrain` lookup that gives such a posting nonzero weight throws a `TerrainLookupError` whose `detail` is `UNKNOWN_TERRAIN_ELEVATION` with the tile and zero-based posting indices. A query exactly on a known posting next to a null returns that posting, and a neighbouring tile answers on a shared edge.
|
|
485
|
+
- **Breaking.** `DtedTerrain` and `MmapTerrain` lookups (`heightM`, `heightMWithOptions`, `orthometricHeightM*`) throw a `TerrainLookupError` whose `detail` is a `TerrainLookupErrorDetail`, where they threw a plain `Error`. The kinds are `UNKNOWN_TERRAIN_ELEVATION`, `NON_WGS84_TERRAIN_TILE`, `MISSING_TERRAIN_TILE`, `INVALID_INPUT`, `PARSE` and `UNKNOWN`. Failed `heightBatch` / `orthometricHeightBatch` entries gain `detail`. An ellipsoidal lookup whose terrain step fails carries the refusal in `detail.terrain`.
|
|
486
|
+
- **Breaking.** `DtedTerrain` refuses a tile whose DSI states a horizontal datum other than WGS84 (`NON_WGS84_TERRAIN_TILE`, with `datum` as a `DtedHorizontalDatum`). `dtedTreeToMmapStore` and `writeDtedTreeToMmapStore` refuse such a tile with `NonWgs84Tile`. A blank datum still reads as WGS84.
|
|
487
|
+
- **Breaking.** DTED tiles are checked against their UHL metadata, and a tile whose origin disagrees with its file name is refused; it used to read as sea level. A posting below -16000 m other than the null is read as two's complement, as GDAL reads it. Terrain stores built before this release keep the value decoded at conversion.
|
|
488
|
+
- **Breaking.** `MmapTerrain.fromBytes`, `fromVec` and `fromPath` refuse an index record whose tile id lies outside the coordinate domain (`TileIdOutOfRange`). They also refuse one whose bounds are not its one-degree cell's edges (`TileBoundsMismatch`, with `field`).
|
|
489
|
+
- Terrain-store bilinear lookup agrees bit for bit with raw DTED lookup west of the prime meridian and south of the equator.
|
|
490
|
+
- New `parseOceanLoadingBlqBlock(text)`, `parseOceanLoadingBlqBlocks(text)`, `writeOceanLoadingBlqBlock(block)` and `writeOceanLoadingBlqBlocks(blocks)`. They read and write `{ station, amplitudeM, phaseDeg, comments }` blocks in the standard constituent order. Each retained comment and column-order header line is kept with its `placement` (`beforeStation`, `beforeRow` with `row`, or `afterRows`).
|
|
491
|
+
- A BLQ column-order header is one of:
|
|
492
|
+
- a `COLUMN ORDER` declaration
|
|
493
|
+
- a line of labels only
|
|
494
|
+
- a comment in which a word `ORDER` is followed by labels to the end of the line
|
|
495
|
+
|
|
496
|
+
A header naming a label outside the eleven constituents, repeating one, or holding another count is refused with a `BlqParseError`.
|
|
497
|
+
- The BLQ writer throws a `BlqWriteError` with the zero-based `block` index for a block that would not read back unchanged:
|
|
498
|
+
- a station that is empty, holds a line break or surrounding whitespace, or reads as a comment, header or row
|
|
499
|
+
- a non-finite coefficient
|
|
500
|
+
- a comment that is misplaced or would not read as a comment
|
|
501
|
+
- a comment after the rows of any block but the last
|
|
502
|
+
|
|
503
|
+
### Fixed
|
|
504
|
+
|
|
505
|
+
- The 2.1.1 documentation of the `MmapTerrain` lookups said a missing tile
|
|
506
|
+
evaluates to `0.0`. A lookup on a missing tile threw in 2.1.1 and throws now,
|
|
507
|
+
as a `TerrainLookupError` with `MISSING_TERRAIN_TILE`; the documentation says
|
|
508
|
+
so.
|
|
509
|
+
|
|
510
|
+
## 2.1.1 - 2026-09-22
|
|
511
|
+
|
|
512
|
+
### Fixed
|
|
513
|
+
|
|
514
|
+
- Engine update: sidereon 2.1.1 / sidereon-core 2.1.1. CODE predicted
|
|
515
|
+
ionosphere maps resolve to the archive AIUB now serves them from: `cod_prd1`
|
|
516
|
+
is `CODE/IONO/PRD/COD0OPSP0D_<date>0000_01D_01H_GIM.INX.gz` and `cod_prd2`
|
|
517
|
+
is `CODE/IONO/PRD/COD0OPSP1D_<date>0000_01D_01H_GIM.INX.gz`. The
|
|
518
|
+
`CODE/IONO/P1/<year>` and `CODE/IONO/P2/<year>` `COD0OPSPRD` trees they were
|
|
519
|
+
read from stopped receiving issues after 2026-09-21 and are now empty, so
|
|
520
|
+
every predicted-IONEX URL `distributionLocation` and
|
|
521
|
+
`predictedIonexLineCandidates` returned pointed at an object that no longer
|
|
522
|
+
exists. For the dates both layouts carried the objects decompress to the
|
|
523
|
+
same bytes.
|
|
524
|
+
- The two predicted lines now carry distinct official filenames, so
|
|
525
|
+
`productIdentity("cod_prd1", ...)` and `productIdentity("cod_prd2", ...)`
|
|
526
|
+
for one map date differ in `officialFilename` as well as in
|
|
527
|
+
`predictionHorizonDays` and `cacheKey`.
|
|
528
|
+
- `newestPublishedProduct` and `resolveFirstPublishedPredictedIonex` attribute
|
|
529
|
+
only objects under `CODE/IONO/PRD/` to a predicted line, not the rolling
|
|
530
|
+
copies CODE keeps at the tree root.
|
|
531
|
+
- The JavaScript API is unchanged.
|
|
532
|
+
|
|
3
533
|
## 2.1.0 - 2026-09-05
|
|
4
534
|
|
|
5
535
|
### Added
|