gnss-js 0.1.2 → 1.0.1
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/LICENSE +23 -0
- package/README.md +171 -5
- package/dist/analysis.cjs +1488 -0
- package/dist/analysis.d.cts +211 -0
- package/dist/analysis.d.ts +211 -0
- package/dist/analysis.js +14 -0
- package/dist/antex.cjs +239 -0
- package/dist/antex.d.cts +77 -0
- package/dist/antex.d.ts +77 -0
- package/dist/antex.js +8 -0
- package/dist/chunk-37QNKGTC.js +65 -0
- package/dist/chunk-4YN353Q7.js +440 -0
- package/dist/chunk-5CMSYIYM.js +214 -0
- package/dist/chunk-5S5MONFK.js +0 -0
- package/dist/chunk-6FAL6P4G.js +12 -0
- package/dist/chunk-7NOFXKET.js +433 -0
- package/dist/chunk-BJHTBYNG.js +154 -0
- package/dist/chunk-HBLU2EJ4.js +802 -0
- package/dist/chunk-HKN3PUGN.js +800 -0
- package/dist/chunk-LEEU5OIO.js +75 -0
- package/dist/chunk-LWNTWBHB.js +268 -0
- package/dist/chunk-MIIM4LDY.js +239 -0
- package/dist/chunk-SO3POWWR.js +502 -0
- package/dist/chunk-W4YMQKWH.js +622 -0
- package/dist/chunk-WP2JFDLA.js +1475 -0
- package/dist/chunk-YDMYO3YK.js +220 -0
- package/dist/constants.cjs +423 -0
- package/dist/constants.d.cts +14 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.js +103 -0
- package/dist/coordinates.cjs +337 -0
- package/dist/coordinates.d.cts +81 -0
- package/dist/coordinates.d.ts +81 -0
- package/dist/coordinates.js +39 -0
- package/dist/ecef-CF0uAysr.d.cts +40 -0
- package/dist/ecef-CF0uAysr.d.ts +40 -0
- package/dist/ephemeris-C10stHhM.d.cts +114 -0
- package/dist/ephemeris-C10stHhM.d.ts +114 -0
- package/dist/gnss-BT6ulR17.d.cts +56 -0
- package/dist/gnss-C-tgoYNa.d.ts +56 -0
- package/dist/index.cjs +5940 -78
- package/dist/index.d.cts +17 -96
- package/dist/index.d.ts +17 -96
- package/dist/index.js +352 -415
- package/dist/nav-BAI1a9vK.d.cts +108 -0
- package/dist/nav-BAI1a9vK.d.ts +108 -0
- package/dist/nmea.cjs +247 -0
- package/dist/nmea.d.cts +37 -0
- package/dist/nmea.d.ts +37 -0
- package/dist/nmea.js +12 -0
- package/dist/ntrip.cjs +180 -0
- package/dist/ntrip.d.cts +98 -0
- package/dist/ntrip.d.ts +98 -0
- package/dist/ntrip.js +10 -0
- package/dist/orbit.cjs +511 -0
- package/dist/orbit.d.cts +117 -0
- package/dist/orbit.d.ts +117 -0
- package/dist/orbit.js +29 -0
- package/dist/parser-JPjjFgeP.d.cts +73 -0
- package/dist/parser-JPjjFgeP.d.ts +73 -0
- package/dist/rinex.cjs +1469 -0
- package/dist/rinex.d.cts +116 -0
- package/dist/rinex.d.ts +116 -0
- package/dist/rinex.js +37 -0
- package/dist/rtcm3.cjs +1512 -0
- package/dist/rtcm3.d.cts +143 -0
- package/dist/rtcm3.d.ts +143 -0
- package/dist/rtcm3.js +31 -0
- package/dist/signals.cjs +858 -0
- package/dist/signals.d.cts +93 -0
- package/dist/signals.d.ts +93 -0
- package/dist/signals.js +70 -0
- package/dist/time-DnI1VpE8.d.cts +33 -0
- package/dist/time-DnI1VpE8.d.ts +33 -0
- package/dist/time.cjs +585 -0
- package/dist/time.d.cts +307 -0
- package/dist/time.d.ts +307 -0
- package/dist/time.js +154 -0
- package/package.json +112 -14
- package/.prettierrc +0 -5
- package/jest.config.ts +0 -9
- package/test/functions.test.ts +0 -63
- package/test/gnss-scales.test.ts +0 -95
- package/test/julian.test.ts +0 -145
- package/test/rinex.test.ts +0 -11
- package/test/test.ts +0 -10
- package/test/utc.test.ts +0 -25
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RINEX 3.x/4.x navigation file parser.
|
|
3
|
+
* Parses GPS, Galileo, BeiDou (Keplerian), and GLONASS (state vector) ephemerides.
|
|
4
|
+
*/
|
|
5
|
+
interface NavHeader {
|
|
6
|
+
version: number;
|
|
7
|
+
type: string;
|
|
8
|
+
leapSeconds: number | null;
|
|
9
|
+
ionoCorrections: Record<string, number[]>;
|
|
10
|
+
}
|
|
11
|
+
/** GPS / Galileo / BeiDou / QZSS / NavIC Keplerian ephemeris. */
|
|
12
|
+
interface KeplerEphemeris {
|
|
13
|
+
system: 'G' | 'E' | 'C' | 'J' | 'I';
|
|
14
|
+
/** Satellite PRN, e.g. "G14". */
|
|
15
|
+
prn: string;
|
|
16
|
+
/** Epoch of clock in seconds of GPS week. */
|
|
17
|
+
toc: number;
|
|
18
|
+
/** Epoch of clock as a Date. */
|
|
19
|
+
tocDate: Date;
|
|
20
|
+
/** SV clock bias in seconds. */
|
|
21
|
+
af0: number;
|
|
22
|
+
/** SV clock drift in s/s. */
|
|
23
|
+
af1: number;
|
|
24
|
+
/** SV clock drift rate in s/s². */
|
|
25
|
+
af2: number;
|
|
26
|
+
/** Issue of data (ephemeris). */
|
|
27
|
+
iode: number;
|
|
28
|
+
/** Amplitude of sine correction to orbit radius in meters. */
|
|
29
|
+
crs: number;
|
|
30
|
+
/** Mean motion difference in rad/s. */
|
|
31
|
+
deltaN: number;
|
|
32
|
+
/** Mean anomaly at reference time in radians. */
|
|
33
|
+
m0: number;
|
|
34
|
+
/** Amplitude of cosine correction to argument of latitude in radians. */
|
|
35
|
+
cuc: number;
|
|
36
|
+
/** Eccentricity (dimensionless). */
|
|
37
|
+
e: number;
|
|
38
|
+
/** Amplitude of sine correction to argument of latitude in radians. */
|
|
39
|
+
cus: number;
|
|
40
|
+
/** Square root of semi-major axis in m^(1/2). */
|
|
41
|
+
sqrtA: number;
|
|
42
|
+
/** Time of ephemeris in seconds of GPS week. */
|
|
43
|
+
toe: number;
|
|
44
|
+
/** Amplitude of cosine correction to inclination in radians. */
|
|
45
|
+
cic: number;
|
|
46
|
+
/** Right ascension of ascending node at reference time in radians. */
|
|
47
|
+
omega0: number;
|
|
48
|
+
/** Amplitude of sine correction to inclination in radians. */
|
|
49
|
+
cis: number;
|
|
50
|
+
/** Inclination at reference time in radians. */
|
|
51
|
+
i0: number;
|
|
52
|
+
/** Amplitude of cosine correction to orbit radius in meters. */
|
|
53
|
+
crc: number;
|
|
54
|
+
/** Argument of perigee in radians. */
|
|
55
|
+
omega: number;
|
|
56
|
+
/** Rate of right ascension in rad/s. */
|
|
57
|
+
omegaDot: number;
|
|
58
|
+
/** Rate of inclination in rad/s. */
|
|
59
|
+
idot: number;
|
|
60
|
+
/** GPS week number. */
|
|
61
|
+
week: number;
|
|
62
|
+
/** SV health flag. */
|
|
63
|
+
svHealth: number;
|
|
64
|
+
/** Total group delay in seconds. */
|
|
65
|
+
tgd: number;
|
|
66
|
+
}
|
|
67
|
+
/** GLONASS / SBAS state-vector ephemeris. Positions in km, velocities in km/s, accelerations in km/s². */
|
|
68
|
+
interface GlonassEphemeris {
|
|
69
|
+
system: 'R' | 'S';
|
|
70
|
+
prn: string;
|
|
71
|
+
tocDate: Date;
|
|
72
|
+
/** Clock bias (seconds). */
|
|
73
|
+
tauN: number;
|
|
74
|
+
/** Relative frequency bias (dimensionless). */
|
|
75
|
+
gammaN: number;
|
|
76
|
+
/** Message frame time (seconds of UTC day). */
|
|
77
|
+
messageFrameTime: number;
|
|
78
|
+
/** X position in km (PZ-90). */
|
|
79
|
+
x: number;
|
|
80
|
+
/** X velocity in km/s. */
|
|
81
|
+
xDot: number;
|
|
82
|
+
/** X acceleration in km/s² (luni-solar). */
|
|
83
|
+
xAcc: number;
|
|
84
|
+
/** Y position in km (PZ-90). */
|
|
85
|
+
y: number;
|
|
86
|
+
/** Y velocity in km/s. */
|
|
87
|
+
yDot: number;
|
|
88
|
+
/** Y acceleration in km/s² (luni-solar). */
|
|
89
|
+
yAcc: number;
|
|
90
|
+
/** Z position in km (PZ-90). */
|
|
91
|
+
z: number;
|
|
92
|
+
/** Z velocity in km/s. */
|
|
93
|
+
zDot: number;
|
|
94
|
+
/** Z acceleration in km/s² (luni-solar). */
|
|
95
|
+
zAcc: number;
|
|
96
|
+
/** Health flag (0 = healthy). */
|
|
97
|
+
health: number;
|
|
98
|
+
/** GLONASS frequency channel number (-7 to +6). */
|
|
99
|
+
freqNum: number;
|
|
100
|
+
}
|
|
101
|
+
type Ephemeris = KeplerEphemeris | GlonassEphemeris;
|
|
102
|
+
interface NavResult {
|
|
103
|
+
header: NavHeader;
|
|
104
|
+
ephemerides: Ephemeris[];
|
|
105
|
+
}
|
|
106
|
+
declare function parseNavFile(text: string): NavResult;
|
|
107
|
+
|
|
108
|
+
export { type Ephemeris as E, type GlonassEphemeris as G, type KeplerEphemeris as K, type NavHeader as N, type NavResult as a, parseNavFile as p };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RINEX 3.x/4.x navigation file parser.
|
|
3
|
+
* Parses GPS, Galileo, BeiDou (Keplerian), and GLONASS (state vector) ephemerides.
|
|
4
|
+
*/
|
|
5
|
+
interface NavHeader {
|
|
6
|
+
version: number;
|
|
7
|
+
type: string;
|
|
8
|
+
leapSeconds: number | null;
|
|
9
|
+
ionoCorrections: Record<string, number[]>;
|
|
10
|
+
}
|
|
11
|
+
/** GPS / Galileo / BeiDou / QZSS / NavIC Keplerian ephemeris. */
|
|
12
|
+
interface KeplerEphemeris {
|
|
13
|
+
system: 'G' | 'E' | 'C' | 'J' | 'I';
|
|
14
|
+
/** Satellite PRN, e.g. "G14". */
|
|
15
|
+
prn: string;
|
|
16
|
+
/** Epoch of clock in seconds of GPS week. */
|
|
17
|
+
toc: number;
|
|
18
|
+
/** Epoch of clock as a Date. */
|
|
19
|
+
tocDate: Date;
|
|
20
|
+
/** SV clock bias in seconds. */
|
|
21
|
+
af0: number;
|
|
22
|
+
/** SV clock drift in s/s. */
|
|
23
|
+
af1: number;
|
|
24
|
+
/** SV clock drift rate in s/s². */
|
|
25
|
+
af2: number;
|
|
26
|
+
/** Issue of data (ephemeris). */
|
|
27
|
+
iode: number;
|
|
28
|
+
/** Amplitude of sine correction to orbit radius in meters. */
|
|
29
|
+
crs: number;
|
|
30
|
+
/** Mean motion difference in rad/s. */
|
|
31
|
+
deltaN: number;
|
|
32
|
+
/** Mean anomaly at reference time in radians. */
|
|
33
|
+
m0: number;
|
|
34
|
+
/** Amplitude of cosine correction to argument of latitude in radians. */
|
|
35
|
+
cuc: number;
|
|
36
|
+
/** Eccentricity (dimensionless). */
|
|
37
|
+
e: number;
|
|
38
|
+
/** Amplitude of sine correction to argument of latitude in radians. */
|
|
39
|
+
cus: number;
|
|
40
|
+
/** Square root of semi-major axis in m^(1/2). */
|
|
41
|
+
sqrtA: number;
|
|
42
|
+
/** Time of ephemeris in seconds of GPS week. */
|
|
43
|
+
toe: number;
|
|
44
|
+
/** Amplitude of cosine correction to inclination in radians. */
|
|
45
|
+
cic: number;
|
|
46
|
+
/** Right ascension of ascending node at reference time in radians. */
|
|
47
|
+
omega0: number;
|
|
48
|
+
/** Amplitude of sine correction to inclination in radians. */
|
|
49
|
+
cis: number;
|
|
50
|
+
/** Inclination at reference time in radians. */
|
|
51
|
+
i0: number;
|
|
52
|
+
/** Amplitude of cosine correction to orbit radius in meters. */
|
|
53
|
+
crc: number;
|
|
54
|
+
/** Argument of perigee in radians. */
|
|
55
|
+
omega: number;
|
|
56
|
+
/** Rate of right ascension in rad/s. */
|
|
57
|
+
omegaDot: number;
|
|
58
|
+
/** Rate of inclination in rad/s. */
|
|
59
|
+
idot: number;
|
|
60
|
+
/** GPS week number. */
|
|
61
|
+
week: number;
|
|
62
|
+
/** SV health flag. */
|
|
63
|
+
svHealth: number;
|
|
64
|
+
/** Total group delay in seconds. */
|
|
65
|
+
tgd: number;
|
|
66
|
+
}
|
|
67
|
+
/** GLONASS / SBAS state-vector ephemeris. Positions in km, velocities in km/s, accelerations in km/s². */
|
|
68
|
+
interface GlonassEphemeris {
|
|
69
|
+
system: 'R' | 'S';
|
|
70
|
+
prn: string;
|
|
71
|
+
tocDate: Date;
|
|
72
|
+
/** Clock bias (seconds). */
|
|
73
|
+
tauN: number;
|
|
74
|
+
/** Relative frequency bias (dimensionless). */
|
|
75
|
+
gammaN: number;
|
|
76
|
+
/** Message frame time (seconds of UTC day). */
|
|
77
|
+
messageFrameTime: number;
|
|
78
|
+
/** X position in km (PZ-90). */
|
|
79
|
+
x: number;
|
|
80
|
+
/** X velocity in km/s. */
|
|
81
|
+
xDot: number;
|
|
82
|
+
/** X acceleration in km/s² (luni-solar). */
|
|
83
|
+
xAcc: number;
|
|
84
|
+
/** Y position in km (PZ-90). */
|
|
85
|
+
y: number;
|
|
86
|
+
/** Y velocity in km/s. */
|
|
87
|
+
yDot: number;
|
|
88
|
+
/** Y acceleration in km/s² (luni-solar). */
|
|
89
|
+
yAcc: number;
|
|
90
|
+
/** Z position in km (PZ-90). */
|
|
91
|
+
z: number;
|
|
92
|
+
/** Z velocity in km/s. */
|
|
93
|
+
zDot: number;
|
|
94
|
+
/** Z acceleration in km/s² (luni-solar). */
|
|
95
|
+
zAcc: number;
|
|
96
|
+
/** Health flag (0 = healthy). */
|
|
97
|
+
health: number;
|
|
98
|
+
/** GLONASS frequency channel number (-7 to +6). */
|
|
99
|
+
freqNum: number;
|
|
100
|
+
}
|
|
101
|
+
type Ephemeris = KeplerEphemeris | GlonassEphemeris;
|
|
102
|
+
interface NavResult {
|
|
103
|
+
header: NavHeader;
|
|
104
|
+
ephemerides: Ephemeris[];
|
|
105
|
+
}
|
|
106
|
+
declare function parseNavFile(text: string): NavResult;
|
|
107
|
+
|
|
108
|
+
export { type Ephemeris as E, type GlonassEphemeris as G, type KeplerEphemeris as K, type NavHeader as N, type NavResult as a, parseNavFile as p };
|
package/dist/nmea.cjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/nmea/index.ts
|
|
21
|
+
var nmea_exports = {};
|
|
22
|
+
__export(nmea_exports, {
|
|
23
|
+
computeStats: () => computeStats,
|
|
24
|
+
nmeaCoordToDecimal: () => nmeaCoordToDecimal,
|
|
25
|
+
parseNmeaFile: () => parseNmeaFile,
|
|
26
|
+
verifyChecksum: () => verifyChecksum
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(nmea_exports);
|
|
29
|
+
function nmeaCoordToDecimal(raw, direction) {
|
|
30
|
+
if (!raw || !direction) return NaN;
|
|
31
|
+
const isLon = direction === "E" || direction === "W";
|
|
32
|
+
const degLen = isLon ? 3 : 2;
|
|
33
|
+
const degrees = parseInt(raw.substring(0, degLen), 10);
|
|
34
|
+
const minutes = parseFloat(raw.substring(degLen));
|
|
35
|
+
const decimal = degrees + minutes / 60;
|
|
36
|
+
return direction === "S" || direction === "W" ? -decimal : decimal;
|
|
37
|
+
}
|
|
38
|
+
function verifyChecksum(sentence) {
|
|
39
|
+
const starIdx = sentence.indexOf("*");
|
|
40
|
+
if (starIdx === -1) return false;
|
|
41
|
+
const body = sentence.substring(sentence.indexOf("$") + 1, starIdx);
|
|
42
|
+
let xor = 0;
|
|
43
|
+
for (let i = 0; i < body.length; i++) xor ^= body.charCodeAt(i);
|
|
44
|
+
const expected = parseInt(sentence.substring(starIdx + 1, starIdx + 3), 16);
|
|
45
|
+
return xor === expected;
|
|
46
|
+
}
|
|
47
|
+
function buildDate(timeStr, dateStr) {
|
|
48
|
+
if (!timeStr || timeStr.length < 6) return null;
|
|
49
|
+
const h = parseInt(timeStr.substring(0, 2), 10);
|
|
50
|
+
const m = parseInt(timeStr.substring(2, 4), 10);
|
|
51
|
+
const s = parseFloat(timeStr.substring(4));
|
|
52
|
+
if (isNaN(h) || isNaN(m) || isNaN(s)) return null;
|
|
53
|
+
let year = 2e3, month = 0, day = 1;
|
|
54
|
+
if (dateStr && dateStr.length >= 6) {
|
|
55
|
+
day = parseInt(dateStr.substring(0, 2), 10);
|
|
56
|
+
month = parseInt(dateStr.substring(2, 4), 10) - 1;
|
|
57
|
+
const yy = parseInt(dateStr.substring(4, 6), 10);
|
|
58
|
+
year = yy < 80 ? 2e3 + yy : 1900 + yy;
|
|
59
|
+
}
|
|
60
|
+
const ms = Math.round(s % 1 * 1e3);
|
|
61
|
+
return new Date(Date.UTC(year, month, day, h, m, Math.floor(s), ms));
|
|
62
|
+
}
|
|
63
|
+
function parseGGA(fields) {
|
|
64
|
+
if (fields.length < 10) return null;
|
|
65
|
+
const quality = parseInt(fields[6], 10);
|
|
66
|
+
if (isNaN(quality) || quality === 0) return null;
|
|
67
|
+
const lat = nmeaCoordToDecimal(fields[2], fields[3]);
|
|
68
|
+
const lon = nmeaCoordToDecimal(fields[4], fields[5]);
|
|
69
|
+
if (isNaN(lat) || isNaN(lon)) return null;
|
|
70
|
+
return {
|
|
71
|
+
time: buildDate(fields[1]),
|
|
72
|
+
timeKey: fields[1],
|
|
73
|
+
lat,
|
|
74
|
+
lon,
|
|
75
|
+
alt: fields[9] ? parseFloat(fields[9]) : null,
|
|
76
|
+
satellites: fields[7] ? parseInt(fields[7], 10) : null,
|
|
77
|
+
fixQuality: quality
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function parseRMC(fields) {
|
|
81
|
+
if (fields.length < 10) return null;
|
|
82
|
+
if (fields[2] !== "A") return null;
|
|
83
|
+
const lat = nmeaCoordToDecimal(fields[3], fields[4]);
|
|
84
|
+
const lon = nmeaCoordToDecimal(fields[5], fields[6]);
|
|
85
|
+
if (isNaN(lat) || isNaN(lon)) return null;
|
|
86
|
+
return {
|
|
87
|
+
time: buildDate(fields[1], fields[9]),
|
|
88
|
+
timeKey: fields[1],
|
|
89
|
+
lat,
|
|
90
|
+
lon,
|
|
91
|
+
speed: fields[7] ? parseFloat(fields[7]) : null,
|
|
92
|
+
course: fields[8] ? parseFloat(fields[8]) : null,
|
|
93
|
+
hasDate: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function computeStats(fixes) {
|
|
97
|
+
const valid = fixes.filter((f) => f.fixQuality > 0);
|
|
98
|
+
const withTime = valid.filter((f) => f.time !== null);
|
|
99
|
+
const withSat = valid.filter((f) => f.satellites !== null).map((f) => f.satellites);
|
|
100
|
+
let startTime = null;
|
|
101
|
+
let endTime = null;
|
|
102
|
+
let duration = null;
|
|
103
|
+
if (withTime.length >= 2) {
|
|
104
|
+
startTime = withTime[0].time;
|
|
105
|
+
endTime = withTime[withTime.length - 1].time;
|
|
106
|
+
duration = (endTime.getTime() - startTime.getTime()) / 1e3;
|
|
107
|
+
} else if (withTime.length === 1) {
|
|
108
|
+
startTime = endTime = withTime[0].time;
|
|
109
|
+
}
|
|
110
|
+
let totalDistance = null;
|
|
111
|
+
if (valid.length >= 2) {
|
|
112
|
+
let dist = 0;
|
|
113
|
+
for (let i = 1; i < valid.length; i++) {
|
|
114
|
+
const prev = valid[i - 1], curr = valid[i];
|
|
115
|
+
dist += haversine(prev.lat, prev.lon, curr.lat, curr.lon);
|
|
116
|
+
}
|
|
117
|
+
totalDistance = dist;
|
|
118
|
+
}
|
|
119
|
+
const speeds = valid.filter((f) => f.speed !== null).map((f) => f.speed * 1.852);
|
|
120
|
+
const maxSpeed = speeds.length > 0 ? Math.max(...speeds) : null;
|
|
121
|
+
let cep = null;
|
|
122
|
+
let drms2 = null;
|
|
123
|
+
let hRms = null;
|
|
124
|
+
let vRms = null;
|
|
125
|
+
if (valid.length >= 2) {
|
|
126
|
+
const toRad = (d) => d * Math.PI / 180;
|
|
127
|
+
const R = 6378137;
|
|
128
|
+
const e2 = 0.00669437999014;
|
|
129
|
+
const ecef = valid.map((f) => {
|
|
130
|
+
const lat = toRad(f.lat), lon = toRad(f.lon), h = f.alt ?? 0;
|
|
131
|
+
const N = R / Math.sqrt(1 - e2 * Math.sin(lat) ** 2);
|
|
132
|
+
return [
|
|
133
|
+
(N + h) * Math.cos(lat) * Math.cos(lon),
|
|
134
|
+
(N + h) * Math.cos(lat) * Math.sin(lon),
|
|
135
|
+
((1 - e2) * N + h) * Math.sin(lat)
|
|
136
|
+
];
|
|
137
|
+
});
|
|
138
|
+
const n = ecef.length;
|
|
139
|
+
const mx = ecef.reduce((s, p) => s + p[0], 0) / n;
|
|
140
|
+
const my = ecef.reduce((s, p) => s + p[1], 0) / n;
|
|
141
|
+
const mz = ecef.reduce((s, p) => s + p[2], 0) / n;
|
|
142
|
+
const refLon = Math.atan2(my, mx);
|
|
143
|
+
const refP = Math.sqrt(mx * mx + my * my);
|
|
144
|
+
const refLat = Math.atan2(mz, refP * (1 - e2));
|
|
145
|
+
const sinLat = Math.sin(refLat), cosLat = Math.cos(refLat);
|
|
146
|
+
const sinLon = Math.sin(refLon), cosLon = Math.cos(refLon);
|
|
147
|
+
const enu = ecef.map((p) => {
|
|
148
|
+
const dx = p[0] - mx, dy = p[1] - my, dz = p[2] - mz;
|
|
149
|
+
return {
|
|
150
|
+
e: -sinLon * dx + cosLon * dy,
|
|
151
|
+
n: -cosLon * sinLat * dx - sinLon * sinLat * dy + cosLat * dz,
|
|
152
|
+
u: cosLon * cosLat * dx + sinLon * cosLat * dy + sinLat * dz
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
const hDist = enu.map((p) => Math.sqrt(p.e * p.e + p.n * p.n));
|
|
156
|
+
hDist.sort((a, b) => a - b);
|
|
157
|
+
const medIdx = Math.floor(hDist.length * 0.5);
|
|
158
|
+
cep = hDist[medIdx];
|
|
159
|
+
const hSqSum = enu.reduce((s, p) => s + p.e * p.e + p.n * p.n, 0);
|
|
160
|
+
hRms = Math.sqrt(hSqSum / n);
|
|
161
|
+
drms2 = 2 * hRms;
|
|
162
|
+
const vSqSum = enu.reduce((s, p) => s + p.u * p.u, 0);
|
|
163
|
+
vRms = Math.sqrt(vSqSum / n);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
totalFixes: fixes.length,
|
|
167
|
+
validFixes: valid.length,
|
|
168
|
+
duration,
|
|
169
|
+
startTime,
|
|
170
|
+
endTime,
|
|
171
|
+
totalDistance,
|
|
172
|
+
maxSpeed,
|
|
173
|
+
cep,
|
|
174
|
+
drms2,
|
|
175
|
+
hRms,
|
|
176
|
+
vRms,
|
|
177
|
+
avgSatellites: withSat.length > 0 ? withSat.reduce((a, b) => a + b, 0) / withSat.length : null
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function haversine(lat1, lon1, lat2, lon2) {
|
|
181
|
+
const toRad = (d) => d * Math.PI / 180;
|
|
182
|
+
const dLat = toRad(lat2 - lat1);
|
|
183
|
+
const dLon = toRad(lon2 - lon1);
|
|
184
|
+
const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
|
|
185
|
+
return 6371e3 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
186
|
+
}
|
|
187
|
+
function parseNmeaFile(content) {
|
|
188
|
+
const lines = content.split(/\r?\n/);
|
|
189
|
+
const fixMap = /* @__PURE__ */ new Map();
|
|
190
|
+
const fixOrder = [];
|
|
191
|
+
let lastDate;
|
|
192
|
+
for (const raw of lines) {
|
|
193
|
+
const line = raw.trim();
|
|
194
|
+
if (!line.startsWith("$")) continue;
|
|
195
|
+
if (line.includes("*") && !verifyChecksum(line)) continue;
|
|
196
|
+
const fields = line.split("*")[0].split(",");
|
|
197
|
+
const sentenceId = fields[0].substring(3);
|
|
198
|
+
let partial = null;
|
|
199
|
+
if (sentenceId === "GGA") partial = parseGGA(fields);
|
|
200
|
+
else if (sentenceId === "RMC") partial = parseRMC(fields);
|
|
201
|
+
if (!partial) continue;
|
|
202
|
+
const key = partial.timeKey || `line-${fixOrder.length}`;
|
|
203
|
+
if (partial.hasDate && partial.time) {
|
|
204
|
+
const d = partial.time;
|
|
205
|
+
lastDate = `${String(d.getUTCDate()).padStart(2, "0")}${String(d.getUTCMonth() + 1).padStart(2, "0")}${String(d.getUTCFullYear() % 100).padStart(2, "0")}`;
|
|
206
|
+
}
|
|
207
|
+
const existing = fixMap.get(key);
|
|
208
|
+
if (existing) {
|
|
209
|
+
if (partial.alt !== void 0 && partial.alt !== null)
|
|
210
|
+
existing.alt = partial.alt;
|
|
211
|
+
if (partial.satellites !== void 0 && partial.satellites !== null)
|
|
212
|
+
existing.satellites = partial.satellites;
|
|
213
|
+
if (partial.fixQuality !== void 0)
|
|
214
|
+
existing.fixQuality = partial.fixQuality;
|
|
215
|
+
if (partial.speed !== void 0 && partial.speed !== null)
|
|
216
|
+
existing.speed = partial.speed;
|
|
217
|
+
if (partial.course !== void 0 && partial.course !== null)
|
|
218
|
+
existing.course = partial.course;
|
|
219
|
+
if (partial.hasDate && partial.time) existing.time = partial.time;
|
|
220
|
+
} else {
|
|
221
|
+
let time = partial.time;
|
|
222
|
+
if (!partial.hasDate && time && lastDate) {
|
|
223
|
+
time = buildDate(partial.timeKey, lastDate);
|
|
224
|
+
}
|
|
225
|
+
fixMap.set(key, {
|
|
226
|
+
time,
|
|
227
|
+
lat: partial.lat ?? 0,
|
|
228
|
+
lon: partial.lon ?? 0,
|
|
229
|
+
alt: partial.alt ?? null,
|
|
230
|
+
satellites: partial.satellites ?? null,
|
|
231
|
+
fixQuality: partial.fixQuality ?? 0,
|
|
232
|
+
speed: partial.speed ?? null,
|
|
233
|
+
course: partial.course ?? null
|
|
234
|
+
});
|
|
235
|
+
fixOrder.push(key);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const fixes = fixOrder.map((k) => fixMap.get(k));
|
|
239
|
+
return { fixes, stats: computeStats(fixes) };
|
|
240
|
+
}
|
|
241
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
242
|
+
0 && (module.exports = {
|
|
243
|
+
computeStats,
|
|
244
|
+
nmeaCoordToDecimal,
|
|
245
|
+
parseNmeaFile,
|
|
246
|
+
verifyChecksum
|
|
247
|
+
});
|
package/dist/nmea.d.cts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface NmeaFix {
|
|
2
|
+
time: Date | null;
|
|
3
|
+
lat: number;
|
|
4
|
+
lon: number;
|
|
5
|
+
alt: number | null;
|
|
6
|
+
satellites: number | null;
|
|
7
|
+
fixQuality: number;
|
|
8
|
+
speed: number | null;
|
|
9
|
+
course: number | null;
|
|
10
|
+
}
|
|
11
|
+
interface NmeaStats {
|
|
12
|
+
totalFixes: number;
|
|
13
|
+
validFixes: number;
|
|
14
|
+
duration: number | null;
|
|
15
|
+
startTime: Date | null;
|
|
16
|
+
endTime: Date | null;
|
|
17
|
+
totalDistance: number | null;
|
|
18
|
+
maxSpeed: number | null;
|
|
19
|
+
cep: number | null;
|
|
20
|
+
drms2: number | null;
|
|
21
|
+
hRms: number | null;
|
|
22
|
+
vRms: number | null;
|
|
23
|
+
avgSatellites: number | null;
|
|
24
|
+
}
|
|
25
|
+
interface NmeaTrack {
|
|
26
|
+
fixes: NmeaFix[];
|
|
27
|
+
stats: NmeaStats;
|
|
28
|
+
}
|
|
29
|
+
/** Convert NMEA latitude/longitude (ddmm.mmmm / dddmm.mmmm) to decimal degrees. */
|
|
30
|
+
declare function nmeaCoordToDecimal(raw: string, direction: string): number;
|
|
31
|
+
/** Verify XOR checksum between '$' and '*'. */
|
|
32
|
+
declare function verifyChecksum(sentence: string): boolean;
|
|
33
|
+
declare function computeStats(fixes: NmeaFix[]): NmeaStats;
|
|
34
|
+
/** Parse an NMEA log file and return a track with fixes and statistics. */
|
|
35
|
+
declare function parseNmeaFile(content: string): NmeaTrack;
|
|
36
|
+
|
|
37
|
+
export { type NmeaFix, type NmeaStats, type NmeaTrack, computeStats, nmeaCoordToDecimal, parseNmeaFile, verifyChecksum };
|
package/dist/nmea.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface NmeaFix {
|
|
2
|
+
time: Date | null;
|
|
3
|
+
lat: number;
|
|
4
|
+
lon: number;
|
|
5
|
+
alt: number | null;
|
|
6
|
+
satellites: number | null;
|
|
7
|
+
fixQuality: number;
|
|
8
|
+
speed: number | null;
|
|
9
|
+
course: number | null;
|
|
10
|
+
}
|
|
11
|
+
interface NmeaStats {
|
|
12
|
+
totalFixes: number;
|
|
13
|
+
validFixes: number;
|
|
14
|
+
duration: number | null;
|
|
15
|
+
startTime: Date | null;
|
|
16
|
+
endTime: Date | null;
|
|
17
|
+
totalDistance: number | null;
|
|
18
|
+
maxSpeed: number | null;
|
|
19
|
+
cep: number | null;
|
|
20
|
+
drms2: number | null;
|
|
21
|
+
hRms: number | null;
|
|
22
|
+
vRms: number | null;
|
|
23
|
+
avgSatellites: number | null;
|
|
24
|
+
}
|
|
25
|
+
interface NmeaTrack {
|
|
26
|
+
fixes: NmeaFix[];
|
|
27
|
+
stats: NmeaStats;
|
|
28
|
+
}
|
|
29
|
+
/** Convert NMEA latitude/longitude (ddmm.mmmm / dddmm.mmmm) to decimal degrees. */
|
|
30
|
+
declare function nmeaCoordToDecimal(raw: string, direction: string): number;
|
|
31
|
+
/** Verify XOR checksum between '$' and '*'. */
|
|
32
|
+
declare function verifyChecksum(sentence: string): boolean;
|
|
33
|
+
declare function computeStats(fixes: NmeaFix[]): NmeaStats;
|
|
34
|
+
/** Parse an NMEA log file and return a track with fixes and statistics. */
|
|
35
|
+
declare function parseNmeaFile(content: string): NmeaTrack;
|
|
36
|
+
|
|
37
|
+
export { type NmeaFix, type NmeaStats, type NmeaTrack, computeStats, nmeaCoordToDecimal, parseNmeaFile, verifyChecksum };
|