@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.
@@ -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 [ps, pt] =
59
- options.plugs === "side" ? sidePlugs(ways, s, e.source, t, e.target) : [placePlug(s, e.source, center(t.box)), placePlug(t, e.target, center(s.box))];
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.
@@ -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, levelLinks, type ItemPair, type LevelLinkOptions } from "../levels.ts";
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, levelCut, SECTION_DEFAULTS, treeDepth, type Section, type SectionOptions } from "../sections.ts";
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, routeOf, options) };
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 one zoom level for each depth of the tree, and a last level with each node alone.
229
- * With the option `levels`, only those depths get a level with groups.
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, routeOf: ReadonlyMap<string, Route>, options: SectionsOptions): Level[] {
235
- const indexOfNode = new Map(graph.nodes.map((n, i) => [n.id, i]));
236
- const levels: Level[] = [];
237
- const deepest = treeDepth(root);
238
- const shown = options.levels && new Set(options.levels);
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
  }
@@ -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
- /** The edges with one key share their ways. Without a rule, all edges share. An edge without a key shares with no edge. */
26
- readonly rule?: GroupRule;
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 (!options.rule) return "*";
118
- return options.rule(e) ?? `\u0000${e.id}`;
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
- for (const run of runs.values()) {
315
- const edges = options.laneOrder === "run" ? laneOrderOfRun(run) : orderLanes(run.points, run.ways.map((w) => routeOf.get(w.edge.id)!), "bundle");
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
  };
@@ -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, options.rule, options.minSize ?? 2)) {
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, type Placed } from "./lane-order.ts";
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 = indexOf(p, s.points, 0);
61
+ let at = findRun(p, s.points, 0);
72
62
  let reverse = false;
73
63
  if (at < 0) {
74
- at = indexOf(p, [...s.points].reverse(), 0);
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: 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 { 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_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";
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, SHARED_SCHEMA),
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. */
@@ -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
- /** The steps that have strategies, in the order of the pipeline. */
78
- export const STEPS: readonly Step[] = ["group", "router", "highway", "curve", "room"];
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 `highway` are optional in the type, so a registry from version 0.1.5 or 0.3.0 stays a registry. `createRegistry` always gives them.
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, "group" | "highway">]: readonly Strategy<S>[] } & {
153
- readonly group?: readonly Strategy<"group">[];
154
- readonly highway?: readonly Strategy<"highway">[];
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([...(registry.group ?? []), ...registry.router, ...(registry.highway ?? []), ...registry.curve, ...registry.room, ...strategies]);
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. */