@mark1russell7/highways 0.7.0 → 0.8.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/build/index.js CHANGED
@@ -1048,9 +1048,10 @@ function checkProperties(graph, result, limit = 100) {
1048
1048
  detail: `run ${run.id} has fewer than two edges`
1049
1049
  })) return out;
1050
1050
  }
1051
+ let back;
1051
1052
  for (const id of run.edges) {
1052
1053
  const r = routes.get(id);
1053
- if (!r || !(holds$2(r.points, run.points) || holds$2(r.points, [...run.points].reverse()))) {
1054
+ if (!r || !(holds$2(r.points, run.points) || holds$2(r.points, back ??= [...run.points].reverse()))) {
1054
1055
  if (add({
1055
1056
  rule: "run-shared",
1056
1057
  edge: id,
@@ -1115,7 +1116,11 @@ function onBoundary$1(p, b) {
1115
1116
  /** This function tells if the polyline `p` holds the points of `part` in a row. */
1116
1117
  function holds$2(p, part) {
1117
1118
  if (part.length === 0) return true;
1118
- for (let i = 0; i + part.length <= p.length; i++) if (part.every((q, j) => dist(q, p[i + j]) <= .5)) return true;
1119
+ for (let i = 0; i + part.length <= p.length; i++) {
1120
+ let j = 0;
1121
+ while (j < part.length && dist(part[j], p[i + j]) <= .5) j++;
1122
+ if (j === part.length) return true;
1123
+ }
1119
1124
  return false;
1120
1125
  }
1121
1126
  // src/curves/sample.ts
@@ -2043,6 +2048,109 @@ function straightCurve(points) {
2043
2048
  to
2044
2049
  });
2045
2050
  }
2051
+ // src/compare.ts
2052
+ /** This function compares two texts by their UTF-16 code units. It gives 0 only for equal texts. */
2053
+ const byText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
2054
+ /** This function compares two edges by the hash of their ids, and then by their ids. Thus the order of two edges does not depend on the input. */
2055
+ function byHash(a, b) {
2056
+ if (a.h !== b.h) return a.h - b.h;
2057
+ return byText(a.e.id, b.e.id);
2058
+ }
2059
+ // src/lane-order.ts
2060
+ /** Two points nearer than this distance are the same point. The value is the same as in `segments.ts`. */
2061
+ const SAME$4 = 1e-6;
2062
+ /** The angle, from -π to π, of the turn at `base` from the direction `from` → `base` to the point `to`. */
2063
+ function turn$1(from, base, to) {
2064
+ const dx = base.x - from.x;
2065
+ const dy = base.y - from.y;
2066
+ const vx = to.x - base.x;
2067
+ const vy = to.y - base.y;
2068
+ return Math.atan2(dx * vy - dy * vx, dx * vx + dy * vy);
2069
+ }
2070
+ /** This function gives the place where the routes `P` and `Q` part after the last point of the run, or nothing. */
2071
+ function partAfter(P, Q) {
2072
+ for (let j = 1;; j++) {
2073
+ const a = P.p[P.e + j];
2074
+ const b = Q.p[Q.e + j];
2075
+ if (a === void 0 || b === void 0) return void 0;
2076
+ if (a !== b && dist(a, b) > SAME$4) {
2077
+ const from = P.p[P.e + j - 2];
2078
+ const at = P.p[P.e + j - 1];
2079
+ const side = turn$1(from, at, a) - turn$1(from, at, b);
2080
+ return side === 0 ? void 0 : {
2081
+ at,
2082
+ side
2083
+ };
2084
+ }
2085
+ }
2086
+ }
2087
+ /** This function gives the place where the routes `P` and `Q` part before the first point of the run, or nothing. */
2088
+ function partBefore(P, Q) {
2089
+ for (let j = 1;; j++) {
2090
+ const a = P.p[P.s - j];
2091
+ const b = Q.p[Q.s - j];
2092
+ if (a === void 0 || b === void 0) return void 0;
2093
+ if (a !== b && dist(a, b) > SAME$4) {
2094
+ const from = P.p[P.s - j + 2];
2095
+ const at = P.p[P.s - j + 1];
2096
+ const side = turn$1(from, at, b) - turn$1(from, at, a);
2097
+ return side === 0 ? void 0 : {
2098
+ at,
2099
+ side
2100
+ };
2101
+ }
2102
+ }
2103
+ }
2104
+ /** This function compares two placed routes by their edge ids. */
2105
+ const byEdge$2 = (P, Q) => byText(P.edge, Q.edge);
2106
+ /** This function puts the point `a` before the point `b`: the smaller x first, then the smaller y. */
2107
+ const firstPoint = (a, b) => a.x < b.x - SAME$4 || Math.abs(a.x - b.x) <= SAME$4 && a.y < b.y;
2108
+ /**
2109
+ * This function is the relation of the order `run`. The side after the run tells the order first, then the side before the run, then the edges.
2110
+ * A value below 0 puts `P` before `Q`. The relation is not transitive: three lanes can make a cycle.
2111
+ */
2112
+ const byRun = (P, Q) => partAfter(P, Q)?.side ?? partBefore(P, Q)?.side ?? byEdge$2(P, Q);
2113
+ /**
2114
+ * This function is the relation of the order `bundle`. When the two ends disagree, the end with the smaller x, then the smaller y, tells the side.
2115
+ * A value below 0 puts `P` before `Q`. The relation is not transitive: three lanes can make a cycle.
2116
+ */
2117
+ const byBundle = (P, Q) => {
2118
+ const after = partAfter(P, Q);
2119
+ const before = partBefore(P, Q);
2120
+ if (after && before && Math.sign(after.side) !== Math.sign(before.side)) return firstPoint(after.at, before.at) ? after.side : before.side;
2121
+ return after?.side ?? before?.side ?? byEdge$2(P, Q);
2122
+ };
2123
+ /**
2124
+ * This function sorts a list with a stable merge sort of the package, not with `Array.prototype.sort`.
2125
+ * The pairs that it gives to `compare` depend only on the list and on the results of `compare`.
2126
+ * Thus the result is the same on each JavaScript engine, also when `compare` is not a total order.
2127
+ * A value of `compare(a, b)` above 0 puts `b` before `a`. Any other value (below 0, 0 or NaN) keeps `a` first.
2128
+ */
2129
+ function mergeSort(list, compare) {
2130
+ if (list.length < 2) return [...list];
2131
+ const mid = list.length >> 1;
2132
+ const left = mergeSort(list.slice(0, mid), compare);
2133
+ const right = mergeSort(list.slice(mid), compare);
2134
+ const out = [];
2135
+ let i = 0;
2136
+ let j = 0;
2137
+ while (i < left.length && j < right.length) if (compare(left[i], right[j]) > 0) out.push(right[j++]);
2138
+ else out.push(left[i++]);
2139
+ while (i < left.length) out.push(left[i++]);
2140
+ while (j < right.length) out.push(right[j++]);
2141
+ return out;
2142
+ }
2143
+ /** This function gives the lane order of a run: the merge sort of `start` with the relation `compare`. It tells the observer of `observeLanes`. */
2144
+ function orderRun(start, compare) {
2145
+ return mergeSort(start, compare);
2146
+ }
2147
+ /**
2148
+ * This function gives the lane order of the placed routes of a run. `order` selects the relation.
2149
+ * With `run`, the sort starts from the order of `placed`. With `bundle`, it starts from the order of the edges, so that each run sorts the same edges the same way.
2150
+ */
2151
+ function orderPlaced(placed, order) {
2152
+ return order === "run" ? orderRun(placed, byRun) : orderRun([...placed].sort(byEdge$2), byBundle);
2153
+ }
2046
2154
  // src/segments.ts
2047
2155
  const SAME$3 = 1e-6;
2048
2156
  /** This function gives the index where `p` holds the points of `part` in a row, from `from` on, or -1. */
@@ -2223,6 +2331,7 @@ function laneRoutes(source, open, laneWidth, maxWidth = Infinity) {
2223
2331
  /** This function gives the route of each path, with the lanes of each segment at the spacing that `spacingOf` gives. */
2224
2332
  function lanePaths(graph, spacingOf) {
2225
2333
  const byId = new Map(graph.segments.map((s) => [s.id, s]));
2334
+ const spacings = /* @__PURE__ */ new Map();
2226
2335
  const routes = [];
2227
2336
  for (const path of graph.paths) {
2228
2337
  const ends = pathPoints(graph, path);
@@ -2230,7 +2339,8 @@ function lanePaths(graph, spacingOf) {
2230
2339
  for (const ref of path.segments) {
2231
2340
  const s = byId.get(ref.segment);
2232
2341
  if (!s) continue;
2233
- const spacing = spacingOf(s);
2342
+ let spacing = spacings.get(s);
2343
+ if (spacing === void 0) spacings.set(s, spacing = spacingOf(s));
2234
2344
  const moved = offsetPolyline(s.points, ((s.count - 1) / 2 - ref.lane) * spacing);
2235
2345
  for (const q of ref.reverse ? moved.reverse() : moved) if (out.length === 0 || dist(q, out[out.length - 1]) > SAME$3) out.push(q);
2236
2346
  }
@@ -2245,51 +2355,6 @@ function lanePaths(graph, spacingOf) {
2245
2355
  }
2246
2356
  return routes;
2247
2357
  }
2248
- /** The angle, from -π to π, of the turn at `base` from the direction `from` → `base` to the point `to`. */
2249
- function turn$1(from, base, to) {
2250
- const dx = base.x - from.x;
2251
- const dy = base.y - from.y;
2252
- const vx = to.x - base.x;
2253
- const vy = to.y - base.y;
2254
- return Math.atan2(dx * vy - dy * vx, dx * vx + dy * vy);
2255
- }
2256
- /** This function gives the place where the routes `P` and `Q` part after the last point of the run, or nothing. */
2257
- function partAfter(P, Q) {
2258
- for (let j = 1;; j++) {
2259
- const a = P.p[P.e + j];
2260
- const b = Q.p[Q.e + j];
2261
- if (a === void 0 || b === void 0) return void 0;
2262
- if (dist(a, b) > SAME$3) {
2263
- const from = P.p[P.e + j - 2];
2264
- const at = P.p[P.e + j - 1];
2265
- const side = turn$1(from, at, a) - turn$1(from, at, b);
2266
- return side === 0 ? void 0 : {
2267
- at,
2268
- side
2269
- };
2270
- }
2271
- }
2272
- }
2273
- /** This function gives the place where the routes `P` and `Q` part before the first point of the run, or nothing. */
2274
- function partBefore(P, Q) {
2275
- for (let j = 1;; j++) {
2276
- const a = P.p[P.s - j];
2277
- const b = Q.p[Q.s - j];
2278
- if (a === void 0 || b === void 0) return void 0;
2279
- if (dist(a, b) > SAME$3) {
2280
- const from = P.p[P.s - j + 2];
2281
- const at = P.p[P.s - j + 1];
2282
- const side = turn$1(from, at, b) - turn$1(from, at, a);
2283
- return side === 0 ? void 0 : {
2284
- at,
2285
- side
2286
- };
2287
- }
2288
- }
2289
- }
2290
- const byEdge$2 = (P, Q) => P.edge < Q.edge ? -1 : P.edge > Q.edge ? 1 : 0;
2291
- /** This function puts the point `a` before the point `b`: the smaller x first, then the smaller y. */
2292
- const firstPoint = (a, b) => a.x < b.x - SAME$3 || Math.abs(a.x - b.x) <= SAME$3 && a.y < b.y;
2293
2358
  /**
2294
2359
  * This function gives the lane order of the edges on a shared way `run` (metro-line order).
2295
2360
  * The routes are polylines that hold the points of the run in a row, in either direction. A route that does not hold the run goes last.
@@ -2300,6 +2365,11 @@ const firstPoint = (a, b) => a.x < b.x - SAME$3 || Math.abs(a.x - b.x) <= SAME$3
2300
2365
  * With the order `bundle`, two routes that part on one side at both ends keep that side.
2301
2366
  * When the two ends disagree, the two routes must cross. The end with the smaller x, then the smaller y, tells the side.
2302
2367
  * Thus each shared way of a common stretch gives two routes the same order, and the crossing is where they part.
2368
+ *
2369
+ * This relation of two lanes is not transitive, so three lanes can make a cycle. The sort of the engine then gives another order on another engine.
2370
+ * Thus the function sorts with a merge sort of the package (LANE-ORDER). The order is the same on each JavaScript engine.
2371
+ * On a run with no cycle, the order is the order of each correct sort, for each order of `routes`.
2372
+ * On a run with a cycle, the order depends on the order of `routes`. The item LANE-STRETCH is to remove the cycles.
2303
2373
  */
2304
2374
  function orderLanes(run, routes, order = "run") {
2305
2375
  const placed = [];
@@ -2324,17 +2394,7 @@ function orderLanes(run, routes, order = "run") {
2324
2394
  else lost.push(r.edge);
2325
2395
  }
2326
2396
  }
2327
- if (order === "run") placed.sort((P, Q) => partAfter(P, Q)?.side ?? partBefore(P, Q)?.side ?? byEdge$2(P, Q));
2328
- else {
2329
- placed.sort(byEdge$2);
2330
- placed.sort((P, Q) => {
2331
- const after = partAfter(P, Q);
2332
- const before = partBefore(P, Q);
2333
- if (after && before && Math.sign(after.side) !== Math.sign(before.side)) return firstPoint(after.at, before.at) ? after.side : before.side;
2334
- return after?.side ?? before?.side ?? byEdge$2(P, Q);
2335
- });
2336
- }
2337
- return [...placed.map((x) => x.edge), ...lost.sort()];
2397
+ return [...orderPlaced(placed, order).map((x) => x.edge), ...lost.sort()];
2338
2398
  }
2339
2399
  // src/curves/highway/index.ts
2340
2400
  /** A turn smaller than this angle, in radians, is no corner. A turn nearer to a half turn than this angle stays sharp. */
@@ -6123,7 +6183,6 @@ function snellMerges(ways, firstId, clear, delta) {
6123
6183
  }
6124
6184
  }
6125
6185
  // src/routers/shared.ts
6126
- const byText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
6127
6186
  /** The angle, from -π to π, of the turn at `base` from the direction `from` → `base` to the point `to`. */
6128
6187
  function turn(from, base, to) {
6129
6188
  const dx = base.x - from.x;
@@ -6268,31 +6327,42 @@ function sharedRouter(options) {
6268
6327
  const pad = ways.margin / 2;
6269
6328
  snellMerges(order, corners.points.length + ends.size, (a, b) => ways.obstacles.clear(a, b, pad), (f) => Math.min(.95, share(f)));
6270
6329
  }
6330
+ const N = corners.points.length + ends.size + 1;
6331
+ const pairOf = (a, b) => a < b ? a * N + b : b * N + a;
6271
6332
  const users = /* @__PURE__ */ new Map();
6272
6333
  for (const w of order) {
6273
6334
  if (!w.found) continue;
6274
6335
  let mine = users.get(w.key);
6275
6336
  if (!mine) users.set(w.key, mine = /* @__PURE__ */ new Map());
6276
6337
  for (let i = 1; i < w.ids.length; i++) {
6277
- const a = w.ids[i - 1];
6278
- const b = w.ids[i];
6279
- const k = a < b ? `${a}:${b}` : `${b}:${a}`;
6338
+ const k = pairOf(w.ids[i - 1], w.ids[i]);
6280
6339
  const list = mine.get(k);
6281
6340
  if (list) list.push(w.edge.id);
6282
6341
  else mine.set(k, [w.edge.id]);
6283
6342
  }
6284
6343
  }
6344
+ const texts = /* @__PURE__ */ new Map();
6345
+ const sigs = /* @__PURE__ */ new Map();
6346
+ for (const [key, mine] of users) {
6347
+ const own = /* @__PURE__ */ new Map();
6348
+ for (const [k, list] of mine) {
6349
+ if (list.length < 2) {
6350
+ own.set(k, 0);
6351
+ continue;
6352
+ }
6353
+ const text = [...list].sort(byText).join("");
6354
+ let n = texts.get(text);
6355
+ if (n === void 0) texts.set(text, n = texts.size + 1);
6356
+ own.set(k, n);
6357
+ }
6358
+ sigs.set(key, own);
6359
+ }
6285
6360
  const runs = /* @__PURE__ */ new Map();
6286
6361
  const points = /* @__PURE__ */ new Map();
6287
6362
  for (const w of all) {
6288
6363
  if (!w.found) continue;
6289
- const used = users.get(w.key);
6290
- const sig = (i) => {
6291
- const a = w.ids[i];
6292
- const b = w.ids[i + 1];
6293
- const list = used.get(a < b ? `${a}:${b}` : `${b}:${a}`);
6294
- return list.length < 2 ? "" : [...list].sort(byText).join("");
6295
- };
6364
+ const own = sigs.get(w.key);
6365
+ const sig = (i) => own.get(pairOf(w.ids[i], w.ids[i + 1]));
6296
6366
  const out = [w.ps.point];
6297
6367
  const append = (piece) => {
6298
6368
  for (const p of piece) if (dist(p, out[out.length - 1]) > 1e-9) out.push(p);
@@ -6302,7 +6372,7 @@ function sharedRouter(options) {
6302
6372
  const s = sig(i);
6303
6373
  if (i + 1 < w.seq.length - 1 && sig(i + 1) === s) continue;
6304
6374
  const raw = w.seq.slice(start, i + 2);
6305
- if (s === "") append(corners.pull(raw));
6375
+ if (s === 0) append(corners.pull(raw));
6306
6376
  else {
6307
6377
  const a = w.ids[start];
6308
6378
  const b = w.ids[i + 1];
@@ -6428,7 +6498,7 @@ function laneOrderOfRun(run) {
6428
6498
  }
6429
6499
  return byText(p.edge.id, q.edge.id);
6430
6500
  };
6431
- return [...run.ways].sort(compare).map((w) => w.edge.id);
6501
+ return orderRun(run.ways, compare).map((w) => w.edge.id);
6432
6502
  }
6433
6503
  // src/routers/straight.ts
6434
6504
  /** This function gives the point where the ray from the center of the box to `toward` leaves the box. */
@@ -9460,14 +9530,28 @@ function sharedStretches(ways, o) {
9460
9530
  }
9461
9531
  const stretches = /* @__PURE__ */ new Map();
9462
9532
  const wayOf = /* @__PURE__ */ new Map();
9533
+ const texts = /* @__PURE__ */ new Map();
9534
+ const sigs = /* @__PURE__ */ new Map();
9535
+ const sigOf = (k) => {
9536
+ let n = sigs.get(k);
9537
+ if (n !== void 0) return n;
9538
+ const list = users.get(k) ?? [];
9539
+ if (list.length < min) n = 0;
9540
+ else {
9541
+ const text = [...list].sort().join("");
9542
+ n = texts.get(text);
9543
+ if (n === void 0) texts.set(text, n = texts.size + 1);
9544
+ }
9545
+ sigs.set(k, n);
9546
+ return n;
9547
+ };
9463
9548
  for (const w of clean) {
9464
9549
  wayOf.set(w.edge, w.points);
9465
9550
  const sig = (i) => {
9466
9551
  const a = w.points[i];
9467
9552
  const b = w.points[i + 1];
9468
- if (!usable(a, b)) return "";
9469
- const list = users.get(`${w.key}\u0000${linkKey(pointKey(a), pointKey(b))}`) ?? [];
9470
- return list.length < min ? "" : [...list].sort().join("");
9553
+ if (!usable(a, b)) return 0;
9554
+ return sigOf(`${w.key}\u0000${linkKey(pointKey(a), pointKey(b))}`);
9471
9555
  };
9472
9556
  let start = 0;
9473
9557
  for (let i = 0; i < w.points.length - 1; i++) {
@@ -9475,7 +9559,7 @@ function sharedStretches(ways, o) {
9475
9559
  if (i + 1 < w.points.length - 1 && sig(i + 1) === s) continue;
9476
9560
  const from = start;
9477
9561
  start = i + 1;
9478
- if (s === "") continue;
9562
+ if (s === 0) continue;
9479
9563
  const a = pointKey(w.points[from]);
9480
9564
  const b = pointKey(w.points[i + 1]);
9481
9565
  if (a === b) continue;
@@ -9485,13 +9569,17 @@ function sharedStretches(ways, o) {
9485
9569
  if (!found) stretches.set(id, found = {
9486
9570
  key: w.key,
9487
9571
  points: a < b ? piece : [...piece].reverse(),
9488
- members: []
9489
- });
9490
- if (!found.members.some((m) => m.edge === w.edge)) found.members.push({
9491
- edge: w.edge,
9492
- forward: a < b,
9493
- at: from
9572
+ members: [],
9573
+ seen: /* @__PURE__ */ new Set()
9494
9574
  });
9575
+ if (!found.seen.has(w.edge)) {
9576
+ found.seen.add(w.edge);
9577
+ found.members.push({
9578
+ edge: w.edge,
9579
+ forward: a < b,
9580
+ at: from
9581
+ });
9582
+ }
9495
9583
  }
9496
9584
  }
9497
9585
  const kept = [...stretches.values()].filter((s) => s.members.length >= min);
@@ -22999,11 +23087,6 @@ function round3(value) {
22999
23087
  return r === 0 ? 0 : r;
23000
23088
  }
23001
23089
  // src/order/index.ts
23002
- /** This function compares two edges by the hash of their ids, and then by their ids. Thus the order of two edges does not depend on the input. */
23003
- function byHash(a, b) {
23004
- if (a.h !== b.h) return a.h - b.h;
23005
- return a.e.id < b.e.id ? -1 : a.e.id > b.e.id ? 1 : 0;
23006
- }
23007
23090
  /**
23008
23091
  * This function gives the graph with its edges in the stable order of WILD-053: by the FNV-1a hash of the id, then by the id.
23009
23092
  * The nodes and the edges do not change. Two orders of the same edges give the same graph.
@@ -26995,9 +27078,12 @@ function distancesTo(points, adj, start, targets) {
26995
27078
  const done = new Uint8Array(adj.length);
26996
27079
  const goal = new Set(targets);
26997
27080
  const ends = targets.map((t) => points[t]);
27081
+ const guides = new Float64Array(adj.length).fill(-1);
26998
27082
  const guide = (v) => {
27083
+ if (guides[v] >= 0) return guides[v];
26999
27084
  let h = Infinity;
27000
27085
  for (const t of ends) h = Math.min(h, dist(points[v], t));
27086
+ guides[v] = h;
27001
27087
  return h;
27002
27088
  };
27003
27089
  const heap = new NodeHeap();