gnss-js 1.24.0 → 1.26.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.
@@ -0,0 +1,189 @@
1
+ import { E as Ephemeris } from './nav-DImXUdbp.cjs';
2
+
3
+ /**
4
+ * BINEX (BINary EXchange) framing, shared by the observation and
5
+ * navigation decoders.
6
+ *
7
+ * A forward-readable record is: a 1-byte sync that encodes byte order and
8
+ * checksum model, a record ID (ubnxi), a message length (ubnxi), the
9
+ * message body, then a trailing checksum whose width depends on the
10
+ * covered-byte count. Sync bytes (ref [1] §"Format Details"):
11
+ *
12
+ * 0xC2 little-endian, regular CRC (forward)
13
+ * 0xE2 big-endian, regular CRC (forward)
14
+ * 0xC8 little-endian, enhanced CRC (forward)
15
+ * 0xE8 big-endian, enhanced CRC (forward)
16
+ *
17
+ * The reverse-readable variants (0xD2/0xF2/0xD8/0xF8 leading and
18
+ * 0xB4/0xB0/0xE4/0xE0 terminating) are NOT decoded here (deferred).
19
+ *
20
+ * Checksum widths over the covered bytes N = recordID + lengthField +
21
+ * body (regular model): N ≤ 127 → 1-byte XOR; 128–4095 → CRC16
22
+ * (x^16+x^12+x^5+1, i.e. poly 0x1021 MSB-first, init 0); 4096–1048575 →
23
+ * CRC32 (poly 0x04C11DB7 MSB-first); ≥ 1048576 → 16-byte MD5. Enhanced
24
+ * model shifts every threshold up one step (CRC16 from 0 bytes, no XOR).
25
+ * The 16-byte MD5 case is not implemented (records that large do not
26
+ * occur in GNSS obs/eph streams); such a record is treated as bad.
27
+ *
28
+ * ubnxi (1–4 byte unsigned) and the field layouts are ported from RTKLIB
29
+ * demo5/2.4.3 src/rcv/binex.c (getbnxi, decode_bnx, Copyright (c)
30
+ * 2013-2018 T. Takasu, BSD-2-Clause) and cross-checked against the
31
+ * EarthScope/UNAVCO BINEX definition [1].
32
+ *
33
+ * [1] UNAVCO/EarthScope, BINEX: Binary exchange format
34
+ * (http://binex.unavco.org/binex.html).
35
+ */
36
+ /** BINEX 2-byte CRC (x^16+x^12+x^5+1), init 0, MSB-first. */
37
+ declare function binexCrc16(data: Uint8Array, start: number, end: number): number;
38
+ /**
39
+ * BINEX 4-byte CRC (poly 0x04C11DB7), init 0, MSB-first. Not exercised
40
+ * by any known fixture (obs/eph records use XOR-8 or CRC16); implemented
41
+ * to spec but fixture-unverified.
42
+ */
43
+ declare function binexCrc32(data: Uint8Array, start: number, end: number): number;
44
+ /** 8-bit XOR checksum of `data[start, end)`. */
45
+ declare function binexCsum8(data: Uint8Array, start: number, end: number): number;
46
+ /**
47
+ * Decode a BINEX ubnxi (1–4 byte unsigned integer, MSB-first) at
48
+ * `data[pos]`. The first up-to-three bytes contribute their low 7 bits
49
+ * with the high bit as a continuation flag; a fourth byte (reached only
50
+ * when the first three all continue) contributes all 8 bits. Returns the
51
+ * value and the number of bytes consumed (1–4). Ported from RTKLIB
52
+ * getbnxi.
53
+ */
54
+ declare function getBnxi(data: Uint8Array, pos: number): {
55
+ value: number;
56
+ size: number;
57
+ };
58
+ /** One checksum-valid forward BINEX record located in a byte stream. */
59
+ interface BinexRecord {
60
+ /** Record ID (e.g. 0x01, 0x7f). */
61
+ id: number;
62
+ /** Offset of the sync byte. */
63
+ start: number;
64
+ /** Offset of the message body (first byte after the length field). */
65
+ body: number;
66
+ /** Message body length in bytes. */
67
+ len: number;
68
+ /** True when the sync selects little-endian field byte order. */
69
+ littleEndian: boolean;
70
+ /** True when the sync selects the enhanced-CRC model. */
71
+ enhanced: boolean;
72
+ }
73
+ /**
74
+ * Iterate every checksum-valid forward BINEX record in `data`. Corrupt
75
+ * or truncated candidates resync at the next byte; checksum failures
76
+ * additionally increment `stats.badCrc`. Reverse-readable records are
77
+ * not recognised.
78
+ */
79
+ declare function binexRecords(data: Uint8Array, view: DataView, stats: {
80
+ badCrc: number;
81
+ }): Generator<BinexRecord>;
82
+
83
+ /**
84
+ * BINEX record 0x01 — decoded GNSS ephemeris — with the per-constellation
85
+ * subrecords laid out in RTKLIB src/rcv/binex.c:
86
+ *
87
+ * 0x01-01 GPS LNAV (decode_bnx_01_01)
88
+ * 0x01-02 GLONASS (decode_bnx_01_02)
89
+ * 0x01-03 SBAS (decode_bnx_01_03)
90
+ * 0x01-04 Galileo (decode_bnx_01_04)
91
+ * 0x01-05 BeiDou-2/3 (decode_bnx_01_05)
92
+ * 0x01-06 QZSS (decode_bnx_01_06)
93
+ *
94
+ * Ported from RTKLIB demo5/2.4.3 src/rcv/binex.c (Copyright (c) 2013-2018
95
+ * T. Takasu, BSD-2-Clause) and cross-checked against the EarthScope/UNAVCO
96
+ * BINEX definition and its reference RINEX fixtures.
97
+ *
98
+ * Output records mirror what `parseNavFile` produces for the equivalent
99
+ * RINEX navigation file, exactly like the SBF/NovAtel decoders:
100
+ * - the Keplerian angles M0/Ω0/ω/i0 arrive as radians on the wire (R8),
101
+ * while the rate terms Δn/Ω̇/İ arrive as semicircles (R4) and are
102
+ * scaled by π (IS-GPS-200 SC2RAD);
103
+ * - BeiDou epochs and weeks stay on the BDT scale (no BDT→GPST shift),
104
+ * matching a RINEX BDS record and the NovAtel decoder;
105
+ * - GLONASS state vectors are in km (PZ-90), the clock is −τ_n (the
106
+ * RINEX sign), the reference epoch is UTC, and `messageFrameTime` is
107
+ * the frame time `tof` as broadcast (seconds of day);
108
+ * - deferred subrecords: 0x01-00 (raw-byte ephemeris) and 0x01-14 (a
109
+ * newer Galileo encoding not covered by RTKLIB).
110
+ */
111
+
112
+ /**
113
+ * Decode a record-0x01 ephemeris body. `body` points at the subrecord-ID
114
+ * byte. Returns the decoded `Ephemeris`, or null for an unsupported or
115
+ * malformed subrecord. Minimum body lengths mirror RTKLIB's guards.
116
+ */
117
+ declare function decodeBinexEph(view: DataView, body: number, len: number, le: boolean): Ephemeris | null;
118
+
119
+ /**
120
+ * BINEX (BINary EXchange) decoding — the path from a receiver BINEX log
121
+ * to RINEX-grade observables and broadcast ephemerides, mirroring the
122
+ * SBF and NovAtel decoders in this package.
123
+ *
124
+ * `parseBinex` runs a single framing pass over the byte stream and emits
125
+ * both observation epochs (record 0x7f-05, the obs record `convbin`
126
+ * reads) and decoded `Ephemeris` records (record 0x01, per-constellation
127
+ * subrecords). Frame walking, the ubnxi variable-length integer, the
128
+ * XOR-8/CRC16/CRC32 checksum models and the field layouts are ported
129
+ * from RTKLIB demo5/2.4.3 src/rcv/binex.c (Copyright (c) 2013-2018
130
+ * T. Takasu, BSD-2-Clause) and cross-checked against the
131
+ * EarthScope/UNAVCO BINEX definition and its reference RINEX fixtures.
132
+ *
133
+ * Framing (forward records only): a sync byte (0xE2 big-endian /
134
+ * 0xC2 little-endian, regular CRC; 0xE8/0xC8 enhanced CRC), the record
135
+ * ID, the message length (ubnxi), the body, then the checksum. The
136
+ * subrecord ID is the first body byte. Reverse-readable records
137
+ * (0xD2/0xF2/0xD8/0xF8 and terminating variants) are NOT decoded.
138
+ *
139
+ * Implemented: 0x7f-05 observations (multi-GNSS pseudorange, carrier
140
+ * phase, Doppler, C/N0, slip) and 0x01-01…06 ephemeris (GPS, GLONASS,
141
+ * SBAS, Galileo, BeiDou, QZSS). Deferred (counted, skipped): 0x00 site
142
+ * metadata, 0x02/0x03 generalized data, 0x7d/0x7e prototyping, the
143
+ * 0x7f-00…04 obs prototypes, 0x01-00 raw-byte ephemeris and 0x01-14.
144
+ */
145
+
146
+ interface BinexMeasurement {
147
+ /** RINEX PRN, e.g. "G04", "R11", "S23", "J01". */
148
+ prn: string;
149
+ /** RINEX band+attribute, e.g. "1C", "2W", "5Q", "2I". */
150
+ code: string;
151
+ /** Pseudorange (m). */
152
+ pr: number | null;
153
+ /** Carrier phase (cycles, RINEX sign), null when the frequency is unknown. */
154
+ cp: number | null;
155
+ /** Doppler (Hz), null when the record carries none for the signal. */
156
+ doppler: number | null;
157
+ /** C/N0 (dB-Hz). */
158
+ cn0: number | null;
159
+ /** Loss-of-lock / cycle-slip flag. */
160
+ slip: boolean;
161
+ }
162
+ interface BinexEpoch {
163
+ /** Receiver epoch (GPS-scale ms — same convention as the RINEX parser). */
164
+ timeMs: number;
165
+ meas: BinexMeasurement[];
166
+ }
167
+ interface BinexParseResult {
168
+ /** Observation epochs (record 0x7f-05), one per record. */
169
+ epochs: BinexEpoch[];
170
+ /** Broadcast ephemerides (record 0x01), duplicates suppressed. */
171
+ ephemerides: Ephemeris[];
172
+ /** Count per record/subrecord, keyed "0x7f-05" / "0x01-01" style. */
173
+ messageCounts: Record<string, number>;
174
+ /** Observation codes seen per system letter, in first-seen order. */
175
+ obsCodes: Record<string, string[]>;
176
+ /** Records whose checksum failed (corruption indicator). */
177
+ badCrc: number;
178
+ }
179
+ /**
180
+ * Decode every supported BINEX record in a byte stream: 0x7f-05
181
+ * observation epochs and 0x01-01…06 broadcast ephemerides. Repeated
182
+ * broadcasts of an unchanged ephemeris are suppressed the way RTKLIB
183
+ * does: GPS/Galileo/QZSS/BeiDou by issue of data (iode) plus toe and toc,
184
+ * GLONASS/SBAS by reference epoch and health. Other records are counted
185
+ * and skipped; checksum failures resync at the next byte.
186
+ */
187
+ declare function parseBinex(data: Uint8Array): BinexParseResult;
188
+
189
+ export { type BinexEpoch, type BinexMeasurement, type BinexParseResult, type BinexRecord, binexCrc16, binexCrc32, binexCsum8, binexRecords, decodeBinexEph, getBnxi, parseBinex };
@@ -0,0 +1,189 @@
1
+ import { E as Ephemeris } from './nav-DImXUdbp.js';
2
+
3
+ /**
4
+ * BINEX (BINary EXchange) framing, shared by the observation and
5
+ * navigation decoders.
6
+ *
7
+ * A forward-readable record is: a 1-byte sync that encodes byte order and
8
+ * checksum model, a record ID (ubnxi), a message length (ubnxi), the
9
+ * message body, then a trailing checksum whose width depends on the
10
+ * covered-byte count. Sync bytes (ref [1] §"Format Details"):
11
+ *
12
+ * 0xC2 little-endian, regular CRC (forward)
13
+ * 0xE2 big-endian, regular CRC (forward)
14
+ * 0xC8 little-endian, enhanced CRC (forward)
15
+ * 0xE8 big-endian, enhanced CRC (forward)
16
+ *
17
+ * The reverse-readable variants (0xD2/0xF2/0xD8/0xF8 leading and
18
+ * 0xB4/0xB0/0xE4/0xE0 terminating) are NOT decoded here (deferred).
19
+ *
20
+ * Checksum widths over the covered bytes N = recordID + lengthField +
21
+ * body (regular model): N ≤ 127 → 1-byte XOR; 128–4095 → CRC16
22
+ * (x^16+x^12+x^5+1, i.e. poly 0x1021 MSB-first, init 0); 4096–1048575 →
23
+ * CRC32 (poly 0x04C11DB7 MSB-first); ≥ 1048576 → 16-byte MD5. Enhanced
24
+ * model shifts every threshold up one step (CRC16 from 0 bytes, no XOR).
25
+ * The 16-byte MD5 case is not implemented (records that large do not
26
+ * occur in GNSS obs/eph streams); such a record is treated as bad.
27
+ *
28
+ * ubnxi (1–4 byte unsigned) and the field layouts are ported from RTKLIB
29
+ * demo5/2.4.3 src/rcv/binex.c (getbnxi, decode_bnx, Copyright (c)
30
+ * 2013-2018 T. Takasu, BSD-2-Clause) and cross-checked against the
31
+ * EarthScope/UNAVCO BINEX definition [1].
32
+ *
33
+ * [1] UNAVCO/EarthScope, BINEX: Binary exchange format
34
+ * (http://binex.unavco.org/binex.html).
35
+ */
36
+ /** BINEX 2-byte CRC (x^16+x^12+x^5+1), init 0, MSB-first. */
37
+ declare function binexCrc16(data: Uint8Array, start: number, end: number): number;
38
+ /**
39
+ * BINEX 4-byte CRC (poly 0x04C11DB7), init 0, MSB-first. Not exercised
40
+ * by any known fixture (obs/eph records use XOR-8 or CRC16); implemented
41
+ * to spec but fixture-unverified.
42
+ */
43
+ declare function binexCrc32(data: Uint8Array, start: number, end: number): number;
44
+ /** 8-bit XOR checksum of `data[start, end)`. */
45
+ declare function binexCsum8(data: Uint8Array, start: number, end: number): number;
46
+ /**
47
+ * Decode a BINEX ubnxi (1–4 byte unsigned integer, MSB-first) at
48
+ * `data[pos]`. The first up-to-three bytes contribute their low 7 bits
49
+ * with the high bit as a continuation flag; a fourth byte (reached only
50
+ * when the first three all continue) contributes all 8 bits. Returns the
51
+ * value and the number of bytes consumed (1–4). Ported from RTKLIB
52
+ * getbnxi.
53
+ */
54
+ declare function getBnxi(data: Uint8Array, pos: number): {
55
+ value: number;
56
+ size: number;
57
+ };
58
+ /** One checksum-valid forward BINEX record located in a byte stream. */
59
+ interface BinexRecord {
60
+ /** Record ID (e.g. 0x01, 0x7f). */
61
+ id: number;
62
+ /** Offset of the sync byte. */
63
+ start: number;
64
+ /** Offset of the message body (first byte after the length field). */
65
+ body: number;
66
+ /** Message body length in bytes. */
67
+ len: number;
68
+ /** True when the sync selects little-endian field byte order. */
69
+ littleEndian: boolean;
70
+ /** True when the sync selects the enhanced-CRC model. */
71
+ enhanced: boolean;
72
+ }
73
+ /**
74
+ * Iterate every checksum-valid forward BINEX record in `data`. Corrupt
75
+ * or truncated candidates resync at the next byte; checksum failures
76
+ * additionally increment `stats.badCrc`. Reverse-readable records are
77
+ * not recognised.
78
+ */
79
+ declare function binexRecords(data: Uint8Array, view: DataView, stats: {
80
+ badCrc: number;
81
+ }): Generator<BinexRecord>;
82
+
83
+ /**
84
+ * BINEX record 0x01 — decoded GNSS ephemeris — with the per-constellation
85
+ * subrecords laid out in RTKLIB src/rcv/binex.c:
86
+ *
87
+ * 0x01-01 GPS LNAV (decode_bnx_01_01)
88
+ * 0x01-02 GLONASS (decode_bnx_01_02)
89
+ * 0x01-03 SBAS (decode_bnx_01_03)
90
+ * 0x01-04 Galileo (decode_bnx_01_04)
91
+ * 0x01-05 BeiDou-2/3 (decode_bnx_01_05)
92
+ * 0x01-06 QZSS (decode_bnx_01_06)
93
+ *
94
+ * Ported from RTKLIB demo5/2.4.3 src/rcv/binex.c (Copyright (c) 2013-2018
95
+ * T. Takasu, BSD-2-Clause) and cross-checked against the EarthScope/UNAVCO
96
+ * BINEX definition and its reference RINEX fixtures.
97
+ *
98
+ * Output records mirror what `parseNavFile` produces for the equivalent
99
+ * RINEX navigation file, exactly like the SBF/NovAtel decoders:
100
+ * - the Keplerian angles M0/Ω0/ω/i0 arrive as radians on the wire (R8),
101
+ * while the rate terms Δn/Ω̇/İ arrive as semicircles (R4) and are
102
+ * scaled by π (IS-GPS-200 SC2RAD);
103
+ * - BeiDou epochs and weeks stay on the BDT scale (no BDT→GPST shift),
104
+ * matching a RINEX BDS record and the NovAtel decoder;
105
+ * - GLONASS state vectors are in km (PZ-90), the clock is −τ_n (the
106
+ * RINEX sign), the reference epoch is UTC, and `messageFrameTime` is
107
+ * the frame time `tof` as broadcast (seconds of day);
108
+ * - deferred subrecords: 0x01-00 (raw-byte ephemeris) and 0x01-14 (a
109
+ * newer Galileo encoding not covered by RTKLIB).
110
+ */
111
+
112
+ /**
113
+ * Decode a record-0x01 ephemeris body. `body` points at the subrecord-ID
114
+ * byte. Returns the decoded `Ephemeris`, or null for an unsupported or
115
+ * malformed subrecord. Minimum body lengths mirror RTKLIB's guards.
116
+ */
117
+ declare function decodeBinexEph(view: DataView, body: number, len: number, le: boolean): Ephemeris | null;
118
+
119
+ /**
120
+ * BINEX (BINary EXchange) decoding — the path from a receiver BINEX log
121
+ * to RINEX-grade observables and broadcast ephemerides, mirroring the
122
+ * SBF and NovAtel decoders in this package.
123
+ *
124
+ * `parseBinex` runs a single framing pass over the byte stream and emits
125
+ * both observation epochs (record 0x7f-05, the obs record `convbin`
126
+ * reads) and decoded `Ephemeris` records (record 0x01, per-constellation
127
+ * subrecords). Frame walking, the ubnxi variable-length integer, the
128
+ * XOR-8/CRC16/CRC32 checksum models and the field layouts are ported
129
+ * from RTKLIB demo5/2.4.3 src/rcv/binex.c (Copyright (c) 2013-2018
130
+ * T. Takasu, BSD-2-Clause) and cross-checked against the
131
+ * EarthScope/UNAVCO BINEX definition and its reference RINEX fixtures.
132
+ *
133
+ * Framing (forward records only): a sync byte (0xE2 big-endian /
134
+ * 0xC2 little-endian, regular CRC; 0xE8/0xC8 enhanced CRC), the record
135
+ * ID, the message length (ubnxi), the body, then the checksum. The
136
+ * subrecord ID is the first body byte. Reverse-readable records
137
+ * (0xD2/0xF2/0xD8/0xF8 and terminating variants) are NOT decoded.
138
+ *
139
+ * Implemented: 0x7f-05 observations (multi-GNSS pseudorange, carrier
140
+ * phase, Doppler, C/N0, slip) and 0x01-01…06 ephemeris (GPS, GLONASS,
141
+ * SBAS, Galileo, BeiDou, QZSS). Deferred (counted, skipped): 0x00 site
142
+ * metadata, 0x02/0x03 generalized data, 0x7d/0x7e prototyping, the
143
+ * 0x7f-00…04 obs prototypes, 0x01-00 raw-byte ephemeris and 0x01-14.
144
+ */
145
+
146
+ interface BinexMeasurement {
147
+ /** RINEX PRN, e.g. "G04", "R11", "S23", "J01". */
148
+ prn: string;
149
+ /** RINEX band+attribute, e.g. "1C", "2W", "5Q", "2I". */
150
+ code: string;
151
+ /** Pseudorange (m). */
152
+ pr: number | null;
153
+ /** Carrier phase (cycles, RINEX sign), null when the frequency is unknown. */
154
+ cp: number | null;
155
+ /** Doppler (Hz), null when the record carries none for the signal. */
156
+ doppler: number | null;
157
+ /** C/N0 (dB-Hz). */
158
+ cn0: number | null;
159
+ /** Loss-of-lock / cycle-slip flag. */
160
+ slip: boolean;
161
+ }
162
+ interface BinexEpoch {
163
+ /** Receiver epoch (GPS-scale ms — same convention as the RINEX parser). */
164
+ timeMs: number;
165
+ meas: BinexMeasurement[];
166
+ }
167
+ interface BinexParseResult {
168
+ /** Observation epochs (record 0x7f-05), one per record. */
169
+ epochs: BinexEpoch[];
170
+ /** Broadcast ephemerides (record 0x01), duplicates suppressed. */
171
+ ephemerides: Ephemeris[];
172
+ /** Count per record/subrecord, keyed "0x7f-05" / "0x01-01" style. */
173
+ messageCounts: Record<string, number>;
174
+ /** Observation codes seen per system letter, in first-seen order. */
175
+ obsCodes: Record<string, string[]>;
176
+ /** Records whose checksum failed (corruption indicator). */
177
+ badCrc: number;
178
+ }
179
+ /**
180
+ * Decode every supported BINEX record in a byte stream: 0x7f-05
181
+ * observation epochs and 0x01-01…06 broadcast ephemerides. Repeated
182
+ * broadcasts of an unchanged ephemeris are suppressed the way RTKLIB
183
+ * does: GPS/Galileo/QZSS/BeiDou by issue of data (iode) plus toe and toc,
184
+ * GLONASS/SBAS by reference epoch and health. Other records are counted
185
+ * and skipped; checksum failures resync at the next byte.
186
+ */
187
+ declare function parseBinex(data: Uint8Array): BinexParseResult;
188
+
189
+ export { type BinexEpoch, type BinexMeasurement, type BinexParseResult, type BinexRecord, binexCrc16, binexCrc32, binexCsum8, binexRecords, decodeBinexEph, getBnxi, parseBinex };
package/dist/binex.js ADDED
@@ -0,0 +1,19 @@
1
+ import {
2
+ binexCrc16,
3
+ binexCrc32,
4
+ binexCsum8,
5
+ binexRecords,
6
+ decodeBinexEph,
7
+ getBnxi,
8
+ parseBinex
9
+ } from "./chunk-OK4GGNOO.js";
10
+ import "./chunk-IQ5OIMQD.js";
11
+ export {
12
+ binexCrc16,
13
+ binexCrc32,
14
+ binexCsum8,
15
+ binexRecords,
16
+ decodeBinexEph,
17
+ getBnxi,
18
+ parseBinex
19
+ };
@@ -43,6 +43,51 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
43
43
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
44
44
  }
45
45
 
46
+ // src/positioning/gim.ts
47
+ var R_E = 6371e3;
48
+ var H_ION = 45e4;
49
+ var F_L1 = 157542e4;
50
+ var IONO_L1_M_PER_TECU = 403e15 / (F_L1 * F_L1);
51
+ function tecAtMap(grid, mapIdx, latDeg, lonDeg) {
52
+ const m = grid.maps[mapIdx];
53
+ const { lats, lons } = grid;
54
+ const w = lons.length;
55
+ const fi = (latDeg - lats[0]) / (lats[lats.length - 1] - lats[0]) * (lats.length - 1);
56
+ const fj = (lonDeg - lons[0]) / (lons[w - 1] - lons[0]) * (w - 1);
57
+ const i0 = Math.max(0, Math.min(lats.length - 2, Math.floor(fi)));
58
+ const j0 = Math.max(0, Math.min(w - 2, Math.floor(fj)));
59
+ const di = Math.max(0, Math.min(1, fi - i0));
60
+ const dj = Math.max(0, Math.min(1, fj - j0));
61
+ return m[i0 * w + j0] * (1 - di) * (1 - dj) + m[i0 * w + j0 + 1] * (1 - di) * dj + m[(i0 + 1) * w + j0] * di * (1 - dj) + m[(i0 + 1) * w + j0 + 1] * di * dj;
62
+ }
63
+ function gimVerticalTec(grid, timeMs, latDeg, lonDeg) {
64
+ const e = grid.epochs;
65
+ if (e.length === 0 || timeMs < e[0] || timeMs > e[e.length - 1]) return null;
66
+ let i = 0;
67
+ while (i < e.length - 2 && e[i + 1] <= timeMs) i++;
68
+ const span = e[i + 1] - e[i];
69
+ const f = span > 0 ? (timeMs - e[i]) / span : 0;
70
+ const a = tecAtMap(grid, i, latDeg, lonDeg);
71
+ const b = tecAtMap(grid, i + 1, latDeg, lonDeg);
72
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
73
+ return a * (1 - f) + b * f;
74
+ }
75
+ function gimSlantIonoDelayL1(grid, latRad, lonRad, azRad, elRad, timeMs) {
76
+ if (elRad <= 0) return null;
77
+ const sinZp = R_E / (R_E + H_ION) * Math.cos(elRad);
78
+ const mapping = 1 / Math.sqrt(1 - sinZp * sinZp);
79
+ const psi = Math.PI / 2 - elRad - Math.asin(sinZp);
80
+ const latI = Math.asin(
81
+ Math.sin(latRad) * Math.cos(psi) + Math.cos(latRad) * Math.sin(psi) * Math.cos(azRad)
82
+ );
83
+ const lonI = lonRad + Math.asin(Math.sin(psi) * Math.sin(azRad) / Math.cos(latI));
84
+ const latDeg = latI * 180 / Math.PI;
85
+ const lonDeg = ((lonI * 180 / Math.PI + 540) % 360 + 360) % 360 - 180;
86
+ const vtec = gimVerticalTec(grid, timeMs, latDeg, lonDeg);
87
+ if (vtec === null) return null;
88
+ return IONO_L1_M_PER_TECU * vtec * mapping;
89
+ }
90
+
46
91
  // src/positioning/lambda.ts
47
92
  var LOOPMAX = 1e4;
48
93
  var ROUND = (x) => Math.floor(x + 0.5);
@@ -1084,10 +1129,17 @@ var PRIMARY_FREQ_HZ = {
1084
1129
  C: 1561098e3,
1085
1130
  R: 1602e6
1086
1131
  };
1087
- var F_L1 = 157542e4;
1088
- function tropoDelay2(elevationRad) {
1089
- const sinEl = Math.sin(elevationRad);
1090
- return 2.47 / (sinEl + 0.0121);
1132
+ var F_L12 = 157542e4;
1133
+ function tropoDelay2(elevationRad, latRad, heightM) {
1134
+ if (elevationRad <= 0) return 0;
1135
+ const h = heightM < 0 ? 0 : heightM > 1e4 ? 1e4 : heightM;
1136
+ const humi = 0.7;
1137
+ const pres = 1013.25 * Math.pow(1 - 22557e-9 * h, 5.2568);
1138
+ const temp = 15 - 65e-4 * h + 273.16;
1139
+ const e = 6.108 * humi * Math.exp((17.15 * temp - 4684) / (temp - 38.45));
1140
+ const zhd = 22768e-7 * pres / (1 - 266e-5 * Math.cos(2 * latRad) - 28e-5 * (h / 1e3));
1141
+ const zwd = 2277e-6 * (1255 / temp + 0.05) * e;
1142
+ return (zhd + zwd) / Math.sin(elevationRad);
1091
1143
  }
1092
1144
  function sagnac2(pos, travelTimeS) {
1093
1145
  const theta = OMEGA_E * travelTimeS;
@@ -1120,7 +1172,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1120
1172
  maxIterations = 15,
1121
1173
  convergenceM = 1e-4,
1122
1174
  tgd = true,
1123
- iono
1175
+ iono,
1176
+ gim
1124
1177
  } = opts;
1125
1178
  const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
1126
1179
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
@@ -1147,7 +1200,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1147
1200
  let rows = 0;
1148
1201
  for (const k of Object.keys(residuals2)) delete residuals2[k];
1149
1202
  const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
1150
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
1203
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
1151
1204
  for (const prn of prns) {
1152
1205
  const psr = pseudoranges.get(prn);
1153
1206
  const eph = ephemerides.get(prn);
@@ -1177,11 +1230,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1177
1230
  const sinEl = Math.max(Math.sin(elev), 0.1);
1178
1231
  weight = sinEl * sinEl;
1179
1232
  }
1180
- const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
1233
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
1181
1234
  let ionoM = 0;
1182
- if (iono && positionSane) {
1183
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
1184
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
1235
+ if ((gim || iono) && positionSane) {
1236
+ let l1M = null;
1237
+ if (gim) l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
1238
+ if (l1M === null && iono) {
1239
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
1240
+ }
1241
+ if (l1M !== null) {
1242
+ const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
1243
+ ionoM = l1M * (F_L12 / f * (F_L12 / f));
1244
+ }
1185
1245
  }
1186
1246
  const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
1187
1247
  const v = psr - predicted;
@@ -1263,6 +1323,9 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1263
1323
 
1264
1324
  export {
1265
1325
  klobucharDelay,
1326
+ IONO_L1_M_PER_TECU,
1327
+ gimVerticalTec,
1328
+ gimSlantIonoDelayL1,
1266
1329
  lambdaSearch,
1267
1330
  lambdaReduction,
1268
1331
  toRtkEpoch,
@@ -1,9 +1,11 @@
1
+ import {
2
+ crc24q
3
+ } from "./chunk-726LQBGM.js";
1
4
  import {
2
5
  GPS_PI,
3
- crc24q,
4
6
  getBitS,
5
7
  getBitU
6
- } from "./chunk-WZDFZ76K.js";
8
+ } from "./chunk-IQ5OIMQD.js";
7
9
  import {
8
10
  getUtcDate
9
11
  } from "./chunk-HVXYFUCB.js";