gnss-js 1.2.0 → 1.3.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/README.md CHANGED
@@ -51,6 +51,16 @@ const { distance } = vincenty(
51
51
  // distance in meters, angles in radians
52
52
  ```
53
53
 
54
+ ### Visibility / pass prediction
55
+
56
+ ```ts
57
+ import { computeVisibility } from 'gnss-js/orbit';
58
+
59
+ const vis = computeVisibility(ephemerides, rxEcef, startMs, endMs, 300, 10);
60
+ // vis.passes: rise/peak/set per satellite; vis.pdop / vis.visibleCount
61
+ // per epoch; vis.elevation[prn]: elevation timeline for plotting.
62
+ ```
63
+
54
64
  ### Single-point positioning
55
65
 
56
66
  ```ts
@@ -440,6 +440,65 @@ function computeLiveSkyPositions(ephemerides, rxPos, cn0Map) {
440
440
  }
441
441
  return result;
442
442
  }
443
+ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, elevationMaskDeg = 10) {
444
+ const stepMs = stepSec * 1e3;
445
+ const times = [];
446
+ for (let t = startMs; t <= endMs; t += stepMs) times.push(t);
447
+ const maskRad = elevationMaskDeg * Math.PI / 180;
448
+ const all = computeAllPositions(ephemerides, times, rxPos);
449
+ const elevation = {};
450
+ const visibleCount = new Array(times.length).fill(0);
451
+ const pdop = new Array(times.length).fill(null);
452
+ const visiblePerEpoch = times.map(() => []);
453
+ for (const prn of all.prns) {
454
+ const series = all.positions[prn];
455
+ elevation[prn] = series.map((p) => p ? p.el : null);
456
+ for (let i = 0; i < series.length; i++) {
457
+ const p = series[i];
458
+ if (p && p.el >= maskRad) {
459
+ visibleCount[i]++;
460
+ visiblePerEpoch[i].push({ az: p.az, el: p.el });
461
+ }
462
+ }
463
+ }
464
+ for (let i = 0; i < times.length; i++) {
465
+ const dop = computeDop(visiblePerEpoch[i]);
466
+ pdop[i] = dop ? dop.pdop : null;
467
+ }
468
+ const passes = [];
469
+ for (const prn of all.prns) {
470
+ const els = elevation[prn];
471
+ let start = -1;
472
+ let peakEl = -Infinity;
473
+ let peakTime = 0;
474
+ for (let i = 0; i <= els.length; i++) {
475
+ const el = i < els.length ? els[i] : null;
476
+ const above = el !== null && el !== void 0 && el >= maskRad;
477
+ if (above) {
478
+ if (start === -1) {
479
+ start = i;
480
+ peakEl = -Infinity;
481
+ }
482
+ if (el > peakEl) {
483
+ peakEl = el;
484
+ peakTime = times[i];
485
+ }
486
+ } else if (start !== -1) {
487
+ passes.push({
488
+ prn,
489
+ system: prn[0],
490
+ rise: times[start],
491
+ set: times[i - 1],
492
+ peakTime,
493
+ peakEl
494
+ });
495
+ start = -1;
496
+ }
497
+ }
498
+ }
499
+ passes.sort((a, b) => a.rise - b.rise);
500
+ return { times, elevation, visibleCount, pdop, passes };
501
+ }
443
502
 
444
503
  export {
445
504
  keplerPosition,
@@ -450,5 +509,6 @@ export {
450
509
  computeSatPosition,
451
510
  navTimesFromEph,
452
511
  computeAllPositions,
453
- computeLiveSkyPositions
512
+ computeLiveSkyPositions,
513
+ computeVisibility
454
514
  };
package/dist/index.cjs CHANGED
@@ -110,6 +110,7 @@ __export(src_exports, {
110
110
  computePsdDb: () => computePsdDb,
111
111
  computeSatPosition: () => computeSatPosition,
112
112
  computeStats: () => computeStats2,
113
+ computeVisibility: () => computeVisibility,
113
114
  connectToMountpoint: () => connectToMountpoint,
114
115
  createStationMeta: () => createStationMeta,
115
116
  createStreamStats: () => createStreamStats,
@@ -4408,6 +4409,65 @@ function computeLiveSkyPositions(ephemerides, rxPos, cn0Map) {
4408
4409
  }
4409
4410
  return result;
4410
4411
  }
4412
+ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, elevationMaskDeg = 10) {
4413
+ const stepMs = stepSec * 1e3;
4414
+ const times = [];
4415
+ for (let t = startMs; t <= endMs; t += stepMs) times.push(t);
4416
+ const maskRad = elevationMaskDeg * Math.PI / 180;
4417
+ const all = computeAllPositions(ephemerides, times, rxPos);
4418
+ const elevation = {};
4419
+ const visibleCount = new Array(times.length).fill(0);
4420
+ const pdop = new Array(times.length).fill(null);
4421
+ const visiblePerEpoch = times.map(() => []);
4422
+ for (const prn of all.prns) {
4423
+ const series = all.positions[prn];
4424
+ elevation[prn] = series.map((p) => p ? p.el : null);
4425
+ for (let i = 0; i < series.length; i++) {
4426
+ const p = series[i];
4427
+ if (p && p.el >= maskRad) {
4428
+ visibleCount[i]++;
4429
+ visiblePerEpoch[i].push({ az: p.az, el: p.el });
4430
+ }
4431
+ }
4432
+ }
4433
+ for (let i = 0; i < times.length; i++) {
4434
+ const dop = computeDop(visiblePerEpoch[i]);
4435
+ pdop[i] = dop ? dop.pdop : null;
4436
+ }
4437
+ const passes = [];
4438
+ for (const prn of all.prns) {
4439
+ const els = elevation[prn];
4440
+ let start = -1;
4441
+ let peakEl = -Infinity;
4442
+ let peakTime = 0;
4443
+ for (let i = 0; i <= els.length; i++) {
4444
+ const el = i < els.length ? els[i] : null;
4445
+ const above = el !== null && el !== void 0 && el >= maskRad;
4446
+ if (above) {
4447
+ if (start === -1) {
4448
+ start = i;
4449
+ peakEl = -Infinity;
4450
+ }
4451
+ if (el > peakEl) {
4452
+ peakEl = el;
4453
+ peakTime = times[i];
4454
+ }
4455
+ } else if (start !== -1) {
4456
+ passes.push({
4457
+ prn,
4458
+ system: prn[0],
4459
+ rise: times[start],
4460
+ set: times[i - 1],
4461
+ peakTime,
4462
+ peakEl
4463
+ });
4464
+ start = -1;
4465
+ }
4466
+ }
4467
+ }
4468
+ passes.sort((a, b) => a.rise - b.rise);
4469
+ return { times, elevation, visibleCount, pdop, passes };
4470
+ }
4411
4471
 
4412
4472
  // src/ntrip/index.ts
4413
4473
  function parseStreamEntry(fields) {
@@ -6320,6 +6380,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
6320
6380
  computePsdDb,
6321
6381
  computeSatPosition,
6322
6382
  computeStats,
6383
+ computeVisibility,
6323
6384
  connectToMountpoint,
6324
6385
  createStationMeta,
6325
6386
  createStreamStats,
package/dist/index.d.cts CHANGED
@@ -9,7 +9,7 @@ export { E as EpochSummary, P as ParseOptions, R as RinexHeader, a as RinexResul
9
9
  export { E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, p as parseNavFile } from './nav-BAI1a9vK.cjs';
10
10
  export { B as BitReader, E as EphemerisInfo, R as Rtcm3DebugHandler, a as Rtcm3Decoder, b as Rtcm3Frame, d as decodeEphemeris, r as readString, c as reportDecodeError, s as setRtcm3DebugHandler } from './ephemeris-Ohl6NAfw.cjs';
11
11
  export { MessageTypeStats, MsmEpoch, MsmSatObs, MsmSignal, RTCM3_MESSAGE_NAMES, SatCn0, SignalCn0, StationMeta, StreamStats, createStationMeta, createStreamStats, decodeMsmFull, msmEpochToDate, resetGloFreqCache, rtcm3Constellation, setGloFreqNumber, updateStationMeta, updateStreamStats } from './rtcm3.cjs';
12
- export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris } from './orbit.cjs';
12
+ export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris } from './orbit.cjs';
13
13
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
14
14
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, analyzeQuality } from './analysis.cjs';
15
15
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.cjs';
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { E as EpochSummary, P as ParseOptions, R as RinexHeader, a as RinexResul
9
9
  export { E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, p as parseNavFile } from './nav-BAI1a9vK.js';
10
10
  export { B as BitReader, E as EphemerisInfo, R as Rtcm3DebugHandler, a as Rtcm3Decoder, b as Rtcm3Frame, d as decodeEphemeris, r as readString, c as reportDecodeError, s as setRtcm3DebugHandler } from './ephemeris-Ohl6NAfw.js';
11
11
  export { MessageTypeStats, MsmEpoch, MsmSatObs, MsmSignal, RTCM3_MESSAGE_NAMES, SatCn0, SignalCn0, StationMeta, StreamStats, createStationMeta, createStreamStats, decodeMsmFull, msmEpochToDate, resetGloFreqCache, rtcm3Constellation, setGloFreqNumber, updateStationMeta, updateStreamStats } from './rtcm3.js';
12
- export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris } from './orbit.js';
12
+ export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris } from './orbit.js';
13
13
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.js';
14
14
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, analyzeQuality } from './analysis.js';
15
15
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.js';
package/dist/index.js CHANGED
@@ -92,12 +92,13 @@ import {
92
92
  computeDop,
93
93
  computeLiveSkyPositions,
94
94
  computeSatPosition,
95
+ computeVisibility,
95
96
  ecefToAzEl,
96
97
  glonassPosition,
97
98
  keplerPosition,
98
99
  navTimesFromEph,
99
100
  selectEphemeris
100
- } from "./chunk-VANVV4F7.js";
101
+ } from "./chunk-ZCNXERQ2.js";
101
102
  import {
102
103
  getBdsTime,
103
104
  getDateFromBdsTime,
@@ -322,6 +323,7 @@ export {
322
323
  computePsdDb,
323
324
  computeSatPosition,
324
325
  computeStats,
326
+ computeVisibility,
325
327
  connectToMountpoint,
326
328
  createStationMeta,
327
329
  createStreamStats,
package/dist/orbit.cjs CHANGED
@@ -24,6 +24,7 @@ __export(orbit_exports, {
24
24
  computeDop: () => computeDop,
25
25
  computeLiveSkyPositions: () => computeLiveSkyPositions,
26
26
  computeSatPosition: () => computeSatPosition,
27
+ computeVisibility: () => computeVisibility,
27
28
  ecefToAzEl: () => ecefToAzEl,
28
29
  ecefToGeodetic: () => ecefToGeodetic,
29
30
  geodeticToEcef: () => geodeticToEcef,
@@ -591,12 +592,72 @@ function computeLiveSkyPositions(ephemerides, rxPos, cn0Map) {
591
592
  }
592
593
  return result;
593
594
  }
595
+ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, elevationMaskDeg = 10) {
596
+ const stepMs = stepSec * 1e3;
597
+ const times = [];
598
+ for (let t = startMs; t <= endMs; t += stepMs) times.push(t);
599
+ const maskRad = elevationMaskDeg * Math.PI / 180;
600
+ const all = computeAllPositions(ephemerides, times, rxPos);
601
+ const elevation = {};
602
+ const visibleCount = new Array(times.length).fill(0);
603
+ const pdop = new Array(times.length).fill(null);
604
+ const visiblePerEpoch = times.map(() => []);
605
+ for (const prn of all.prns) {
606
+ const series = all.positions[prn];
607
+ elevation[prn] = series.map((p) => p ? p.el : null);
608
+ for (let i = 0; i < series.length; i++) {
609
+ const p = series[i];
610
+ if (p && p.el >= maskRad) {
611
+ visibleCount[i]++;
612
+ visiblePerEpoch[i].push({ az: p.az, el: p.el });
613
+ }
614
+ }
615
+ }
616
+ for (let i = 0; i < times.length; i++) {
617
+ const dop = computeDop(visiblePerEpoch[i]);
618
+ pdop[i] = dop ? dop.pdop : null;
619
+ }
620
+ const passes = [];
621
+ for (const prn of all.prns) {
622
+ const els = elevation[prn];
623
+ let start = -1;
624
+ let peakEl = -Infinity;
625
+ let peakTime = 0;
626
+ for (let i = 0; i <= els.length; i++) {
627
+ const el = i < els.length ? els[i] : null;
628
+ const above = el !== null && el !== void 0 && el >= maskRad;
629
+ if (above) {
630
+ if (start === -1) {
631
+ start = i;
632
+ peakEl = -Infinity;
633
+ }
634
+ if (el > peakEl) {
635
+ peakEl = el;
636
+ peakTime = times[i];
637
+ }
638
+ } else if (start !== -1) {
639
+ passes.push({
640
+ prn,
641
+ system: prn[0],
642
+ rise: times[start],
643
+ set: times[i - 1],
644
+ peakTime,
645
+ peakEl
646
+ });
647
+ start = -1;
648
+ }
649
+ }
650
+ }
651
+ passes.sort((a, b) => a.rise - b.rise);
652
+ return { times, elevation, visibleCount, pdop, passes };
653
+ }
594
654
  // Annotate the CommonJS export names for ESM import in node:
595
655
  0 && (module.exports = {
596
656
  computeAllPositions,
597
657
  computeDop,
598
658
  computeLiveSkyPositions,
599
659
  computeSatPosition,
660
+ computeVisibility,
600
661
  ecefToAzEl,
601
662
  ecefToGeodetic,
602
663
  geodeticToEcef,
package/dist/orbit.d.cts CHANGED
@@ -113,5 +113,42 @@ declare function computeAllPositions(ephemerides: Ephemeris[], times: number[],
113
113
  * Returns array of SatAzEl for all satellites with valid ephemeris.
114
114
  */
115
115
  declare function computeLiveSkyPositions(ephemerides: Map<string, EphemerisInfo>, rxPos: [number, number, number], cn0Map?: Map<string, number>): SatAzEl[];
116
+ /** A single above-mask visibility interval for one satellite. */
117
+ interface VisibilityPass {
118
+ prn: string;
119
+ system: string;
120
+ /** Rise time (Unix ms) — first sample at or above the mask. */
121
+ rise: number;
122
+ /** Set time (Unix ms) — last sample at or above the mask. */
123
+ set: number;
124
+ /** Time of peak elevation (Unix ms). */
125
+ peakTime: number;
126
+ /** Peak elevation (radians). */
127
+ peakEl: number;
128
+ }
129
+ interface VisibilityResult {
130
+ /** Sample epochs (Unix ms). */
131
+ times: number[];
132
+ /** Elevation (radians) per PRN per epoch; null when below −0.05 rad / no eph. */
133
+ elevation: Record<string, (number | null)[]>;
134
+ /** Number of satellites at or above the mask, per epoch. */
135
+ visibleCount: number[];
136
+ /** PDOP per epoch (null when < 4 satellites above the mask). */
137
+ pdop: (number | null)[];
138
+ /** Discrete above-mask passes, sorted by rise time. */
139
+ passes: VisibilityPass[];
140
+ }
141
+ /**
142
+ * Predict satellite visibility and DOP over a time window for a fixed
143
+ * receiver location.
144
+ *
145
+ * @param ephemerides Broadcast ephemerides covering the window.
146
+ * @param rxPos Receiver ECEF position (meters).
147
+ * @param startMs Window start (Unix ms).
148
+ * @param endMs Window end (Unix ms).
149
+ * @param stepSec Sample spacing in seconds (default 300).
150
+ * @param elevationMaskDeg Elevation mask in degrees (default 10).
151
+ */
152
+ declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, number, number], startMs: number, endMs: number, stepSec?: number, elevationMaskDeg?: number): VisibilityResult;
116
153
 
117
- export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
154
+ export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
package/dist/orbit.d.ts CHANGED
@@ -113,5 +113,42 @@ declare function computeAllPositions(ephemerides: Ephemeris[], times: number[],
113
113
  * Returns array of SatAzEl for all satellites with valid ephemeris.
114
114
  */
115
115
  declare function computeLiveSkyPositions(ephemerides: Map<string, EphemerisInfo>, rxPos: [number, number, number], cn0Map?: Map<string, number>): SatAzEl[];
116
+ /** A single above-mask visibility interval for one satellite. */
117
+ interface VisibilityPass {
118
+ prn: string;
119
+ system: string;
120
+ /** Rise time (Unix ms) — first sample at or above the mask. */
121
+ rise: number;
122
+ /** Set time (Unix ms) — last sample at or above the mask. */
123
+ set: number;
124
+ /** Time of peak elevation (Unix ms). */
125
+ peakTime: number;
126
+ /** Peak elevation (radians). */
127
+ peakEl: number;
128
+ }
129
+ interface VisibilityResult {
130
+ /** Sample epochs (Unix ms). */
131
+ times: number[];
132
+ /** Elevation (radians) per PRN per epoch; null when below −0.05 rad / no eph. */
133
+ elevation: Record<string, (number | null)[]>;
134
+ /** Number of satellites at or above the mask, per epoch. */
135
+ visibleCount: number[];
136
+ /** PDOP per epoch (null when < 4 satellites above the mask). */
137
+ pdop: (number | null)[];
138
+ /** Discrete above-mask passes, sorted by rise time. */
139
+ passes: VisibilityPass[];
140
+ }
141
+ /**
142
+ * Predict satellite visibility and DOP over a time window for a fixed
143
+ * receiver location.
144
+ *
145
+ * @param ephemerides Broadcast ephemerides covering the window.
146
+ * @param rxPos Receiver ECEF position (meters).
147
+ * @param startMs Window start (Unix ms).
148
+ * @param endMs Window end (Unix ms).
149
+ * @param stepSec Sample spacing in seconds (default 300).
150
+ * @param elevationMaskDeg Elevation mask in degrees (default 10).
151
+ */
152
+ declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, number, number], startMs: number, endMs: number, stepSec?: number, elevationMaskDeg?: number): VisibilityResult;
116
153
 
117
- export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
154
+ export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
package/dist/orbit.js CHANGED
@@ -3,12 +3,13 @@ import {
3
3
  computeDop,
4
4
  computeLiveSkyPositions,
5
5
  computeSatPosition,
6
+ computeVisibility,
6
7
  ecefToAzEl,
7
8
  glonassPosition,
8
9
  keplerPosition,
9
10
  navTimesFromEph,
10
11
  selectEphemeris
11
- } from "./chunk-VANVV4F7.js";
12
+ } from "./chunk-ZCNXERQ2.js";
12
13
  import "./chunk-HVXYFUCB.js";
13
14
  import {
14
15
  ecefToGeodetic,
@@ -21,6 +22,7 @@ export {
21
22
  computeDop,
22
23
  computeLiveSkyPositions,
23
24
  computeSatPosition,
25
+ computeVisibility,
24
26
  ecefToAzEl,
25
27
  ecefToGeodetic,
26
28
  geodeticToEcef,
@@ -2,7 +2,7 @@ import {
2
2
  computeDop,
3
3
  computeSatPosition,
4
4
  ecefToAzEl
5
- } from "./chunk-VANVV4F7.js";
5
+ } from "./chunk-ZCNXERQ2.js";
6
6
  import "./chunk-HVXYFUCB.js";
7
7
  import "./chunk-37QNKGTC.js";
8
8
  import "./chunk-6FAL6P4G.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
5
5
  "type": "module",
6
6
  "sideEffects": false,