gnss-js 1.10.0 → 1.12.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/dist/{chunk-AK6LNWZX.js → chunk-DMNFB7Q6.js} +57 -7
- package/dist/{chunk-77VUSDNI.js → chunk-HXF7XVW7.js} +499 -1
- package/dist/{chunk-ZTFKRZMM.js → chunk-LKFSXVIZ.js} +60 -4
- package/dist/index.cjs +623 -12
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +27 -5
- package/dist/orbit.cjs +59 -7
- package/dist/orbit.d.cts +12 -2
- package/dist/orbit.d.ts +12 -2
- package/dist/orbit.js +5 -1
- package/dist/positioning.cjs +101 -10
- package/dist/positioning.d.cts +44 -4
- package/dist/positioning.d.ts +44 -4
- package/dist/positioning.js +4 -2
- package/dist/rinex.cjs +508 -2
- package/dist/rinex.d.cts +129 -1
- package/dist/rinex.d.ts +129 -1
- package/dist/rinex.js +19 -3
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
START_GPS_TIME
|
|
10
10
|
} from "./chunk-LEEU5OIO.js";
|
|
11
11
|
import {
|
|
12
|
+
C_LIGHT,
|
|
12
13
|
OMEGA_E
|
|
13
14
|
} from "./chunk-FIEWO4J4.js";
|
|
14
15
|
|
|
@@ -27,6 +28,7 @@ function gmForSystem(sys) {
|
|
|
27
28
|
if (sys === "C") return GM_BDS;
|
|
28
29
|
return GM_GPS;
|
|
29
30
|
}
|
|
31
|
+
var geoVelocityPass = false;
|
|
30
32
|
function keplerPosition(eph, t) {
|
|
31
33
|
const GM = gmForSystem(eph.system);
|
|
32
34
|
const a = eph.sqrtA * eph.sqrtA;
|
|
@@ -54,8 +56,16 @@ function keplerPosition(eph, t) {
|
|
|
54
56
|
const uk = phik + duk;
|
|
55
57
|
const rk = a * (1 - eph.e * cosE) + drk;
|
|
56
58
|
const ik = eph.i0 + dik + eph.idot * tk;
|
|
59
|
+
const EkDot = n / (1 - eph.e * cosE);
|
|
60
|
+
const vkDot = EkDot * Math.sqrt(1 - eph.e * eph.e) / (1 - eph.e * cosE);
|
|
61
|
+
const dukDot = 2 * vkDot * (eph.cus * cos2phi - eph.cuc * sin2phi);
|
|
62
|
+
const drkDot = a * eph.e * sinE * EkDot + 2 * vkDot * (eph.crs * cos2phi - eph.crc * sin2phi);
|
|
63
|
+
const dikDot = eph.idot + 2 * vkDot * (eph.cis * cos2phi - eph.cic * sin2phi);
|
|
64
|
+
const ukDot = vkDot + dukDot;
|
|
57
65
|
const xp = rk * Math.cos(uk);
|
|
58
66
|
const yp = rk * Math.sin(uk);
|
|
67
|
+
const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
|
|
68
|
+
const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
|
|
59
69
|
const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
|
|
60
70
|
if (isBdsGeo) {
|
|
61
71
|
const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
|
|
@@ -71,23 +81,43 @@ function keplerPosition(eph, t) {
|
|
|
71
81
|
const sinPhi = Math.sin(phi);
|
|
72
82
|
const COS5 = Math.cos(-5 * Math.PI / 180);
|
|
73
83
|
const SIN5 = Math.sin(-5 * Math.PI / 180);
|
|
84
|
+
const px = xg * cosPhi + yg * sinPhi * COS5 + zg * sinPhi * SIN5;
|
|
85
|
+
const py = -xg * sinPhi + yg * cosPhi * COS5 + zg * cosPhi * SIN5;
|
|
86
|
+
const pz = -yg * SIN5 + zg * COS5;
|
|
87
|
+
if (geoVelocityPass) {
|
|
88
|
+
return { prn: eph.prn, x: px, y: py, z: pz, vx: 0, vy: 0, vz: 0 };
|
|
89
|
+
}
|
|
90
|
+
geoVelocityPass = true;
|
|
91
|
+
const h = 0.5;
|
|
92
|
+
const pm = keplerPosition(eph, t - h);
|
|
93
|
+
const pp = keplerPosition(eph, t + h);
|
|
94
|
+
geoVelocityPass = false;
|
|
74
95
|
return {
|
|
75
96
|
prn: eph.prn,
|
|
76
|
-
x:
|
|
77
|
-
y:
|
|
78
|
-
z:
|
|
97
|
+
x: px,
|
|
98
|
+
y: py,
|
|
99
|
+
z: pz,
|
|
100
|
+
vx: (pp.x - pm.x) / (2 * h),
|
|
101
|
+
vy: (pp.y - pm.y) / (2 * h),
|
|
102
|
+
vz: (pp.z - pm.z) / (2 * h)
|
|
79
103
|
};
|
|
80
104
|
}
|
|
81
105
|
const omegak = eph.omega0 + (eph.omegaDot - OMEGA_E) * tk - OMEGA_E * eph.toe;
|
|
106
|
+
const omegakDot = eph.omegaDot - OMEGA_E;
|
|
82
107
|
const cosO = Math.cos(omegak);
|
|
83
108
|
const sinO = Math.sin(omegak);
|
|
84
109
|
const cosI = Math.cos(ik);
|
|
85
110
|
const sinI = Math.sin(ik);
|
|
111
|
+
const x = xp * cosO - yp * cosI * sinO;
|
|
112
|
+
const y = xp * sinO + yp * cosI * cosO;
|
|
86
113
|
return {
|
|
87
114
|
prn: eph.prn,
|
|
88
|
-
x
|
|
89
|
-
y
|
|
90
|
-
z: yp * sinI
|
|
115
|
+
x,
|
|
116
|
+
y,
|
|
117
|
+
z: yp * sinI,
|
|
118
|
+
vx: xpDot * cosO - ypDot * cosI * sinO + yp * sinI * sinO * dikDot - omegakDot * y,
|
|
119
|
+
vy: xpDot * sinO + ypDot * cosI * cosO - yp * sinI * cosO * dikDot + omegakDot * x,
|
|
120
|
+
vz: ypDot * sinI + yp * cosI * dikDot
|
|
91
121
|
};
|
|
92
122
|
}
|
|
93
123
|
function gloDerivatives(state, acc) {
|
|
@@ -141,7 +171,25 @@ function glonassPosition(eph, tUtc) {
|
|
|
141
171
|
if (Math.abs(remainder) > 1e-3) {
|
|
142
172
|
state = rk4Step(state, acc, remainder);
|
|
143
173
|
}
|
|
144
|
-
return {
|
|
174
|
+
return {
|
|
175
|
+
prn: eph.prn,
|
|
176
|
+
x: state[0],
|
|
177
|
+
y: state[1],
|
|
178
|
+
z: state[2],
|
|
179
|
+
vx: state[3],
|
|
180
|
+
vy: state[4],
|
|
181
|
+
vz: state[5]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function rangeRate(sat, rx, rxVel = [0, 0, 0]) {
|
|
185
|
+
const dx = sat.x - rx[0];
|
|
186
|
+
const dy = sat.y - rx[1];
|
|
187
|
+
const dz = sat.z - rx[2];
|
|
188
|
+
const rho = Math.hypot(dx, dy, dz);
|
|
189
|
+
return ((sat.vx - rxVel[0]) * dx + (sat.vy - rxVel[1]) * dy + (sat.vz - rxVel[2]) * dz) / rho;
|
|
190
|
+
}
|
|
191
|
+
function dopplerHz(rangeRateMs, freqHz) {
|
|
192
|
+
return -rangeRateMs * freqHz / C_LIGHT;
|
|
145
193
|
}
|
|
146
194
|
function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
|
|
147
195
|
const [lat, lon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
@@ -546,6 +594,8 @@ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, el
|
|
|
546
594
|
export {
|
|
547
595
|
keplerPosition,
|
|
548
596
|
glonassPosition,
|
|
597
|
+
rangeRate,
|
|
598
|
+
dopplerHz,
|
|
549
599
|
ecefToAzEl,
|
|
550
600
|
computeDop,
|
|
551
601
|
selectEphemeris,
|
|
@@ -695,6 +695,496 @@ function parseIonex(text) {
|
|
|
695
695
|
return { epochs, lats, lons, maps };
|
|
696
696
|
}
|
|
697
697
|
|
|
698
|
+
// src/rinex/sp3.ts
|
|
699
|
+
var BAD_CLOCK = 999999.999999;
|
|
700
|
+
function parseSp3(text) {
|
|
701
|
+
const lines = text.split("\n");
|
|
702
|
+
const epochs = [];
|
|
703
|
+
const satellites = {};
|
|
704
|
+
let version = "";
|
|
705
|
+
let intervalSec = 0;
|
|
706
|
+
let timeSystem = "GPS";
|
|
707
|
+
let timeSystemSet = false;
|
|
708
|
+
let epochIdx = -1;
|
|
709
|
+
for (const line of lines) {
|
|
710
|
+
if (!version && line.startsWith("#") && line.charAt(1) !== "#") {
|
|
711
|
+
version = line.charAt(1);
|
|
712
|
+
}
|
|
713
|
+
if (line.startsWith("##")) {
|
|
714
|
+
const f = line.slice(2).trim().split(/\s+/);
|
|
715
|
+
intervalSec = Number(f[2]) || 0;
|
|
716
|
+
} else if (line.startsWith("%c") && !timeSystemSet) {
|
|
717
|
+
const sys = line.slice(9, 12).trim();
|
|
718
|
+
if (sys && !/^c+$/.test(sys)) {
|
|
719
|
+
timeSystem = sys;
|
|
720
|
+
timeSystemSet = true;
|
|
721
|
+
}
|
|
722
|
+
} else if (line.startsWith("*")) {
|
|
723
|
+
const f = line.slice(1).trim().split(/\s+/).map(Number);
|
|
724
|
+
if (f.length >= 6 && f.every(Number.isFinite)) {
|
|
725
|
+
const sec = f[5];
|
|
726
|
+
epochs.push(
|
|
727
|
+
Date.UTC(
|
|
728
|
+
f[0],
|
|
729
|
+
f[1] - 1,
|
|
730
|
+
f[2],
|
|
731
|
+
f[3],
|
|
732
|
+
f[4],
|
|
733
|
+
Math.floor(sec),
|
|
734
|
+
Math.round(sec % 1 * 1e3)
|
|
735
|
+
)
|
|
736
|
+
);
|
|
737
|
+
epochIdx++;
|
|
738
|
+
}
|
|
739
|
+
} else if (line.startsWith("P") && epochIdx >= 0) {
|
|
740
|
+
const prn = line.slice(1, 4).replace(/\s/g, "0");
|
|
741
|
+
const f = line.slice(4).trim().split(/\s+/).map(Number);
|
|
742
|
+
if (f.length < 3) continue;
|
|
743
|
+
const [xKm, yKm, zKm] = f;
|
|
744
|
+
let arr = satellites[prn];
|
|
745
|
+
if (!arr) satellites[prn] = arr = [];
|
|
746
|
+
while (arr.length < epochIdx) arr.push(null);
|
|
747
|
+
if (xKm === 0 && yKm === 0 && zKm === 0) {
|
|
748
|
+
arr.push(null);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const clkUs = f[3];
|
|
752
|
+
arr.push({
|
|
753
|
+
x: xKm * 1e3,
|
|
754
|
+
y: yKm * 1e3,
|
|
755
|
+
z: zKm * 1e3,
|
|
756
|
+
clk: clkUs !== void 0 && Math.abs(clkUs) < BAD_CLOCK ? clkUs * 1e-6 : null
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
for (const arr of Object.values(satellites)) {
|
|
761
|
+
while (arr.length < epochs.length) arr.push(null);
|
|
762
|
+
}
|
|
763
|
+
return { version, epochs, intervalSec, timeSystem, satellites };
|
|
764
|
+
}
|
|
765
|
+
function sp3Position(sp3, prn, tMs, order = 9) {
|
|
766
|
+
const samples = sp3.satellites[prn];
|
|
767
|
+
const { epochs } = sp3;
|
|
768
|
+
const n = epochs.length;
|
|
769
|
+
if (!samples || n < order) return null;
|
|
770
|
+
if (tMs < epochs[0] || tMs > epochs[n - 1]) return null;
|
|
771
|
+
let lo = 0;
|
|
772
|
+
let hi = n - 1;
|
|
773
|
+
while (lo < hi) {
|
|
774
|
+
const mid = lo + hi + 1 >> 1;
|
|
775
|
+
if (epochs[mid] <= tMs) lo = mid;
|
|
776
|
+
else hi = mid - 1;
|
|
777
|
+
}
|
|
778
|
+
let start = lo - (order >> 1) + (order % 2 === 0 ? 1 : 0);
|
|
779
|
+
start = Math.max(0, Math.min(n - order, start));
|
|
780
|
+
for (let i = start; i < start + order; i++) {
|
|
781
|
+
if (!samples[i]) return null;
|
|
782
|
+
}
|
|
783
|
+
const t0 = epochs[start];
|
|
784
|
+
const t = (tMs - t0) / 1e3;
|
|
785
|
+
let x = 0;
|
|
786
|
+
let y = 0;
|
|
787
|
+
let z = 0;
|
|
788
|
+
for (let i = 0; i < order; i++) {
|
|
789
|
+
const ti = (epochs[start + i] - t0) / 1e3;
|
|
790
|
+
let w = 1;
|
|
791
|
+
for (let j = 0; j < order; j++) {
|
|
792
|
+
if (j === i) continue;
|
|
793
|
+
const tj = (epochs[start + j] - t0) / 1e3;
|
|
794
|
+
w *= (t - tj) / (ti - tj);
|
|
795
|
+
}
|
|
796
|
+
const s = samples[start + i];
|
|
797
|
+
x += w * s.x;
|
|
798
|
+
y += w * s.y;
|
|
799
|
+
z += w * s.z;
|
|
800
|
+
}
|
|
801
|
+
let clk = null;
|
|
802
|
+
const a = samples[lo];
|
|
803
|
+
const b = samples[Math.min(lo + 1, n - 1)];
|
|
804
|
+
if (a?.clk != null && b?.clk != null) {
|
|
805
|
+
const span = epochs[Math.min(lo + 1, n - 1)] - epochs[lo];
|
|
806
|
+
const f = span > 0 ? (tMs - epochs[lo]) / span : 0;
|
|
807
|
+
clk = a.clk * (1 - f) + b.clk * f;
|
|
808
|
+
} else if (a?.clk != null) {
|
|
809
|
+
clk = a.clk;
|
|
810
|
+
}
|
|
811
|
+
return { x, y, z, clk };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/rinex/obs-writer-common.ts
|
|
815
|
+
function createGzipLineSink() {
|
|
816
|
+
const encoder = new TextEncoder();
|
|
817
|
+
const compressor = new CompressionStream("gzip");
|
|
818
|
+
const writer = compressor.writable.getWriter();
|
|
819
|
+
const compressedChunks = [];
|
|
820
|
+
const readerDone = (async () => {
|
|
821
|
+
const reader = compressor.readable.getReader();
|
|
822
|
+
for (; ; ) {
|
|
823
|
+
const { done, value } = await reader.read();
|
|
824
|
+
if (done) break;
|
|
825
|
+
compressedChunks.push(value);
|
|
826
|
+
}
|
|
827
|
+
})();
|
|
828
|
+
let batch = [];
|
|
829
|
+
async function flush() {
|
|
830
|
+
if (batch.length === 0) return;
|
|
831
|
+
await writer.write(encoder.encode(batch.join("\n") + "\n"));
|
|
832
|
+
batch = [];
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
push(line) {
|
|
836
|
+
batch.push(line);
|
|
837
|
+
},
|
|
838
|
+
flush,
|
|
839
|
+
async finish() {
|
|
840
|
+
await flush();
|
|
841
|
+
await writer.close();
|
|
842
|
+
await readerDone;
|
|
843
|
+
return new Blob(compressedChunks, {
|
|
844
|
+
type: "application/gzip"
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function stationHeaderLines(header) {
|
|
850
|
+
const pos = header.approxPosition ?? [0, 0, 0];
|
|
851
|
+
const delta = header.antDelta ?? [0, 0, 0];
|
|
852
|
+
return [
|
|
853
|
+
hdrLine(header.markerName || "UNKNOWN", "MARKER NAME"),
|
|
854
|
+
hdrLine("", "MARKER NUMBER"),
|
|
855
|
+
hdrLine(
|
|
856
|
+
`${padL(header.observer || "", 20)}${padL(header.agency || "", 40)}`,
|
|
857
|
+
"OBSERVER / AGENCY"
|
|
858
|
+
),
|
|
859
|
+
hdrLine(
|
|
860
|
+
`${padL(header.receiverNumber || "", 20)}${padL(header.receiverType || "", 20)}${padL(header.receiverVersion || "", 20)}`,
|
|
861
|
+
"REC # / TYPE / VERS"
|
|
862
|
+
),
|
|
863
|
+
hdrLine(
|
|
864
|
+
`${padL(header.antNumber || "", 20)}${padL(header.antType || "", 20)}`,
|
|
865
|
+
"ANT # / TYPE"
|
|
866
|
+
),
|
|
867
|
+
hdrLine(
|
|
868
|
+
`${fmtF(pos[0], 14, 4)}${fmtF(pos[1], 14, 4)}${fmtF(pos[2], 14, 4)}`,
|
|
869
|
+
"APPROX POSITION XYZ"
|
|
870
|
+
),
|
|
871
|
+
hdrLine(
|
|
872
|
+
`${fmtF(delta[0], 14, 4)}${fmtF(delta[1], 14, 4)}${fmtF(delta[2], 14, 4)}`,
|
|
873
|
+
"ANTENNA: DELTA H/E/N"
|
|
874
|
+
)
|
|
875
|
+
];
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/rinex/obs-writer.ts
|
|
879
|
+
function writeRinexObsBlob(header, epochs, obsTypes) {
|
|
880
|
+
return writeModernObsBlob(header, epochs, obsTypes, {
|
|
881
|
+
version: "3.04",
|
|
882
|
+
comment: "Merged from multiple RINEX files"
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
async function writeModernObsBlob(header, epochs, obsTypes, opts) {
|
|
886
|
+
if (epochs.length === 0) return new Blob([], { type: "application/gzip" });
|
|
887
|
+
const sink = createGzipLineSink();
|
|
888
|
+
const BATCH = 200;
|
|
889
|
+
const systems = [...obsTypes.keys()];
|
|
890
|
+
const sysChar = systems.length === 1 ? systems[0] : "M";
|
|
891
|
+
sink.push(
|
|
892
|
+
hdrLine(
|
|
893
|
+
` ${opts.version} OBSERVATION DATA ${sysChar}`,
|
|
894
|
+
"RINEX VERSION / TYPE"
|
|
895
|
+
)
|
|
896
|
+
);
|
|
897
|
+
const now = /* @__PURE__ */ new Date();
|
|
898
|
+
const dateStr = `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(now.getUTCDate()).padStart(2, "0")} ${String(now.getUTCHours()).padStart(2, "0")}${String(now.getUTCMinutes()).padStart(2, "0")}${String(now.getUTCSeconds()).padStart(2, "0")} UTC`;
|
|
899
|
+
sink.push(
|
|
900
|
+
hdrLine(
|
|
901
|
+
`${padL("GNSSCalc", 20)}${padL("", 20)}${dateStr}`,
|
|
902
|
+
"PGM / RUN BY / DATE"
|
|
903
|
+
)
|
|
904
|
+
);
|
|
905
|
+
sink.push(hdrLine(opts.comment, "COMMENT"));
|
|
906
|
+
for (const line of stationHeaderLines(header)) sink.push(line);
|
|
907
|
+
for (const [sys, types] of obsTypes) {
|
|
908
|
+
for (let i = 0; i < types.length; i += 13) {
|
|
909
|
+
const chunk = types.slice(i, i + 13);
|
|
910
|
+
let content;
|
|
911
|
+
if (i === 0) {
|
|
912
|
+
content = `${sys} ${padR(String(types.length), 3)}`;
|
|
913
|
+
} else {
|
|
914
|
+
content = " ";
|
|
915
|
+
}
|
|
916
|
+
content += chunk.map((t) => ` ${padL(t, 3)}`).join("");
|
|
917
|
+
sink.push(hdrLine(content, "SYS / # / OBS TYPES"));
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const timeSys = systems.includes("R") && systems.length === 1 ? "GLO" : "GPS";
|
|
921
|
+
const obsTimeLine = (d) => ` ${d.getUTCFullYear()} ${String(d.getUTCMonth() + 1).padStart(2)} ${String(d.getUTCDate()).padStart(2)} ${String(d.getUTCHours()).padStart(2)} ${String(d.getUTCMinutes()).padStart(2)} ${(d.getUTCSeconds() + d.getUTCMilliseconds() / 1e3).toFixed(7).padStart(10)} ${timeSys}`;
|
|
922
|
+
sink.push(
|
|
923
|
+
hdrLine(obsTimeLine(new Date(epochs[0].time)), "TIME OF FIRST OBS")
|
|
924
|
+
);
|
|
925
|
+
sink.push(
|
|
926
|
+
hdrLine(
|
|
927
|
+
obsTimeLine(new Date(epochs[epochs.length - 1].time)),
|
|
928
|
+
"TIME OF LAST OBS"
|
|
929
|
+
)
|
|
930
|
+
);
|
|
931
|
+
if (epochs.length >= 2) {
|
|
932
|
+
const interval = (epochs[1].time - epochs[0].time) / 1e3;
|
|
933
|
+
if (interval > 0 && interval < 3600) {
|
|
934
|
+
sink.push(hdrLine(fmtF(interval, 10, 3), "INTERVAL"));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (header.glonassSlots && Object.keys(header.glonassSlots).length > 0) {
|
|
938
|
+
const entries = Object.entries(header.glonassSlots).sort(
|
|
939
|
+
([a], [b]) => Number(a) - Number(b)
|
|
940
|
+
);
|
|
941
|
+
for (let i = 0; i < entries.length; i += 8) {
|
|
942
|
+
const chunk = entries.slice(i, i + 8);
|
|
943
|
+
let content = i === 0 ? padR(String(entries.length), 3) + " " : " ";
|
|
944
|
+
for (const [slot, freq] of chunk) {
|
|
945
|
+
content += `R${padR(slot, 2)} ${padR(String(freq), 2)} `;
|
|
946
|
+
}
|
|
947
|
+
sink.push(hdrLine(content, "GLONASS SLOT / FRQ #"));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
sink.push(hdrLine("", "END OF HEADER"));
|
|
951
|
+
await sink.flush();
|
|
952
|
+
let epochCount = 0;
|
|
953
|
+
for (const epoch of epochs) {
|
|
954
|
+
const t = new Date(epoch.time);
|
|
955
|
+
const sec = t.getUTCSeconds() + t.getUTCMilliseconds() / 1e3;
|
|
956
|
+
const prns = [...epoch.sats.keys()].sort();
|
|
957
|
+
sink.push(
|
|
958
|
+
`> ${t.getUTCFullYear()} ${String(t.getUTCMonth() + 1).padStart(2, "0")} ${String(t.getUTCDate()).padStart(2, "0")} ${String(t.getUTCHours()).padStart(2, "0")} ${String(t.getUTCMinutes()).padStart(2, "0")}${fmtF(sec, 11, 7)} 0${padR(String(prns.length), 3)}`
|
|
959
|
+
);
|
|
960
|
+
for (const prn of prns) {
|
|
961
|
+
const sys = prn[0];
|
|
962
|
+
const sysTypes = obsTypes.get(sys);
|
|
963
|
+
if (!sysTypes) continue;
|
|
964
|
+
const valArr = epoch.sats.get(prn);
|
|
965
|
+
let line = prn;
|
|
966
|
+
for (let i = 0; i < sysTypes.length; i++) {
|
|
967
|
+
const val = i < valArr.length ? valArr[i] : NaN;
|
|
968
|
+
if (!isNaN(val)) {
|
|
969
|
+
line += fmtF(val, 14, 3) + " ";
|
|
970
|
+
} else {
|
|
971
|
+
line += " ".repeat(16);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
sink.push(line);
|
|
975
|
+
}
|
|
976
|
+
epochCount++;
|
|
977
|
+
if (epochCount % BATCH === 0) await sink.flush();
|
|
978
|
+
}
|
|
979
|
+
return sink.finish();
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/rinex/obs-writer-v2.ts
|
|
983
|
+
var V3_TO_V2 = {
|
|
984
|
+
// GPS
|
|
985
|
+
C1C: "C1",
|
|
986
|
+
C1S: "C1",
|
|
987
|
+
C1L: "C1",
|
|
988
|
+
C1X: "C1",
|
|
989
|
+
C1W: "P1",
|
|
990
|
+
C1P: "P1",
|
|
991
|
+
C2C: "C2",
|
|
992
|
+
C2S: "P2",
|
|
993
|
+
C2L: "P2",
|
|
994
|
+
C2X: "P2",
|
|
995
|
+
C2W: "P2",
|
|
996
|
+
C2P: "P2",
|
|
997
|
+
C5I: "C5",
|
|
998
|
+
C5Q: "C5",
|
|
999
|
+
C5X: "C5",
|
|
1000
|
+
L1C: "L1",
|
|
1001
|
+
L1S: "L1",
|
|
1002
|
+
L1L: "L1",
|
|
1003
|
+
L1X: "L1",
|
|
1004
|
+
L1W: "L1",
|
|
1005
|
+
L1P: "L1",
|
|
1006
|
+
L2C: "L2",
|
|
1007
|
+
L2S: "L2",
|
|
1008
|
+
L2L: "L2",
|
|
1009
|
+
L2X: "L2",
|
|
1010
|
+
L2W: "L2",
|
|
1011
|
+
L2P: "L2",
|
|
1012
|
+
L5I: "L5",
|
|
1013
|
+
L5Q: "L5",
|
|
1014
|
+
L5X: "L5",
|
|
1015
|
+
D1C: "D1",
|
|
1016
|
+
D1S: "D1",
|
|
1017
|
+
D1L: "D1",
|
|
1018
|
+
D1X: "D1",
|
|
1019
|
+
D1W: "D1",
|
|
1020
|
+
D1P: "D1",
|
|
1021
|
+
D2C: "D2",
|
|
1022
|
+
D2S: "D2",
|
|
1023
|
+
D2L: "D2",
|
|
1024
|
+
D2X: "D2",
|
|
1025
|
+
D2W: "D2",
|
|
1026
|
+
D2P: "D2",
|
|
1027
|
+
D5I: "D5",
|
|
1028
|
+
D5Q: "D5",
|
|
1029
|
+
D5X: "D5",
|
|
1030
|
+
S1C: "S1",
|
|
1031
|
+
S1S: "S1",
|
|
1032
|
+
S1L: "S1",
|
|
1033
|
+
S1X: "S1",
|
|
1034
|
+
S1W: "S1",
|
|
1035
|
+
S1P: "S1",
|
|
1036
|
+
S2C: "S2",
|
|
1037
|
+
S2S: "S2",
|
|
1038
|
+
S2L: "S2",
|
|
1039
|
+
S2X: "S2",
|
|
1040
|
+
S2W: "S2",
|
|
1041
|
+
S2P: "S2",
|
|
1042
|
+
S5I: "S5",
|
|
1043
|
+
S5Q: "S5",
|
|
1044
|
+
S5X: "S5"
|
|
1045
|
+
};
|
|
1046
|
+
function v3ToV2Code(code3) {
|
|
1047
|
+
return V3_TO_V2[code3] ?? null;
|
|
1048
|
+
}
|
|
1049
|
+
async function writeRinex2ObsBlob(header, epochs, obsTypes) {
|
|
1050
|
+
if (epochs.length === 0) return new Blob([], { type: "application/gzip" });
|
|
1051
|
+
const gpsCodes = obsTypes.get("G") ?? obsTypes.get("R") ?? [];
|
|
1052
|
+
const v2Codes = [];
|
|
1053
|
+
const v2CodeSet = /* @__PURE__ */ new Set();
|
|
1054
|
+
const v3ToV2Map = /* @__PURE__ */ new Map();
|
|
1055
|
+
for (const code3 of gpsCodes) {
|
|
1056
|
+
const code2 = v3ToV2Code(code3);
|
|
1057
|
+
if (!code2) continue;
|
|
1058
|
+
if (!v2CodeSet.has(code2)) {
|
|
1059
|
+
v2CodeSet.add(code2);
|
|
1060
|
+
v3ToV2Map.set(code3, v2Codes.length);
|
|
1061
|
+
v2Codes.push(code2);
|
|
1062
|
+
} else {
|
|
1063
|
+
v3ToV2Map.set(code3, v2Codes.indexOf(code2));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
const gloCodes = obsTypes.get("R") ?? [];
|
|
1067
|
+
for (const code3 of gloCodes) {
|
|
1068
|
+
const code2 = v3ToV2Code(code3);
|
|
1069
|
+
if (!code2) continue;
|
|
1070
|
+
if (!v2CodeSet.has(code2)) {
|
|
1071
|
+
v2CodeSet.add(code2);
|
|
1072
|
+
v3ToV2Map.set(code3, v2Codes.length);
|
|
1073
|
+
v2Codes.push(code2);
|
|
1074
|
+
} else if (!v3ToV2Map.has(code3)) {
|
|
1075
|
+
v3ToV2Map.set(code3, v2Codes.indexOf(code2));
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const sink = createGzipLineSink();
|
|
1079
|
+
const BATCH = 200;
|
|
1080
|
+
const hasSystems = /* @__PURE__ */ new Set();
|
|
1081
|
+
for (const epoch of epochs) {
|
|
1082
|
+
for (const prn of epoch.sats.keys()) {
|
|
1083
|
+
const sys = prn[0];
|
|
1084
|
+
if (sys === "G" || sys === "R") hasSystems.add(sys);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const sysChar = hasSystems.has("G") && hasSystems.has("R") ? "M" : hasSystems.has("R") ? "R" : "G";
|
|
1088
|
+
sink.push(
|
|
1089
|
+
hdrLine(
|
|
1090
|
+
` 2.11 OBSERVATION DATA ${sysChar}`,
|
|
1091
|
+
"RINEX VERSION / TYPE"
|
|
1092
|
+
)
|
|
1093
|
+
);
|
|
1094
|
+
const now = /* @__PURE__ */ new Date();
|
|
1095
|
+
const dateStr = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")} ${String(now.getUTCHours()).padStart(2, "0")}:${String(now.getUTCMinutes()).padStart(2, "0")}:${String(now.getUTCSeconds()).padStart(2, "0")}`;
|
|
1096
|
+
sink.push(
|
|
1097
|
+
hdrLine(
|
|
1098
|
+
`${padL("GNSSCalc", 20)}${padL("", 20)}${dateStr}`,
|
|
1099
|
+
"PGM / RUN BY / DATE"
|
|
1100
|
+
)
|
|
1101
|
+
);
|
|
1102
|
+
sink.push(hdrLine("Converted from RINEX 3 by GNSSCalc", "COMMENT"));
|
|
1103
|
+
for (const line of stationHeaderLines(header)) sink.push(line);
|
|
1104
|
+
sink.push(hdrLine(" 1 1", "WAVELENGTH FACT L1/2"));
|
|
1105
|
+
for (let i = 0; i < v2Codes.length; i += 9) {
|
|
1106
|
+
const chunk = v2Codes.slice(i, i + 9);
|
|
1107
|
+
let content;
|
|
1108
|
+
if (i === 0) {
|
|
1109
|
+
content = padR(String(v2Codes.length), 6);
|
|
1110
|
+
} else {
|
|
1111
|
+
content = " ";
|
|
1112
|
+
}
|
|
1113
|
+
content += chunk.map((c) => padR(c, 6)).join("");
|
|
1114
|
+
sink.push(hdrLine(content, "# / TYPES OF OBSERV"));
|
|
1115
|
+
}
|
|
1116
|
+
const first = new Date(epochs[0].time);
|
|
1117
|
+
const timeSys = sysChar === "R" ? "GLO" : "GPS";
|
|
1118
|
+
sink.push(
|
|
1119
|
+
hdrLine(
|
|
1120
|
+
` ${first.getUTCFullYear()} ${String(first.getUTCMonth() + 1).padStart(2)} ${String(first.getUTCDate()).padStart(2)} ${String(first.getUTCHours()).padStart(2)} ${String(first.getUTCMinutes()).padStart(2)} ${(first.getUTCSeconds() + first.getUTCMilliseconds() / 1e3).toFixed(7).padStart(10)} ${timeSys}`,
|
|
1121
|
+
"TIME OF FIRST OBS"
|
|
1122
|
+
)
|
|
1123
|
+
);
|
|
1124
|
+
sink.push(hdrLine("", "END OF HEADER"));
|
|
1125
|
+
await sink.flush();
|
|
1126
|
+
let epochCount = 0;
|
|
1127
|
+
for (const epoch of epochs) {
|
|
1128
|
+
const t = new Date(epoch.time);
|
|
1129
|
+
const sec = t.getUTCSeconds() + t.getUTCMilliseconds() / 1e3;
|
|
1130
|
+
const prns = [...epoch.sats.keys()].filter((p) => p[0] === "G" || p[0] === "R").sort();
|
|
1131
|
+
if (prns.length === 0) continue;
|
|
1132
|
+
const yy = t.getUTCFullYear() % 100;
|
|
1133
|
+
const yyS = padR(String(yy), 2);
|
|
1134
|
+
const moS = padR(String(t.getUTCMonth() + 1), 2);
|
|
1135
|
+
const ddS = padR(String(t.getUTCDate()), 2);
|
|
1136
|
+
const hhS = padR(String(t.getUTCHours()), 2);
|
|
1137
|
+
const mmS = padR(String(t.getUTCMinutes()), 2);
|
|
1138
|
+
const secS = fmtF(sec, 11, 7);
|
|
1139
|
+
const nSats = padR(String(prns.length), 3);
|
|
1140
|
+
let epochLine = ` ${yyS} ${moS} ${ddS} ${hhS} ${mmS}${secS} 0${nSats}`;
|
|
1141
|
+
for (let i = 0; i < prns.length; i++) {
|
|
1142
|
+
if (i > 0 && i % 12 === 0) {
|
|
1143
|
+
sink.push(epochLine);
|
|
1144
|
+
epochLine = " ".repeat(32);
|
|
1145
|
+
}
|
|
1146
|
+
epochLine += prns[i];
|
|
1147
|
+
}
|
|
1148
|
+
sink.push(epochLine);
|
|
1149
|
+
for (const prn of prns) {
|
|
1150
|
+
const sys = prn[0];
|
|
1151
|
+
const sysCodes3 = obsTypes.get(sys) ?? [];
|
|
1152
|
+
const valArr = epoch.sats.get(prn);
|
|
1153
|
+
const v2Vals = new Array(v2Codes.length).fill(NaN);
|
|
1154
|
+
for (let j = 0; j < sysCodes3.length; j++) {
|
|
1155
|
+
const v2Idx = v3ToV2Map.get(sysCodes3[j]);
|
|
1156
|
+
if (v2Idx != null && j < valArr.length && !isNaN(valArr[j])) {
|
|
1157
|
+
v2Vals[v2Idx] = valArr[j];
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
let line = "";
|
|
1161
|
+
for (let j = 0; j < v2Codes.length; j++) {
|
|
1162
|
+
const val = v2Vals[j];
|
|
1163
|
+
if (!isNaN(val)) {
|
|
1164
|
+
line += fmtF(val, 14, 3) + " ";
|
|
1165
|
+
} else {
|
|
1166
|
+
line += " ".repeat(16);
|
|
1167
|
+
}
|
|
1168
|
+
if ((j + 1) % 5 === 0 || j === v2Codes.length - 1) {
|
|
1169
|
+
sink.push(line);
|
|
1170
|
+
line = "";
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
epochCount++;
|
|
1175
|
+
if (epochCount % BATCH === 0) await sink.flush();
|
|
1176
|
+
}
|
|
1177
|
+
return sink.finish();
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/rinex/obs-writer-v4.ts
|
|
1181
|
+
function writeRinex4ObsBlob(header, epochs, obsTypes) {
|
|
1182
|
+
return writeModernObsBlob(header, epochs, obsTypes, {
|
|
1183
|
+
version: "4.01",
|
|
1184
|
+
comment: "Converted to RINEX 4 by GNSSCalc"
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
698
1188
|
export {
|
|
699
1189
|
padL,
|
|
700
1190
|
padR,
|
|
@@ -704,5 +1194,13 @@ export {
|
|
|
704
1194
|
WarningAccumulator,
|
|
705
1195
|
parseNavFile,
|
|
706
1196
|
writeRinexNav,
|
|
707
|
-
parseIonex
|
|
1197
|
+
parseIonex,
|
|
1198
|
+
parseSp3,
|
|
1199
|
+
sp3Position,
|
|
1200
|
+
createGzipLineSink,
|
|
1201
|
+
stationHeaderLines,
|
|
1202
|
+
writeRinexObsBlob,
|
|
1203
|
+
writeModernObsBlob,
|
|
1204
|
+
writeRinex2ObsBlob,
|
|
1205
|
+
writeRinex4ObsBlob
|
|
708
1206
|
};
|