@mark1russell7/highways 0.8.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.d.ts.map +1 -1
- package/build/index.js +65 -26
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/exact/index.ts +4 -0
- package/src/bench/properties.ts +8 -2
- package/src/highways/stretches.ts +28 -7
- package/src/lane-order.ts +5 -2
- package/src/routers/shared.ts +25 -12
- package/src/segments.ts +4 -1
package/package.json
CHANGED
package/src/bench/exact/index.ts
CHANGED
|
@@ -283,9 +283,13 @@ function distancesTo(points: readonly Point[], adj: readonly (readonly { to: num
|
|
|
283
283
|
const done = new Uint8Array(adj.length);
|
|
284
284
|
const goal = new Set(targets);
|
|
285
285
|
const ends = targets.map((t) => points[t]!);
|
|
286
|
+
// A node can go in the heap more than one time. Each node gets its guide one time (ROUTE-SCALE), so a search to many targets stays fast.
|
|
287
|
+
const guides = new Float64Array(adj.length).fill(-1);
|
|
286
288
|
const guide = (v: number): number => {
|
|
289
|
+
if (guides[v]! >= 0) return guides[v]!;
|
|
287
290
|
let h = Infinity;
|
|
288
291
|
for (const t of ends) h = Math.min(h, dist(points[v]!, t));
|
|
292
|
+
guides[v] = h;
|
|
289
293
|
return h;
|
|
290
294
|
};
|
|
291
295
|
const heap = new NodeHeap();
|
package/src/bench/properties.ts
CHANGED
|
@@ -83,9 +83,11 @@ export function checkProperties(graph: Graph, result: Result, limit = 100): Viol
|
|
|
83
83
|
for (const run of result.runs ?? []) {
|
|
84
84
|
if (run.edges.length < 2)
|
|
85
85
|
if (add({ rule: "run-shared", detail: `run ${run.id} has fewer than two edges` })) return out;
|
|
86
|
+
// The run gets its reverse one time, not one time for each edge (ROUTE-SCALE).
|
|
87
|
+
let back: readonly Point[] | undefined;
|
|
86
88
|
for (const id of run.edges) {
|
|
87
89
|
const r = routes.get(id);
|
|
88
|
-
if (!r || !(holds(r.points, run.points) || holds(r.points, [...run.points].reverse())))
|
|
90
|
+
if (!r || !(holds(r.points, run.points) || holds(r.points, (back ??= [...run.points].reverse()))))
|
|
89
91
|
if (add({ rule: "run-shared", edge: id, detail: `run ${run.id}: the route does not hold the points of the run in a row` })) return out;
|
|
90
92
|
}
|
|
91
93
|
}
|
|
@@ -139,6 +141,10 @@ function onBoundary(p: Point, b: Box): boolean {
|
|
|
139
141
|
/** This function tells if the polyline `p` holds the points of `part` in a row. */
|
|
140
142
|
function holds(p: readonly Point[], part: readonly Point[]): boolean {
|
|
141
143
|
if (part.length === 0) return true;
|
|
142
|
-
for (let i = 0; i + part.length <= p.length; i++)
|
|
144
|
+
for (let i = 0; i + part.length <= p.length; i++) {
|
|
145
|
+
let j = 0;
|
|
146
|
+
while (j < part.length && dist(part[j]!, p[i + j]!) <= TOLERANCE) j++;
|
|
147
|
+
if (j === part.length) return true;
|
|
148
|
+
}
|
|
143
149
|
return false;
|
|
144
150
|
}
|
|
@@ -77,17 +77,35 @@ export function sharedStretches(ways: readonly KeyedWay[], o: Readonly<SharedStr
|
|
|
77
77
|
readonly key: string;
|
|
78
78
|
readonly points: readonly Point[];
|
|
79
79
|
readonly members: { edge: string; forward: boolean; at: number }[];
|
|
80
|
+
/** The edges of `members`, so that the check of a member does not scan the list. */
|
|
81
|
+
readonly seen: Set<string>;
|
|
80
82
|
}
|
|
81
83
|
const stretches = new Map<string, Stretch>();
|
|
82
84
|
const wayOf = new Map<string, readonly Point[]>();
|
|
85
|
+
// The signature of each link (ROUTE-SCALE): 0 for a link with fewer than `min` ways, else the number of the sorted list of its ways.
|
|
86
|
+
// Each link gets its signature one time. Two links get the same number only when they have the same sorted list, so the stretches do not change.
|
|
87
|
+
const texts = new Map<string, number>();
|
|
88
|
+
const sigs = new Map<string, number>();
|
|
89
|
+
const sigOf = (k: string): number => {
|
|
90
|
+
let n = sigs.get(k);
|
|
91
|
+
if (n !== undefined) return n;
|
|
92
|
+
const list = users.get(k) ?? [];
|
|
93
|
+
if (list.length < min) n = 0;
|
|
94
|
+
else {
|
|
95
|
+
const text = [...list].sort().join("\u0001");
|
|
96
|
+
n = texts.get(text);
|
|
97
|
+
if (n === undefined) texts.set(text, (n = texts.size + 1));
|
|
98
|
+
}
|
|
99
|
+
sigs.set(k, n);
|
|
100
|
+
return n;
|
|
101
|
+
};
|
|
83
102
|
for (const w of clean) {
|
|
84
103
|
wayOf.set(w.edge, w.points);
|
|
85
|
-
const sig = (i: number):
|
|
104
|
+
const sig = (i: number): number => {
|
|
86
105
|
const a = w.points[i]!;
|
|
87
106
|
const b = w.points[i + 1]!;
|
|
88
|
-
if (!usable(a, b)) return
|
|
89
|
-
|
|
90
|
-
return list.length < min ? "" : [...list].sort().join("\u0001");
|
|
107
|
+
if (!usable(a, b)) return 0;
|
|
108
|
+
return sigOf(`${w.key}\u0000${linkKey(pointKey(a), pointKey(b))}`);
|
|
91
109
|
};
|
|
92
110
|
let start = 0;
|
|
93
111
|
for (let i = 0; i < w.points.length - 1; i++) {
|
|
@@ -95,15 +113,18 @@ export function sharedStretches(ways: readonly KeyedWay[], o: Readonly<SharedStr
|
|
|
95
113
|
if (i + 1 < w.points.length - 1 && sig(i + 1) === s) continue;
|
|
96
114
|
const from = start;
|
|
97
115
|
start = i + 1;
|
|
98
|
-
if (s ===
|
|
116
|
+
if (s === 0) continue;
|
|
99
117
|
const a = pointKey(w.points[from]!);
|
|
100
118
|
const b = pointKey(w.points[i + 1]!);
|
|
101
119
|
if (a === b) continue;
|
|
102
120
|
const id = `${w.key}\u0000${s}\u0000${linkKey(a, b)}`;
|
|
103
121
|
const piece = w.points.slice(from, i + 2);
|
|
104
122
|
let found = stretches.get(id);
|
|
105
|
-
if (!found) stretches.set(id, (found = { key: w.key, points: a < b ? piece : [...piece].reverse(), members: [] }));
|
|
106
|
-
if (!found.
|
|
123
|
+
if (!found) stretches.set(id, (found = { key: w.key, points: a < b ? piece : [...piece].reverse(), members: [], seen: new Set() }));
|
|
124
|
+
if (!found.seen.has(w.edge)) {
|
|
125
|
+
found.seen.add(w.edge);
|
|
126
|
+
found.members.push({ edge: w.edge, forward: a < b, at: from });
|
|
127
|
+
}
|
|
107
128
|
}
|
|
108
129
|
}
|
|
109
130
|
// The order of the stretches of each way gives the `seq` of its members.
|
package/src/lane-order.ts
CHANGED
|
@@ -31,13 +31,16 @@ function turn(from: Point, base: Point, to: Point): number {
|
|
|
31
31
|
return Math.atan2(dx * vy - dy * vx, dx * vx + dy * vy);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// The routes of one run often hold the same point objects past the run. A point is at the distance 0 from itself,
|
|
35
|
+
// so the check `a !== b` skips the distance and keeps each result (ROUTE-SCALE).
|
|
36
|
+
|
|
34
37
|
/** This function gives the place where the routes `P` and `Q` part after the last point of the run, or nothing. */
|
|
35
38
|
function partAfter(P: Placed, Q: Placed): Parting | undefined {
|
|
36
39
|
for (let j = 1; ; j++) {
|
|
37
40
|
const a = P.p[P.e + j];
|
|
38
41
|
const b = Q.p[Q.e + j];
|
|
39
42
|
if (a === undefined || b === undefined) return undefined;
|
|
40
|
-
if (dist(a, b) > SAME) {
|
|
43
|
+
if (a !== b && dist(a, b) > SAME) {
|
|
41
44
|
const from = P.p[P.e + j - 2]!;
|
|
42
45
|
const at = P.p[P.e + j - 1]!;
|
|
43
46
|
const side = turn(from, at, a) - turn(from, at, b);
|
|
@@ -52,7 +55,7 @@ function partBefore(P: Placed, Q: Placed): Parting | undefined {
|
|
|
52
55
|
const a = P.p[P.s - j];
|
|
53
56
|
const b = Q.p[Q.s - j];
|
|
54
57
|
if (a === undefined || b === undefined) return undefined;
|
|
55
|
-
if (dist(a, b) > SAME) {
|
|
58
|
+
if (a !== b && dist(a, b) > SAME) {
|
|
56
59
|
const from = P.p[P.s - j + 2]!;
|
|
57
60
|
const at = P.p[P.s - j + 1]!;
|
|
58
61
|
// Seen backward, the left side is the right side. Thus the order turns around.
|
package/src/routers/shared.ts
CHANGED
|
@@ -238,20 +238,38 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
// The users of each link, with the links to the ends. Two edges from one plug share the link from it.
|
|
241
|
-
const
|
|
241
|
+
const N = corners.points.length + ends.size + 1;
|
|
242
|
+
const pairOf = (a: number, b: number): number => (a < b ? a * N + b : b * N + a);
|
|
243
|
+
const users = new Map<string, Map<number, string[]>>();
|
|
242
244
|
for (const w of order) {
|
|
243
245
|
if (!w.found) continue;
|
|
244
246
|
let mine = users.get(w.key);
|
|
245
247
|
if (!mine) users.set(w.key, (mine = new Map()));
|
|
246
248
|
for (let i = 1; i < w.ids.length; i++) {
|
|
247
|
-
const
|
|
248
|
-
const b = w.ids[i]!;
|
|
249
|
-
const k = a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
249
|
+
const k = pairOf(w.ids[i - 1]!, w.ids[i]!);
|
|
250
250
|
const list = mine.get(k);
|
|
251
251
|
if (list) list.push(w.edge.id);
|
|
252
252
|
else mine.set(k, [w.edge.id]);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
+
// The signature of each link (ROUTE-SCALE): 0 for a link with fewer than 2 users, else the number of the sorted list of its users.
|
|
256
|
+
// Each link gets its signature one time. Two links get the same number only when they have the same sorted list, so the runs do not change.
|
|
257
|
+
const texts = new Map<string, number>();
|
|
258
|
+
const sigs = new Map<string, Map<number, number>>();
|
|
259
|
+
for (const [key, mine] of users) {
|
|
260
|
+
const own = new Map<number, number>();
|
|
261
|
+
for (const [k, list] of mine) {
|
|
262
|
+
if (list.length < 2) {
|
|
263
|
+
own.set(k, 0);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const text = [...list].sort(byText).join("\u0001");
|
|
267
|
+
let n = texts.get(text);
|
|
268
|
+
if (n === undefined) texts.set(text, (n = texts.size + 1));
|
|
269
|
+
own.set(k, n);
|
|
270
|
+
}
|
|
271
|
+
sigs.set(key, own);
|
|
272
|
+
}
|
|
255
273
|
|
|
256
274
|
// Each way breaks into stretches where the set of edges on the links stays the same.
|
|
257
275
|
// A shared stretch loses its needless corners one time, so it has the same points in each of its routes.
|
|
@@ -259,13 +277,8 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
259
277
|
const points = new Map<string, Point[]>();
|
|
260
278
|
for (const w of all) {
|
|
261
279
|
if (!w.found) continue;
|
|
262
|
-
const
|
|
263
|
-
const sig = (i: number):
|
|
264
|
-
const a = w.ids[i]!;
|
|
265
|
-
const b = w.ids[i + 1]!;
|
|
266
|
-
const list = used.get(a < b ? `${a}:${b}` : `${b}:${a}`)!;
|
|
267
|
-
return list.length < 2 ? "" : [...list].sort(byText).join("\u0001");
|
|
268
|
-
};
|
|
280
|
+
const own = sigs.get(w.key)!;
|
|
281
|
+
const sig = (i: number): number => own.get(pairOf(w.ids[i]!, w.ids[i + 1]!))!;
|
|
269
282
|
const out: Point[] = [w.ps.point];
|
|
270
283
|
const append = (piece: readonly Point[]): void => {
|
|
271
284
|
for (const p of piece) if (dist(p, out[out.length - 1]!) > 1e-9) out.push(p);
|
|
@@ -275,7 +288,7 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
275
288
|
const s = sig(i);
|
|
276
289
|
if (i + 1 < w.seq.length - 1 && sig(i + 1) === s) continue;
|
|
277
290
|
const raw = w.seq.slice(start, i + 2);
|
|
278
|
-
if (s ===
|
|
291
|
+
if (s === 0) append(corners.pull(raw));
|
|
279
292
|
else {
|
|
280
293
|
const a = w.ids[start]!;
|
|
281
294
|
const b = w.ids[i + 1]!;
|
package/src/segments.ts
CHANGED
|
@@ -174,6 +174,8 @@ export function laneRoutes(source: Result | SegmentGraph, open: ReadonlySet<stri
|
|
|
174
174
|
/** This function gives the route of each path, with the lanes of each segment at the spacing that `spacingOf` gives. */
|
|
175
175
|
function lanePaths(graph: SegmentGraph, spacingOf: (s: Segment) => number): Route[] {
|
|
176
176
|
const byId = new Map(graph.segments.map((s) => [s.id, s]));
|
|
177
|
+
// Each segment gets its spacing one time (ROUTE-SCALE). A key of `runKey` sorts the edges of the segment, and a busy segment is on many paths.
|
|
178
|
+
const spacings = new Map<Segment, number>();
|
|
177
179
|
const routes: Route[] = [];
|
|
178
180
|
for (const path of graph.paths) {
|
|
179
181
|
const ends = pathPoints(graph, path);
|
|
@@ -181,7 +183,8 @@ function lanePaths(graph: SegmentGraph, spacingOf: (s: Segment) => number): Rout
|
|
|
181
183
|
for (const ref of path.segments) {
|
|
182
184
|
const s = byId.get(ref.segment);
|
|
183
185
|
if (!s) continue;
|
|
184
|
-
|
|
186
|
+
let spacing = spacings.get(s);
|
|
187
|
+
if (spacing === undefined) spacings.set(s, (spacing = spacingOf(s)));
|
|
185
188
|
const moved = offsetPolyline(s.points, ((s.count - 1) / 2 - ref.lane) * spacing);
|
|
186
189
|
for (const q of ref.reverse ? moved.reverse() : moved) if (out.length === 0 || dist(q, out[out.length - 1]!) > SAME) out.push(q);
|
|
187
190
|
}
|