@signalk/tracks-plugin 3.0.0 → 3.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/dist/index.js CHANGED
@@ -816,15 +816,16 @@ function createTrackProvider(deps) {
816
816
  *
817
817
  * `epsilon` implies `simplify=true`, so an explicit tolerance is honoured
818
818
  * whether or not the flag came with it. `simplify` alone leaves the tolerance
819
- * to the provider: the spec says "a tolerance suited to the size of the box",
820
- * and the extent of the track *as thinning left it* is the honest basis for
821
- * that — a query bbox may be absent, and when present it is what the client
822
- * searched in rather than what came back. It cannot be the simplified extent,
823
- * since that is the thing being computed.
819
+ * to the provider, and the spec asks for one "suited to the size of the box":
820
+ * with a `bbox` it is sized to that box, which for a chart viewer is its view,
821
+ * so detail follows the zoom. With no box the choice is the provider's, and
822
+ * the extent of the track *as thinning left it* stands in -- not the
823
+ * simplified extent, since that is the thing being computed. A `bbox` without
824
+ * `simplify` simplifies nothing.
824
825
  *
825
- * Chosen from the whole track rather than per segment, so every leg of one
826
- * track is simplified to the same tolerance and the single reported `epsilon`
827
- * describes all of them.
826
+ * Chosen once per track rather than per segment, so every leg of one track is
827
+ * simplified to the same tolerance and the single reported `epsilon` describes
828
+ * all of them.
828
829
  *
829
830
  * A non-positive `epsilon` is not reachable through the API — the schema
830
831
  * declares `exclusiveMinimum: 0`, so the server rejects it — and is treated
@@ -835,16 +836,20 @@ function createTrackProvider(deps) {
835
836
  function chooseEpsilon(points, query) {
836
837
  const explicit = query.epsilon;
837
838
  if (explicit !== void 0 && explicit > 0) return explicit;
838
- return query.simplify === true ? autoEpsilon(points) : void 0;
839
+ if (query.simplify !== true) return;
840
+ if (query.bbox) return extentEpsilon(query.bbox);
841
+ const bounds = boundsOf(points);
842
+ return bounds ? extentEpsilon(bounds) : void 0;
839
843
  }
840
844
  /**
841
- * How much of a track's own extent the automatic tolerance may deviate by.
845
+ * How much of an extent -- the query box, or the track without one -- the
846
+ * automatic tolerance may deviate by.
842
847
  *
843
- * One part in a thousand. Provisional while the v2 API is designed in
844
- * SignalK/signalk-server#2504: it is only a provider-chosen default for
845
- * `simplify` without an explicit epsilon, and a client that calibrates against
846
- * the exact ratio rather than reading the reported `epsilon` back would make
847
- * it expensive to change.
848
+ * One part in a thousand: across a view that is roughly one screen pixel.
849
+ * Provisional while the v2 API is designed in SignalK/signalk-server#2504: it
850
+ * is only a provider-chosen default for `simplify` without an explicit
851
+ * epsilon, and a client that calibrates against the exact ratio rather than
852
+ * reading the reported `epsilon` back would make it expensive to change.
848
853
  */
849
854
  var AUTO_EPSILON_DIVISOR = 1e3;
850
855
  /**
@@ -856,17 +861,15 @@ var AUTO_EPSILON_DIVISOR = 1e3;
856
861
  */
857
862
  var MIN_AUTO_EPSILON = 1;
858
863
  /**
859
- * A tolerance scaled to how much ground a track covers.
864
+ * A tolerance scaled to how much ground an extent covers.
860
865
  *
861
- * One part in a thousand of the track's diagonal: enough to drop the jitter of
862
- * a boat holding station while keeping every turn a passage is made of. A
863
- * fixed metre value cannot do both — 10 m erases nothing on an ocean crossing
864
- * and erases a marina approach entirely.
866
+ * One part in a thousand of its diagonal. For a track that drops the jitter of
867
+ * a boat holding station while keeping every turn a passage is made of; for a
868
+ * view it keeps what a pixel can show. A fixed metre value cannot do either —
869
+ * 10 m erases nothing on an ocean crossing and erases a marina approach
870
+ * entirely.
865
871
  */
866
- function autoEpsilon(points) {
867
- const bounds = boundsOf(points);
868
- if (!bounds) return;
869
- const [west, south, east, north] = bounds;
872
+ function extentEpsilon([west, south, east, north]) {
870
873
  const midLat = (south + north) / 2;
871
874
  const height = (north - south) * M_PER_DEG;
872
875
  const width = (east >= west ? east - west : east + 360 - west) * M_PER_DEG * Math.cos(midLat * Math.PI / 180);
@@ -12186,6 +12189,12 @@ var SqliteTrackStore = class {
12186
12189
  debug;
12187
12190
  /** Timestamp of the last position stored per context, for throttling. */
12188
12191
  lastStored = /* @__PURE__ */ new Map();
12192
+ /**
12193
+ * The stored name per context, dated by its newest report. A name arrives
12194
+ * with every position, so a repeat is answered here instead of committing a
12195
+ * row that has not changed.
12196
+ */
12197
+ lastName = /* @__PURE__ */ new Map();
12189
12198
  constructor(config, debug) {
12190
12199
  this.maxCells = config.maxCells ?? DEFAULT_MAX_CELLS;
12191
12200
  this.segmentGap = config.segmentGap ?? DEFAULT_SEGMENT_GAP;
@@ -12347,7 +12356,16 @@ var SqliteTrackStore = class {
12347
12356
  recordName(context, name, timestamp = Date.now()) {
12348
12357
  const trimmed = name.trim();
12349
12358
  if (trimmed === "") return;
12350
- this.insertName.run(context, trimmed, timestamp);
12359
+ const seen = this.lastName.get(context);
12360
+ if (seen && seen.timestamp > timestamp) return;
12361
+ if (seen?.name === trimmed) {
12362
+ seen.timestamp = timestamp;
12363
+ return;
12364
+ }
12365
+ if (Number(this.insertName.run(context, trimmed, timestamp).changes) > 0) this.lastName.set(context, {
12366
+ name: trimmed,
12367
+ timestamp
12368
+ });
12351
12369
  }
12352
12370
  nameFor(context) {
12353
12371
  return this.selectName.get(context)?.name;
@@ -12356,7 +12374,10 @@ var SqliteTrackStore = class {
12356
12374
  const cutoff = Date.now() - maxAge;
12357
12375
  const dropped = this.db.prepare("SELECT context FROM positions GROUP BY context HAVING MAX(timestamp) < ? AND context IS NOT ?").all(cutoff, keep ?? null);
12358
12376
  this.db.prepare("DELETE FROM positions WHERE context IN (SELECT context FROM positions GROUP BY context HAVING MAX(timestamp) < ?) AND context IS NOT ?").run(cutoff, keep ?? null);
12359
- for (const { context } of dropped) this.db.prepare("DELETE FROM names WHERE context = ?").run(context);
12377
+ for (const { context } of dropped) {
12378
+ this.db.prepare("DELETE FROM names WHERE context = ?").run(context);
12379
+ this.lastName.delete(context);
12380
+ }
12360
12381
  for (const { context } of dropped) this.lastStored.delete(context);
12361
12382
  if (this.retention > 0) {
12362
12383
  const oldest = Date.now() - this.retention;
@@ -12365,7 +12386,14 @@ var SqliteTrackStore = class {
12365
12386
  }
12366
12387
  }
12367
12388
  close() {
12368
- this.db.close();
12389
+ try {
12390
+ const redate = this.db.prepare("UPDATE names SET timestamp = ? WHERE context = ? AND name = ? AND timestamp < ?");
12391
+ this.db.exec("BEGIN");
12392
+ for (const [context, { name, timestamp }] of this.lastName) redate.run(timestamp, context, name, timestamp);
12393
+ this.db.exec("COMMIT");
12394
+ } finally {
12395
+ this.db.close();
12396
+ }
12369
12397
  }
12370
12398
  /** Unsigned round-trip check, used by the tests. */
12371
12399
  cellIdsFor(context) {