gnss-js 1.11.0 → 1.13.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-LKFSXVIZ.js → chunk-E5CP37P2.js} +1 -1
- package/dist/{chunk-77VUSDNI.js → chunk-HXF7XVW7.js} +499 -1
- package/dist/{chunk-DMNFB7Q6.js → chunk-VAHMHYID.js} +9 -2
- package/dist/index.cjs +517 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +23 -5
- package/dist/orbit.cjs +9 -1
- package/dist/orbit.d.cts +8 -1
- package/dist/orbit.d.ts +8 -1
- package/dist/orbit.js +3 -1
- package/dist/positioning.js +2 -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
|
@@ -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
|
};
|
|
@@ -506,8 +506,14 @@ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, el
|
|
|
506
506
|
const stepMs = stepSec * 1e3;
|
|
507
507
|
const times = [];
|
|
508
508
|
for (let t = startMs; t <= endMs; t += stepMs) times.push(t);
|
|
509
|
+
return computeVisibilityFromPositions(
|
|
510
|
+
computeAllPositions(ephemerides, times, rxPos),
|
|
511
|
+
elevationMaskDeg
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
function computeVisibilityFromPositions(all, elevationMaskDeg = 10) {
|
|
515
|
+
const times = all.times;
|
|
509
516
|
const maskRadFor = maskRadForAzimuth(elevationMaskDeg);
|
|
510
|
-
const all = computeAllPositions(ephemerides, times, rxPos);
|
|
511
517
|
const elevation = {};
|
|
512
518
|
const azimuth = {};
|
|
513
519
|
const subLat = {};
|
|
@@ -605,5 +611,6 @@ export {
|
|
|
605
611
|
ephInfoToEphemeris,
|
|
606
612
|
computeLiveSkyPositions,
|
|
607
613
|
maskRadForAzimuth,
|
|
608
|
-
computeVisibility
|
|
614
|
+
computeVisibility,
|
|
615
|
+
computeVisibilityFromPositions
|
|
609
616
|
};
|