@mark1russell7/highways 0.8.1 → 0.9.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 +74 -8
- package/build/index.d.ts +705 -183
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4079 -2639
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/bench/compare.ts +174 -5
- package/src/bench/room.ts +1 -1
- package/src/bench/run.ts +38 -3
- package/src/bench/screen/generators.ts +11 -41
- package/src/bench/screen/screen.ts +16 -10
- package/src/bench/screen/set.ts +156 -49
- package/src/bench/suite.ts +110 -12
- package/src/cases.ts +64 -18
- package/src/families/cases.ts +108 -0
- package/src/families/fixtures.ts +101 -0
- package/src/families/index.ts +36 -0
- package/src/families/model.ts +55 -0
- package/src/fixtures.ts +8 -2
- package/src/highways/soft-keys/index.ts +8 -4
- package/src/index.ts +8 -0
- package/src/lane-order.ts +37 -0
- package/src/lane-orders/stretch/index.ts +486 -0
- package/src/lane-orders/stretch/router.ts +22 -0
- package/src/levels/depths/index.ts +57 -0
- package/src/plugs/facing/index.ts +22 -0
- package/src/portfolio/router.ts +44 -0
- package/src/routers/corners.ts +27 -3
- package/src/routers/sections.ts +22 -40
- package/src/routers/shared.ts +26 -7
- package/src/routers/trunks.ts +21 -3
- package/src/segments.ts +4 -25
- package/src/strategies/builtins.ts +54 -2
- package/src/strategies/model.ts +55 -10
- package/src/strategies/options.ts +36 -4
- package/src/strategies/schemas.ts +68 -5
- package/src/whole/pipeline/index.ts +66 -0
package/src/routers/corners.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { nodeIndex, type Edge, type Graph, type Node, type Result, type Route, t
|
|
|
3
3
|
import { placePlug, type Plug } from "../plugs.ts";
|
|
4
4
|
import { justifyPlugs } from "../plugs/justify.ts";
|
|
5
5
|
import { sidePlugs } from "../plugs/side.ts";
|
|
6
|
+
import type { PlugPair, Strategy } from "../strategies/model.ts";
|
|
6
7
|
import { WayFinder, type WayOptions } from "../ways.ts";
|
|
7
8
|
|
|
8
9
|
/** The ways to place a free plug of the corner router. */
|
|
@@ -24,6 +25,13 @@ export interface CornersOptions extends WayOptions {
|
|
|
24
25
|
* Above 0, the free plugs of each side keep their order and move the least distance (`justifyPlugs`, WILD-002).
|
|
25
26
|
*/
|
|
26
27
|
readonly plugGap?: number;
|
|
28
|
+
/**
|
|
29
|
+
* The plug rule: a strategy of the step `plugs` that places the plugs of each edge. It takes the place of the option `plugs`.
|
|
30
|
+
* Without it, the router places the plugs with the option `plugs`. An edge without plugs from the strategy gets the plugs of `facing`.
|
|
31
|
+
*/
|
|
32
|
+
readonly plug?: Strategy<"plugs">;
|
|
33
|
+
/** The options of the strategy `plug`. The strategy gets its `defaults` for each option that this object does not give. */
|
|
34
|
+
readonly plugOptions?: object;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -38,7 +46,7 @@ export function cornersRouter(options: CornersOptions): Router {
|
|
|
38
46
|
const byId = nodeIndex(graph);
|
|
39
47
|
const ways = new WayFinder(graph.nodes, options);
|
|
40
48
|
const routes: Route[] = [];
|
|
41
|
-
if ((options.plugs ?? "facing") === "facing" && !((options.plugGap ?? 0) > 0)) {
|
|
49
|
+
if (!options.plug && (options.plugs ?? "facing") === "facing" && !((options.plugGap ?? 0) > 0)) {
|
|
42
50
|
for (const e of graph.edges) {
|
|
43
51
|
const s = byId.get(e.source.node);
|
|
44
52
|
const t = byId.get(e.target.node);
|
|
@@ -51,12 +59,17 @@ export function cornersRouter(options: CornersOptions): Router {
|
|
|
51
59
|
}
|
|
52
60
|
// First the plugs of each edge, then the gaps of the plugs of each side, then the ways between the plugs.
|
|
53
61
|
const ends: [Edge, Node, Node, Plug, Plug][] = [];
|
|
62
|
+
const placed = options.plug ? plugsByEdge(options.plug.run({ graph, ways }, options.plugOptions)) : undefined;
|
|
54
63
|
for (const e of graph.edges) {
|
|
55
64
|
const s = byId.get(e.source.node);
|
|
56
65
|
const t = byId.get(e.target.node);
|
|
57
66
|
if (!s || !t) continue;
|
|
58
|
-
const
|
|
59
|
-
|
|
67
|
+
const own = placed?.get(e.id)?.shift();
|
|
68
|
+
const [ps, pt]: [Plug, Plug] = own
|
|
69
|
+
? [own.source, own.target]
|
|
70
|
+
: options.plugs === "side" && !placed
|
|
71
|
+
? sidePlugs(ways, s, e.source, t, e.target)
|
|
72
|
+
: [placePlug(s, e.source, center(t.box)), placePlug(t, e.target, center(s.box))];
|
|
60
73
|
ends.push([e, s, t, ps, pt]);
|
|
61
74
|
}
|
|
62
75
|
if ((options.plugGap ?? 0) > 0) spacePlugs(ends, options.plugGap!);
|
|
@@ -67,6 +80,17 @@ export function cornersRouter(options: CornersOptions): Router {
|
|
|
67
80
|
return options.maxNodes === undefined ? router : { ...router, maxNodes: options.maxNodes };
|
|
68
81
|
}
|
|
69
82
|
|
|
83
|
+
/** This function gives the plugs of a plug strategy by the edge id. Two edges with one id take the entries of that id in order. */
|
|
84
|
+
function plugsByEdge(list: readonly PlugPair[]): Map<string, PlugPair[]> {
|
|
85
|
+
const out = new Map<string, PlugPair[]>();
|
|
86
|
+
for (const p of list) {
|
|
87
|
+
const own = out.get(p.edge);
|
|
88
|
+
if (own) own.push(p);
|
|
89
|
+
else out.set(p.edge, [p]);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
/**
|
|
71
95
|
* This function moves the free plugs of each side of each node, so that two of them are at least `gap` apart (`justifyPlugs`).
|
|
72
96
|
* The plugs keep their order on the side. Two plugs at one place take the order of their other ends. A fixed plug does not move.
|
package/src/routers/sections.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { BACKTRACK_MIN, goesOutAndBack } from "../bench/links.ts";
|
|
2
2
|
import { center, dist, pointSegmentDistance, polylineLength, type Point } from "../geom.ts";
|
|
3
|
-
import { LEVEL_LINK_DEFAULTS,
|
|
3
|
+
import { LEVEL_LINK_DEFAULTS, type LevelLinkOptions } from "../levels.ts";
|
|
4
|
+
import { depthLevels, type DepthLevelOptions } from "../levels/depths/index.ts";
|
|
4
5
|
import { nodeIndex, type Graph, type Level, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
5
6
|
import { placePlug } from "../plugs.ts";
|
|
6
|
-
import { allSections, buildSections, checkSections,
|
|
7
|
+
import { allSections, buildSections, checkSections, SECTION_DEFAULTS, type Section, type SectionOptions } from "../sections.ts";
|
|
7
8
|
import { orderLanes, type RunOrder } from "../segments.ts";
|
|
8
9
|
import type { Strategy } from "../strategies/model.ts";
|
|
9
10
|
import { WayFinder } from "../ways.ts";
|
|
@@ -45,6 +46,14 @@ export interface SectionsOptions extends LevelLinkOptions, SectionOptions {
|
|
|
45
46
|
* The default is each depth, from 0 to the depth of the deepest section.
|
|
46
47
|
*/
|
|
47
48
|
readonly levels?: readonly number[];
|
|
49
|
+
/**
|
|
50
|
+
* The level rule: a strategy of the step `levels` that makes the zoom levels from the tree and the routes.
|
|
51
|
+
* Without it, the router uses the strategy `depths` (`depthLevels`). The strategy gets the options `levels`, `margin`, `k`, `tightMargin`, `plugInset` and `throughCost` of the router,
|
|
52
|
+
* and then `levelOptions`.
|
|
53
|
+
*/
|
|
54
|
+
readonly level?: Strategy<"levels">;
|
|
55
|
+
/** The options of the strategy `level`. They go over the level options of the router. */
|
|
56
|
+
readonly levelOptions?: object;
|
|
48
57
|
/** The lane order of the runs. With `run`, the default, each run gets its order alone. With `bundle`, two edges keep one order on each run of their common stretch. */
|
|
49
58
|
readonly laneOrder?: RunOrder;
|
|
50
59
|
/** The benchmark skips a fixture with more nodes. */
|
|
@@ -210,7 +219,7 @@ export function sectionsRouter(options: SectionsOptions): Router {
|
|
|
210
219
|
if (list.length < 2 || p.length < 2) continue;
|
|
211
220
|
runs.push({ id: `r${runs.length}`, key, points: p, edges: orderLanes(p, list.map((id) => routeOf.get(id)!), options.laneOrder) });
|
|
212
221
|
}
|
|
213
|
-
return { routes, runs, levels: makeLevels(graph, root,
|
|
222
|
+
return { routes, runs, levels: makeLevels(graph, root, routes, options) };
|
|
214
223
|
},
|
|
215
224
|
};
|
|
216
225
|
return options.maxNodes === undefined ? router : { ...router, maxNodes: options.maxNodes };
|
|
@@ -224,43 +233,16 @@ function groupNodes(graph: Graph, options: SectionsOptions): Section {
|
|
|
224
233
|
return root;
|
|
225
234
|
}
|
|
226
235
|
|
|
236
|
+
/** The keys of the router options that the level rule gets. */
|
|
237
|
+
const LEVEL_KEYS = ["levels", "margin", "k", "tightMargin", "plugInset", "throughCost"] as const;
|
|
238
|
+
|
|
227
239
|
/**
|
|
228
|
-
* This function gives
|
|
229
|
-
*
|
|
230
|
-
* At depth d, a section of depth d is one item. A link joins two items and counts the edges between them.
|
|
231
|
-
* In a level with groups, a link goes around the boxes of the other items (`levelLinks`).
|
|
232
|
-
* In the last level, the points of a link are the route of its first edge.
|
|
240
|
+
* This function gives the zoom levels of the router: the levels of the strategy `level`, or else the levels of `depthLevels`.
|
|
241
|
+
* The strategy gets the level options of the router that have a value, and then `levelOptions`.
|
|
233
242
|
*/
|
|
234
|
-
function makeLevels(graph: Graph, root: Section,
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
for (let d = 0; d <= deepest + 1; d++) {
|
|
240
|
-
if (shown && d <= deepest && !shown.has(d)) continue;
|
|
241
|
-
const { items, owner } = levelCut(graph.nodes, root, d);
|
|
242
|
-
const links = new Map<string, { source: string; target: string; count: number; edge: string }>();
|
|
243
|
-
for (const e of graph.edges) {
|
|
244
|
-
const iu = indexOfNode.get(e.source.node);
|
|
245
|
-
const iv = indexOfNode.get(e.target.node);
|
|
246
|
-
if (iu === undefined || iv === undefined) continue;
|
|
247
|
-
const a = owner[iu]!;
|
|
248
|
-
const b = owner[iv]!;
|
|
249
|
-
if (a === b) continue;
|
|
250
|
-
const ida = items[a]!.id;
|
|
251
|
-
const idb = items[b]!.id;
|
|
252
|
-
const key = `${ida}\u0000${idb}`;
|
|
253
|
-
const link = links.get(key);
|
|
254
|
-
if (link) {
|
|
255
|
-
link.count++;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
links.set(key, { source: ida, target: idb, count: 1, edge: e.id });
|
|
259
|
-
}
|
|
260
|
-
const pairs: ItemPair[] = [...links.values()].map(({ source, target, count }) => ({ source, target, count }));
|
|
261
|
-
if (d > deepest)
|
|
262
|
-
levels.push({ depth: d, items, links: [...links.values()].map(({ edge, ...l }) => ({ ...l, points: routeOf.get(edge)?.points ?? [] })) });
|
|
263
|
-
else levels.push({ depth: d, items, links: levelLinks(items, pairs, options) });
|
|
264
|
-
}
|
|
265
|
-
return levels;
|
|
243
|
+
function makeLevels(graph: Graph, root: Section, routes: readonly Route[], options: SectionsOptions): Level[] {
|
|
244
|
+
if (!options.level) return depthLevels({ graph, root, routes }, options);
|
|
245
|
+
const own: Record<string, unknown> = {};
|
|
246
|
+
for (const key of LEVEL_KEYS) if (options[key] !== undefined) own[key] = options[key];
|
|
247
|
+
return options.level.run({ graph, root, routes }, { ...(own as DepthLevelOptions), ...options.levelOptions });
|
|
266
248
|
}
|
package/src/routers/shared.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { center, dist, type Point } from "../geom.ts";
|
|
|
2
2
|
import { nodeIndex, type Edge, type Graph, type GroupRule, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
3
3
|
import { channelWidth } from "../channels.ts";
|
|
4
4
|
import { orderRun } from "../lane-order.ts";
|
|
5
|
+
import { stretchLaneOrders, type LaneStretchOrder } from "../lane-orders/stretch/index.ts";
|
|
5
6
|
import { byText } from "../compare.ts";
|
|
6
7
|
import { placePlug, type Plug } from "../plugs.ts";
|
|
7
8
|
import { buildSections, type Section } from "../sections.ts";
|
|
8
9
|
import { orderLanes, type RunOrder } from "../segments.ts";
|
|
9
10
|
import { WayFinder, type WayOptions } from "../ways.ts";
|
|
10
11
|
import { snellMerges } from "./snell.ts";
|
|
12
|
+
import { groupRuleOf, type GroupRuleName } from "./trunks.ts";
|
|
11
13
|
|
|
12
14
|
/** The cost of a link that edges of one key share: a fixed discount, or the flow cost `len * f^alpha` (EDA-029). */
|
|
13
15
|
export type ShareCost = "fixed" | "flow";
|
|
@@ -22,12 +24,22 @@ export type ShareMerge = "corner" | "snell";
|
|
|
22
24
|
export interface SharedOptions extends WayOptions {
|
|
23
25
|
/** The name in the benchmark. */
|
|
24
26
|
readonly name: string;
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The edges with one key share their ways. The option takes a rule or the name of a built-in rule (`GROUP_RULE_NAMES`).
|
|
29
|
+
* Without a rule, or with `all`, all edges share. An edge without a key shares with no edge.
|
|
30
|
+
*/
|
|
31
|
+
readonly rule?: GroupRule | GroupRuleName;
|
|
27
32
|
/** The cost of a link that an edge with the same key uses already, as a share of its length. The default is 0.5. */
|
|
28
33
|
readonly discount?: number;
|
|
29
34
|
/** The lane order of the runs. With `bundle`, the default, two edges keep one order on each run of their common stretch. With `run`, each run gets its order alone. */
|
|
30
35
|
readonly laneOrder?: RunOrder;
|
|
36
|
+
/**
|
|
37
|
+
* The lane order of each stretch of runs (LANE-STRETCH). With `none`, the default, each run gets its order alone.
|
|
38
|
+
* With `consensus`, `periphery` or `sifting`, the runs that follow each other on the routes share one order. Only the lane order `bundle` reads it.
|
|
39
|
+
*/
|
|
40
|
+
readonly stretchOrder?: LaneStretchOrder;
|
|
41
|
+
/** The number of passes of the stretch order `sifting`, from 1 to 4. The default is 2. */
|
|
42
|
+
readonly siftPasses?: number;
|
|
31
43
|
/**
|
|
32
44
|
* The cost of a shared link. With `fixed`, the default, a link that the key uses already costs `discount` times its length.
|
|
33
45
|
* With `flow`, one more edge on a link with `f` edges costs `len * ((f + 1)^alpha - f^alpha)` (EDA-029).
|
|
@@ -113,9 +125,11 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
113
125
|
const reuseExponent = Math.max(0, options.reuseExponent ?? 1);
|
|
114
126
|
const capacity = Math.max(0, options.capacity ?? 0);
|
|
115
127
|
const laneWidth = Math.max(0, options.laneWidth ?? 4);
|
|
128
|
+
// The name `all` is the same as no rule.
|
|
129
|
+
const rule = options.rule === undefined || options.rule === "all" ? undefined : groupRuleOf(options.rule);
|
|
116
130
|
const keyOf = (e: Edge): string => {
|
|
117
|
-
if (!
|
|
118
|
-
return
|
|
131
|
+
if (!rule) return "*";
|
|
132
|
+
return rule(e) ?? `\u0000${e.id}`;
|
|
119
133
|
};
|
|
120
134
|
const router: Router = {
|
|
121
135
|
name: options.name,
|
|
@@ -311,10 +325,15 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
311
325
|
for (const w of all) routes.push({ edge: w.edge.id, points: points.get(w.edge.id) ?? [w.ps.point, w.pt.point] });
|
|
312
326
|
const shared: SharedRun[] = [];
|
|
313
327
|
const routeOf = new Map(routes.map((r) => [r.edge, r]));
|
|
314
|
-
|
|
315
|
-
|
|
328
|
+
const list = [...runs.values()];
|
|
329
|
+
const routesOf = (run: Run): Route[] => run.ways.map((w) => routeOf.get(w.edge.id)!);
|
|
330
|
+
// With a stretch order, the runs that follow each other on the routes share one lane order (LANE-STRETCH).
|
|
331
|
+
const method = options.laneOrder === "run" ? "none" : (options.stretchOrder ?? "none");
|
|
332
|
+
const stretch = method === "none" ? undefined : stretchLaneOrders(list.map((run) => ({ points: run.points, routes: routesOf(run) })), { method, passes: options.siftPasses ?? 2 });
|
|
333
|
+
list.forEach((run, i) => {
|
|
334
|
+
const edges = options.laneOrder === "run" ? laneOrderOfRun(run) : (stretch?.[i] ?? orderLanes(run.points, routesOf(run), "bundle"));
|
|
316
335
|
shared.push({ id: `r${shared.length}`, key: run.key, points: run.points, edges });
|
|
317
|
-
}
|
|
336
|
+
});
|
|
318
337
|
return { routes, runs: shared };
|
|
319
338
|
},
|
|
320
339
|
};
|
package/src/routers/trunks.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { balancedFan, fanPath, gilbertPoint } from "./fan.ts";
|
|
|
9
9
|
export interface TrunksOptions extends WayOptions {
|
|
10
10
|
/** The name in the benchmark. */
|
|
11
11
|
readonly name: string;
|
|
12
|
-
/** The grouping rule. The edges from one plug with one key share a trunk. */
|
|
13
|
-
readonly rule: GroupRule;
|
|
12
|
+
/** The grouping rule, or the name of a built-in rule (`GROUP_RULE_NAMES`). The edges from one plug with one key share a trunk. */
|
|
13
|
+
readonly rule: GroupRule | GroupRuleName;
|
|
14
14
|
/** The split point is at this share of the way to the center of the targets. The default is 0.5. */
|
|
15
15
|
readonly split?: number;
|
|
16
16
|
/** A group with fewer edges stays unbundled. The default is 2. */
|
|
@@ -55,6 +55,23 @@ export const byCollection: GroupRule = (e) => {
|
|
|
55
55
|
return typeof c === "string" ? c : undefined;
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
/** The names of the built-in grouping rules of the edges. With `collection`, the rule is `byCollection`. With `all`, each edge has one key. */
|
|
59
|
+
export type GroupRuleName = "collection" | "all";
|
|
60
|
+
|
|
61
|
+
/** The names of the built-in grouping rules, in the order of a control. */
|
|
62
|
+
export const GROUP_RULE_NAMES: readonly GroupRuleName[] = ["collection", "all"];
|
|
63
|
+
|
|
64
|
+
/** The rule `all`: each edge has the same key. */
|
|
65
|
+
const allEdges: GroupRule = () => "*";
|
|
66
|
+
|
|
67
|
+
/** This function gives the rule of a name of `GROUP_RULE_NAMES`. It gives a rule of the caller as it is. It throws a `RangeError` for an unknown name. */
|
|
68
|
+
export function groupRuleOf(rule: GroupRule | GroupRuleName): GroupRule {
|
|
69
|
+
if (typeof rule === "function") return rule;
|
|
70
|
+
if (rule === "collection") return byCollection;
|
|
71
|
+
if (rule === "all") return allEdges;
|
|
72
|
+
throw new RangeError(`There is no grouping rule "${String(rule)}". The names are ${GROUP_RULE_NAMES.join(" and ")}.`);
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
/**
|
|
59
76
|
* This function groups the edges by their source plug and their key. Each group keeps the order of the edges.
|
|
60
77
|
* A group with fewer than `minSize` edges does not appear.
|
|
@@ -189,6 +206,7 @@ export function sharedWays(routes: readonly Route[], from: number): { points: Po
|
|
|
189
206
|
* The edges outside the groups go as in the corner router.
|
|
190
207
|
*/
|
|
191
208
|
export function trunksRouter(options: TrunksOptions): Router {
|
|
209
|
+
const rule = groupRuleOf(options.rule);
|
|
192
210
|
const router: Router = {
|
|
193
211
|
name: options.name,
|
|
194
212
|
route(graph: Graph): Result {
|
|
@@ -200,7 +218,7 @@ export function trunksRouter(options: TrunksOptions): Router {
|
|
|
200
218
|
const part = (options.leave ?? "part") === "part";
|
|
201
219
|
// A branch leaves the trunk early only on a line that keeps out of the boxes as far as the corner graph does.
|
|
202
220
|
const clear = (a: Point, b: Point): boolean => ways.obstacles.clear(a, b, ways.margin / 2);
|
|
203
|
-
for (const [id, group] of groupEdges(graph.edges,
|
|
221
|
+
for (const [id, group] of groupEdges(graph.edges, rule, options.minSize ?? 2)) {
|
|
204
222
|
const first = group[0]!;
|
|
205
223
|
const s = byId.get(first.source.node);
|
|
206
224
|
const targets = group.map((e) => byId.get(e.target.node));
|
package/src/segments.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dist, type Point } from "./geom.ts";
|
|
2
|
-
import { orderPlaced,
|
|
2
|
+
import { findRun, orderPlaced, placeRoutes } from "./lane-order.ts";
|
|
3
3
|
import type { Result, Route } from "./model.ts";
|
|
4
4
|
|
|
5
5
|
/** A piece of drawn way. A shared segment carries several edges, in lane order. A private segment carries one edge. */
|
|
@@ -34,16 +34,6 @@ export interface SegmentGraph {
|
|
|
34
34
|
|
|
35
35
|
const SAME = 1e-6;
|
|
36
36
|
|
|
37
|
-
/** This function gives the index where `p` holds the points of `part` in a row, from `from` on, or -1. */
|
|
38
|
-
function indexOf(p: readonly Point[], part: readonly Point[], from: number): number {
|
|
39
|
-
for (let i = from; i + part.length <= p.length; i++) {
|
|
40
|
-
let ok = true;
|
|
41
|
-
for (let j = 0; ok && j < part.length; j++) ok = dist(p[i + j]!, part[j]!) <= SAME;
|
|
42
|
-
if (ok) return i;
|
|
43
|
-
}
|
|
44
|
-
return -1;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
/**
|
|
48
38
|
* This function breaks each route into segments. The runs and the trunks of the bundles become shared segments.
|
|
49
39
|
* The other parts of each route become private segments. The segments of a path, in order, give the points of its route again.
|
|
@@ -68,10 +58,10 @@ export function segmentsOf(result: Result): SegmentGraph {
|
|
|
68
58
|
// The place of each shared segment in the route, in the order of the route.
|
|
69
59
|
const found: { at: number; end: number; ref: SegmentRef }[] = [];
|
|
70
60
|
for (const s of onEdge.get(route.edge) ?? []) {
|
|
71
|
-
let at =
|
|
61
|
+
let at = findRun(p, s.points, 0);
|
|
72
62
|
let reverse = false;
|
|
73
63
|
if (at < 0) {
|
|
74
|
-
at =
|
|
64
|
+
at = findRun(p, [...s.points].reverse(), 0);
|
|
75
65
|
reverse = true;
|
|
76
66
|
}
|
|
77
67
|
if (at >= 0) found.push({ at, end: at + s.points.length - 1, ref: { segment: s.id, reverse, lane: s.lane } });
|
|
@@ -221,18 +211,7 @@ export type RunOrder = "bundle" | "run";
|
|
|
221
211
|
* On a run with a cycle, the order depends on the order of `routes`. The item LANE-STRETCH is to remove the cycles.
|
|
222
212
|
*/
|
|
223
213
|
export function orderLanes(run: readonly Point[], routes: readonly Route[], order: RunOrder = "run"): string[] {
|
|
224
|
-
const placed
|
|
225
|
-
const lost: string[] = [];
|
|
226
|
-
for (const r of routes) {
|
|
227
|
-
let at = indexOf(r.points, run, 0);
|
|
228
|
-
if (at >= 0) placed.push({ edge: r.edge, p: r.points, s: at, e: at + run.length - 1 });
|
|
229
|
-
else {
|
|
230
|
-
const p = [...r.points].reverse();
|
|
231
|
-
at = indexOf(p, run, 0);
|
|
232
|
-
if (at >= 0) placed.push({ edge: r.edge, p, s: at, e: at + run.length - 1 });
|
|
233
|
-
else lost.push(r.edge);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
214
|
+
const { placed, lost } = placeRoutes(run, routes);
|
|
236
215
|
const lanes = orderPlaced(placed, order);
|
|
237
216
|
return [...lanes.map((x) => x.edge), ...lost.sort()];
|
|
238
217
|
}
|
|
@@ -66,7 +66,9 @@ import { TRACLUS_DEFAULTS, traclusPlan, type TraclusOptions } from "../highways/
|
|
|
66
66
|
import { WEISZFELD_DEFAULTS, weiszfeldPlan, type WeiszfeldOptions } from "../highways/weiszfeld/index.ts";
|
|
67
67
|
import { WSPD_DEFAULTS, wspdPlan, type WspdOptions } from "../highways/wspd/index.ts";
|
|
68
68
|
import { ZIPPER_DEFAULTS, zipperPlan, type ZipperOptions } from "../highways/zipper/index.ts";
|
|
69
|
+
import { depthLevels, type DepthLevelOptions } from "../levels/depths/index.ts";
|
|
69
70
|
import type { Graph, Result, Router } from "../model.ts";
|
|
71
|
+
import { facingEnds } from "../plugs/facing/index.ts";
|
|
70
72
|
import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
|
|
71
73
|
import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
|
|
72
74
|
import { cornersRouter, type CornersOptions } from "../routers/corners.ts";
|
|
@@ -76,10 +78,12 @@ import { sharedRouter, type SharedOptions } from "../routers/shared.ts";
|
|
|
76
78
|
import { straightRouter } from "../routers/straight.ts";
|
|
77
79
|
import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunks.ts";
|
|
78
80
|
import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
|
|
81
|
+
import { runPipeline, type PipelineOptions } from "../whole/pipeline/index.ts";
|
|
79
82
|
import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
|
|
80
83
|
import type { OptionSchema } from "./options.ts";
|
|
81
84
|
import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
|
|
82
|
-
import {
|
|
85
|
+
import { DEPTHS_SCHEMA, PIPELINE_SCHEMA } from "./schemas.ts";
|
|
86
|
+
import { CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CONSENSUS_SCHEMA, CUBU_SCHEMA, CUT_SCHEMA, DIFFUSION_SCHEMA, DIVIDED_SCHEMA, EDGE_PATH_SCHEMA, FDEB_SCHEMA, FLOW_CLUSTER_SCHEMA, FLOW_FIELD_SCHEMA, GROWTH_SCHEMA, HDBSCAN_SCHEMA, HIERARCHY_SCHEMA, HIGHWAY_SCHEMA, HOUGH_SCHEMA, HUB_ARC_SCHEMA, KD_SCHEMA, KDEEB_SCHEMA, KEY_SCHEMA, LANES_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, LINK_COMMUNITY_SCHEMA, MEET_SCHEMA, MESH_SCHEMA, MINGLE_SCHEMA, NESTED_SCHEMA, OD_CELL_SCHEMA, P_HUB_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, RESISTANCE_SCHEMA, RIDGE_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SCALE_SPACE_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_ALL_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, SOFT_KEY_SCHEMA, SPECTRAL_SCHEMA, SPIRAL_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TERRITORY_SCHEMA, TRACLUS_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA } from "./schemas.ts";
|
|
83
87
|
|
|
84
88
|
/** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
|
|
85
89
|
export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
|
|
@@ -150,7 +154,7 @@ export const routerStrategies: {
|
|
|
150
154
|
sharedRouter,
|
|
151
155
|
SHARED_SCHEMA,
|
|
152
156
|
),
|
|
153
|
-
"shared-all": routerStrategy<RouterPreset<SharedOptions>>("shared-all", "The same as `shared`, but all edges share their ways.", 10_000, {}, sharedRouter,
|
|
157
|
+
"shared-all": routerStrategy<RouterPreset<SharedOptions>>("shared-all", "The same as `shared`, but all edges share their ways.", 10_000, {}, sharedRouter, SHARED_ALL_SCHEMA),
|
|
154
158
|
sections: routerStrategy<RouterPreset<SectionsOptions>>(
|
|
155
159
|
"sections",
|
|
156
160
|
"A tree of sections, with a hub in each section. A long edge goes through the hubs, and a short edge goes directly.",
|
|
@@ -895,6 +899,51 @@ export const highwayStrategies: {
|
|
|
895
899
|
"corridors+ids": gateStrategy(corridors, stableIdGate),
|
|
896
900
|
});
|
|
897
901
|
|
|
902
|
+
/** The built-in plug rules: the strategies of the step `plugs`. The router `corners` routes between their plugs with its option `plug`. */
|
|
903
|
+
export const plugStrategies: {
|
|
904
|
+
readonly facing: Strategy<"plugs", object>;
|
|
905
|
+
} = Object.freeze({
|
|
906
|
+
facing: defineStrategy({
|
|
907
|
+
step: "plugs",
|
|
908
|
+
name: "facing",
|
|
909
|
+
describe: "Each free plug faces the other end of its edge, on the side of the larger component of the direction. It is the default of the router `corners`.",
|
|
910
|
+
defaults: {},
|
|
911
|
+
schema: {},
|
|
912
|
+
run: (input) => facingEnds(input),
|
|
913
|
+
}),
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
/** The built-in level rules: the strategies of the step `levels`. The router `sections` makes its zoom levels with its option `level`. */
|
|
917
|
+
export const levelStrategies: {
|
|
918
|
+
readonly depths: Strategy<"levels", DepthLevelOptions>;
|
|
919
|
+
} = Object.freeze({
|
|
920
|
+
depths: defineStrategy<"levels", DepthLevelOptions>({
|
|
921
|
+
step: "levels",
|
|
922
|
+
name: "depths",
|
|
923
|
+
describe: "One zoom level for each depth of the tree, and a last level with each node alone. A link of a level goes around the other items. It is the default of the router `sections`.",
|
|
924
|
+
defaults: {},
|
|
925
|
+
schema: DEPTHS_SCHEMA,
|
|
926
|
+
run: (input, o) => depthLevels(input, o),
|
|
927
|
+
}),
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* The built-in whole strategies: the strategies of the step `whole`. The strategy `pipeline` runs one strategy of each step, as the demo of the site does.
|
|
932
|
+
* Without options, it routes the graph with the router `corners`.
|
|
933
|
+
*/
|
|
934
|
+
export const wholeStrategies: {
|
|
935
|
+
readonly pipeline: Strategy<"whole", PipelineOptions>;
|
|
936
|
+
} = Object.freeze({
|
|
937
|
+
pipeline: defineStrategy<"whole", PipelineOptions>({
|
|
938
|
+
step: "whole",
|
|
939
|
+
name: "pipeline",
|
|
940
|
+
describe: "One setting of the steps. The room opens the board, then the router or the highway plan routes it with the rules that the router reads.",
|
|
941
|
+
defaults: { router: routerStrategies.corners as Strategy<"router"> },
|
|
942
|
+
schema: PIPELINE_SCHEMA,
|
|
943
|
+
run: (graph, o) => runPipeline(graph, o),
|
|
944
|
+
}),
|
|
945
|
+
});
|
|
946
|
+
|
|
898
947
|
/** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */
|
|
899
948
|
export const BUILT_IN_STRATEGIES: readonly AnyStrategy[] = Object.freeze([
|
|
900
949
|
...Object.values(groupStrategies),
|
|
@@ -902,6 +951,9 @@ export const BUILT_IN_STRATEGIES: readonly AnyStrategy[] = Object.freeze([
|
|
|
902
951
|
...Object.values(highwayStrategies),
|
|
903
952
|
...Object.values(curveStrategies),
|
|
904
953
|
...Object.values(roomStrategies),
|
|
954
|
+
...Object.values(plugStrategies),
|
|
955
|
+
...Object.values(levelStrategies),
|
|
956
|
+
...Object.values(wholeStrategies),
|
|
905
957
|
]);
|
|
906
958
|
|
|
907
959
|
/** The registry of the built-in strategies. The benchmark and the site take their lists from it. */
|
package/src/strategies/model.ts
CHANGED
|
@@ -2,10 +2,12 @@ import type { CurveSegment } from "../curves/model.ts";
|
|
|
2
2
|
import type { CurveSpace } from "../curves/space.ts";
|
|
3
3
|
import type { Point } from "../geom.ts";
|
|
4
4
|
import type { HighwayInput, HighwayPlan } from "../highways/model.ts";
|
|
5
|
-
import type { Graph, Result, Router } from "../model.ts";
|
|
5
|
+
import type { Graph, Level, Result, Route, Router } from "../model.ts";
|
|
6
|
+
import type { Plug } from "../plugs.ts";
|
|
6
7
|
import type { Boxed, Gap, RoomLink } from "../room/index.ts";
|
|
7
8
|
import type { Section } from "../sections.ts";
|
|
8
9
|
import type { SegmentGraph } from "../segments.ts";
|
|
10
|
+
import type { WayFinder } from "../ways.ts";
|
|
9
11
|
import type { OptionSchema } from "./options.ts";
|
|
10
12
|
|
|
11
13
|
/** The input of a curve strategy: one polyline and the context of its route. */
|
|
@@ -48,21 +50,57 @@ export interface RoomInput {
|
|
|
48
50
|
readonly sections?: Section;
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
/** The input of a plug strategy: the graph, and optionally the way finder of the router that reads the plugs. */
|
|
54
|
+
export interface PlugInput {
|
|
55
|
+
readonly graph: Graph;
|
|
56
|
+
/** The ways around the nodes of the graph, for a strategy that reads the free space. The default strategy `facing` does not read it. */
|
|
57
|
+
readonly ways?: WayFinder;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The plugs of the two ends of one edge: the output of a plug strategy for that edge. */
|
|
61
|
+
export interface PlugPair {
|
|
62
|
+
readonly edge: string;
|
|
63
|
+
readonly source: Plug;
|
|
64
|
+
readonly target: Plug;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The input of a level strategy: the graph, the tree of sections of its grouping rule and the routes of the router. */
|
|
68
|
+
export interface LevelInput {
|
|
69
|
+
readonly graph: Graph;
|
|
70
|
+
/** The tree of sections. The indices of its nodes are the indices of `graph.nodes`. */
|
|
71
|
+
readonly root: Section;
|
|
72
|
+
/** The routes of the router. The default strategy `depths` gives the links of the last level the points of these routes. */
|
|
73
|
+
readonly routes: readonly Route[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The output of a strategy of the step `whole`: the board that the routes go on, and the routes. */
|
|
77
|
+
export interface WholeOutput {
|
|
78
|
+
/** The board: the graph, or the graph with the gaps that a room strategy opened. It has the edges of the input. */
|
|
79
|
+
readonly graph: Graph;
|
|
80
|
+
readonly result: Result;
|
|
81
|
+
}
|
|
82
|
+
|
|
51
83
|
/**
|
|
52
84
|
* The steps that have strategies, with the fixed input and output type of each step.
|
|
53
85
|
*
|
|
54
86
|
* - `group`: a graph in, the tree of sections out. The router `sections` makes its hubs and its zoom levels from this tree.
|
|
87
|
+
* - `plugs`: a graph in, the plugs of the two ends of each edge out. The router `corners` routes between these plugs.
|
|
55
88
|
* - `router`: a graph in, the routes (and the `levels`, `bundles` and `runs`) out.
|
|
56
89
|
* - `highway`: a graph and optionally its first routes in, a highway plan out. `follow` routes the edges along the plan.
|
|
90
|
+
* - `levels`: a graph, its tree of sections and its routes in, the zoom levels out. The router `sections` gives them in its result.
|
|
57
91
|
* - `curve`: a polyline in, the segments of a curve out.
|
|
58
92
|
* - `room`: the boxes and links of a board in, the gaps of the board with their need out.
|
|
93
|
+
* - `whole`: a graph in, the board and its routes out. A strategy of this step runs the other steps.
|
|
59
94
|
*/
|
|
60
95
|
export interface StepTypes {
|
|
61
96
|
readonly group: { readonly input: Graph; readonly output: Section };
|
|
97
|
+
readonly plugs: { readonly input: PlugInput; readonly output: PlugPair[] };
|
|
62
98
|
readonly router: { readonly input: Graph; readonly output: Result };
|
|
63
99
|
readonly highway: { readonly input: HighwayInput; readonly output: HighwayPlan };
|
|
100
|
+
readonly levels: { readonly input: LevelInput; readonly output: Level[] };
|
|
64
101
|
readonly curve: { readonly input: CurveInput; readonly output: CurveSegment[] };
|
|
65
102
|
readonly room: { readonly input: RoomInput; readonly output: Gap[] };
|
|
103
|
+
readonly whole: { readonly input: Graph; readonly output: WholeOutput };
|
|
66
104
|
}
|
|
67
105
|
|
|
68
106
|
/** The name of a step that has strategies. */
|
|
@@ -74,8 +112,12 @@ export type StepInput<S extends Step> = StepTypes[S]["input"];
|
|
|
74
112
|
/** The output type of a step. */
|
|
75
113
|
export type StepOutput<S extends Step> = StepTypes[S]["output"];
|
|
76
114
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
115
|
+
/**
|
|
116
|
+
* The steps that have strategies, in the order of the pipeline.
|
|
117
|
+
* The plugs come before the router, because the router searches its ways between them. The levels come after the routes, because the last level reads them.
|
|
118
|
+
* The step `whole` runs the other steps, so it comes last.
|
|
119
|
+
*/
|
|
120
|
+
export const STEPS: readonly Step[] = ["group", "plugs", "router", "highway", "levels", "curve", "room", "whole"];
|
|
79
121
|
|
|
80
122
|
/**
|
|
81
123
|
* A strategy: one way to do one step, with a name and a description.
|
|
@@ -147,16 +189,16 @@ export function defineStrategy<S extends Step, O extends object>(definition: Str
|
|
|
147
189
|
/**
|
|
148
190
|
* A registry: the strategies of each step, in order. A registry is data. No import changes it.
|
|
149
191
|
* A consumer makes its own registry with `createRegistry`, and adds a strategy with `register`.
|
|
150
|
-
* The lists `group` and `
|
|
192
|
+
* The lists `group`, `highway`, `plugs`, `levels` and `whole` are optional in the type, so a registry of an older version stays a registry. `createRegistry` always gives them.
|
|
151
193
|
*/
|
|
152
|
-
export type Registry = { readonly [S in Exclude<Step,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
194
|
+
export type Registry = { readonly [S in Exclude<Step, OptionalStep>]: readonly Strategy<S>[] } & { readonly [S in OptionalStep]?: readonly Strategy<S>[] };
|
|
195
|
+
|
|
196
|
+
/** The steps whose lists are optional in a `Registry`: the steps that came after version 0.1.5. */
|
|
197
|
+
type OptionalStep = "group" | "highway" | "plugs" | "levels" | "whole";
|
|
156
198
|
|
|
157
199
|
/** This function makes a registry from a list of strategies. It keeps their order. Two strategies with one step and one name are an error. */
|
|
158
200
|
export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
|
|
159
|
-
const out: { [S in Step]: Strategy<S>[] } = { group: [], router: [], highway: [], curve: [], room: [] };
|
|
201
|
+
const out: { [S in Step]: Strategy<S>[] } = { group: [], plugs: [], router: [], highway: [], levels: [], curve: [], room: [], whole: [] };
|
|
160
202
|
for (const s of strategies) {
|
|
161
203
|
const list = out[s.step] as AnyStrategy[] | undefined;
|
|
162
204
|
if (!list) throw new Error(`There is no step "${String(s.step)}". The steps are ${STEPS.join(", ")}.`);
|
|
@@ -165,16 +207,19 @@ export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
|
|
|
165
207
|
}
|
|
166
208
|
return Object.freeze({
|
|
167
209
|
group: Object.freeze(out.group),
|
|
210
|
+
plugs: Object.freeze(out.plugs),
|
|
168
211
|
router: Object.freeze(out.router),
|
|
169
212
|
highway: Object.freeze(out.highway),
|
|
213
|
+
levels: Object.freeze(out.levels),
|
|
170
214
|
curve: Object.freeze(out.curve),
|
|
171
215
|
room: Object.freeze(out.room),
|
|
216
|
+
whole: Object.freeze(out.whole),
|
|
172
217
|
});
|
|
173
218
|
}
|
|
174
219
|
|
|
175
220
|
/** This function gives a new registry with the strategies of `registry` and then `strategies`. It does not change `registry`. */
|
|
176
221
|
export function register(registry: Registry, ...strategies: readonly AnyStrategy[]): Registry {
|
|
177
|
-
return createRegistry([...(
|
|
222
|
+
return createRegistry([...STEPS.flatMap((step) => listStrategies(registry, step) as readonly AnyStrategy[]), ...strategies]);
|
|
178
223
|
}
|
|
179
224
|
|
|
180
225
|
/** This function gives the strategies of one step of a registry, in order. */
|