@mark1russell7/highways 0.3.0 → 0.4.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.
@@ -0,0 +1,111 @@
1
+ import { dist, type Point } from "../geom.ts";
2
+ import type { Graph, Result } from "../model.ts";
3
+
4
+ /** The input of a highway strategy: the boxes and the edges, and optionally the first routes of a router. */
5
+ export interface HighwayInput {
6
+ readonly graph: Graph;
7
+ /**
8
+ * These routes are the first routes, from a router. A strategy that reads first routes makes them itself when this value is absent.
9
+ * The strategy `corridors` makes the ways of the corner graph, as the router `corners` finds them.
10
+ */
11
+ readonly routes?: Result;
12
+ }
13
+
14
+ /**
15
+ * One edge of a highway. The edge joins the spine at the merge point and leaves it at the split point.
16
+ * A place on the spine is the index of a point of the spine plus the share of the next leg. Point `i` of the spine is at `i`.
17
+ * When `on` is larger than `off`, the edge goes along the spine from its last point to its first point.
18
+ */
19
+ export interface HighwayMember {
20
+ readonly edge: string;
21
+ /** The place of the merge point on the spine. */
22
+ readonly on: number;
23
+ /** The place of the split point on the spine. */
24
+ readonly off: number;
25
+ /** The point where the edge joins the spine. */
26
+ readonly merge: Point;
27
+ /** The point where the edge leaves the spine. */
28
+ readonly split: Point;
29
+ /**
30
+ * The place of this highway in the trip of the edge, from 0. An edge can go along several highways, one after the other.
31
+ * `follow` takes the highways of an edge in the order of this value.
32
+ */
33
+ readonly seq: number;
34
+ }
35
+
36
+ /** A highway: a shared way that a bundle of edges follows, from their merge points to their split points. */
37
+ export interface Highway {
38
+ readonly id: string;
39
+ /** The key of the highway, for example the key of the grouping rule or the index of a cluster. */
40
+ readonly key: string;
41
+ /** The spine: the polyline that the edges follow, in free space. */
42
+ readonly spine: readonly Point[];
43
+ /** The edges in lane order. Lane 0 is on the left of the spine, from its first point to its last point. The y axis points down. */
44
+ readonly lanes: readonly string[];
45
+ /** The edges with their merge points and their split points, in the order of `lanes`. */
46
+ readonly members: readonly HighwayMember[];
47
+ }
48
+
49
+ /** The output of a highway strategy: the highways, as data. `follow` routes the edges along them. */
50
+ export interface HighwayPlan {
51
+ readonly highways: readonly Highway[];
52
+ }
53
+
54
+ /** This function gives the point at a place on a polyline. A place outside the polyline gives its nearest end. */
55
+ export function pointAt(line: readonly Point[], at: number): Point {
56
+ if (line.length === 0) throw new Error("The polyline has no point.");
57
+ if (at <= 0) return line[0]!;
58
+ if (at >= line.length - 1) return line[line.length - 1]!;
59
+ const i = Math.floor(at);
60
+ const u = at - i;
61
+ const a = line[i]!;
62
+ if (u === 0) return a;
63
+ const b = line[i + 1]!;
64
+ return { x: a.x + (b.x - a.x) * u, y: a.y + (b.y - a.y) * u };
65
+ }
66
+
67
+ /** This function gives the length of a polyline from the place `from` to the place `to`, with `from` not larger than `to`. */
68
+ export function lengthBetween(line: readonly Point[], from: number, to: number): number {
69
+ if (to <= from) return 0;
70
+ let sum = 0;
71
+ let prev = pointAt(line, from);
72
+ for (let i = Math.floor(from) + 1; i < to; i++) {
73
+ const p = line[i]!;
74
+ sum += dist(prev, p);
75
+ prev = p;
76
+ }
77
+ return sum + dist(prev, pointAt(line, to));
78
+ }
79
+
80
+ /** This function gives a member of a highway with its merge point and its split point at the places `on` and `off` of the spine. */
81
+ export function memberAt(spine: readonly Point[], edge: string, on: number, off: number, seq = 0): HighwayMember {
82
+ return { edge, on, off, merge: pointAt(spine, on), split: pointAt(spine, off), seq };
83
+ }
84
+
85
+ /**
86
+ * This function lists the problems of a plan against its graph. A good plan gives an empty list.
87
+ * Each highway has a spine of two points or more, and two members or more. Each member is an edge of the graph, with `on` and `off` on the spine and apart.
88
+ * An edge is a member of a highway one time at most.
89
+ */
90
+ export function checkPlan(graph: Graph, plan: HighwayPlan): string[] {
91
+ const edges = new Set(graph.edges.map((e) => e.id));
92
+ const out: string[] = [];
93
+ const ids = new Set<string>();
94
+ for (const h of plan.highways) {
95
+ if (ids.has(h.id)) out.push(`Two highways have the id "${h.id}".`);
96
+ ids.add(h.id);
97
+ const last = h.spine.length - 1;
98
+ if (h.spine.length < 2) out.push(`The highway "${h.id}" has a spine with fewer than two points.`);
99
+ if (h.members.length < 2) out.push(`The highway "${h.id}" has fewer than two members.`);
100
+ const seen = new Set<string>();
101
+ for (const m of h.members) {
102
+ if (!edges.has(m.edge)) out.push(`The highway "${h.id}" has the member "${m.edge}", which is not an edge of the graph.`);
103
+ if (seen.has(m.edge)) out.push(`The highway "${h.id}" has the member "${m.edge}" two times.`);
104
+ seen.add(m.edge);
105
+ if (!(m.on >= 0 && m.on <= last && m.off >= 0 && m.off <= last)) out.push(`The member "${m.edge}" of the highway "${h.id}" has a place off the spine.`);
106
+ if (m.on === m.off) out.push(`The member "${m.edge}" of the highway "${h.id}" joins and leaves the spine at one place.`);
107
+ }
108
+ if (h.lanes.length !== h.members.length || h.lanes.some((e) => !seen.has(e))) out.push(`The lanes of the highway "${h.id}" are not its members.`);
109
+ }
110
+ return out;
111
+ }
@@ -0,0 +1,34 @@
1
+ import type { Result } from "../model.ts";
2
+ import { segmentsOf } from "../segments.ts";
3
+ import { memberAt, type Highway, type HighwayMember, type HighwayPlan } from "./model.ts";
4
+
5
+ /**
6
+ * This function gives the highways of a result of a router: one highway for each shared run and for each trunk of a bundle.
7
+ * The spine is the points of the run or of the trunk. Each edge of the run is a member, from one end of the spine to the other end, in the way of its route.
8
+ * The member `seq` is the place of the shared stretch in the route of the edge. The lanes keep the lane order of the router.
9
+ *
10
+ * Thus `follow` routes each edge along the shared stretches of the router. A route that does not hold its run is not a member.
11
+ */
12
+ export function planOfResult(result: Result): HighwayPlan {
13
+ const graph = segmentsOf(result);
14
+ const keys = new Map<string, string>([...(result.runs ?? []).map((r) => [r.id, r.key] as const), ...(result.bundles ?? []).map((b) => [b.id, b.key] as const)]);
15
+ const shared = graph.segments.filter((s) => s.count > 1);
16
+ const ids = new Set(shared.map((s) => s.id));
17
+ const place = new Map<string, { seq: number; reverse: boolean }>();
18
+ for (const path of graph.paths) {
19
+ let seq = 0;
20
+ for (const ref of path.segments) if (ids.has(ref.segment)) place.set(`${ref.segment}\u0000${path.edge}`, { seq: seq++, reverse: ref.reverse });
21
+ }
22
+ const highways: Highway[] = [];
23
+ for (const s of shared) {
24
+ const last = s.points.length - 1;
25
+ const members: HighwayMember[] = [];
26
+ for (const edge of s.edges) {
27
+ const at = place.get(`${s.id}\u0000${edge}`);
28
+ if (at) members.push(at.reverse ? memberAt(s.points, edge, last, 0, at.seq) : memberAt(s.points, edge, 0, last, at.seq));
29
+ }
30
+ if (members.length < 2) continue;
31
+ highways.push({ id: `h${highways.length}`, key: keys.get(s.id) ?? s.id, spine: s.points, lanes: members.map((m) => m.edge), members });
32
+ }
33
+ return { highways };
34
+ }
@@ -0,0 +1,40 @@
1
+ import type { Graph, Result, Router } from "../model.ts";
2
+ import { defineStrategy, type Strategy } from "../strategies/model.ts";
3
+ import type { WayOptions } from "../ways.ts";
4
+ import { follow, type FollowOptions } from "./follow.ts";
5
+
6
+ /** This function gives the options of the way finder in the options of a strategy: `margin` and `k`, when they are present. */
7
+ function wayOptionsOf(options: object): WayOptions {
8
+ const o = options as { margin?: unknown; k?: unknown };
9
+ return { ...(typeof o.margin === "number" ? { margin: o.margin } : {}), ...(typeof o.k === "number" ? { k: o.k } : {}) };
10
+ }
11
+
12
+ /**
13
+ * This function gives a router that runs a highway strategy and then `follow`. The strategy gets the graph without first routes.
14
+ * The way finder of `follow` takes `margin` and `k` from the options of the strategy, and then from `followOptions`.
15
+ * The router keeps the name and the `maxNodes` of the strategy.
16
+ */
17
+ export function highwayRouter<O extends object>(strategy: Strategy<"highway", O>, options?: Partial<O>, followOptions: FollowOptions = {}): Router {
18
+ return {
19
+ name: strategy.name,
20
+ ...(strategy.maxNodes === undefined ? {} : { maxNodes: strategy.maxNodes }),
21
+ route: (graph: Graph): Result => follow(graph, strategy.run({ graph }, options), { ...followOptions, ...wayOptionsOf({ ...strategy.defaults, ...options }) }),
22
+ };
23
+ }
24
+
25
+ /**
26
+ * This function gives a router strategy that runs a highway strategy and then `follow`.
27
+ * The router strategy has the name, the options and the schema of the highway strategy.
28
+ * The benchmark and the screen race such a router with the other routers.
29
+ */
30
+ export function highwayRouterStrategy<O extends object>(strategy: Strategy<"highway", O>): Strategy<"router", O> {
31
+ return defineStrategy<"router", O>({
32
+ step: "router",
33
+ name: strategy.name,
34
+ describe: strategy.describe,
35
+ defaults: strategy.defaults as O,
36
+ ...(strategy.schema === undefined ? {} : { schema: strategy.schema }),
37
+ ...(strategy.maxNodes === undefined ? {} : { maxNodes: strategy.maxNodes }),
38
+ run: (graph: Graph, options: Readonly<O>): Result => follow(graph, strategy.run({ graph }, options), wayOptionsOf(options)),
39
+ });
40
+ }
@@ -0,0 +1,15 @@
1
+ import { sharedRouter, type SharedOptions } from "../../routers/shared.ts";
2
+ import { planOfResult } from "../result.ts";
3
+ import type { HighwayInput, HighwayPlan } from "../model.ts";
4
+
5
+ /** The options of the highway strategy `shared-ways`: the options of the router `shared`, without the name and the limit of the benchmark. */
6
+ export type SharedWaysOptions = Omit<SharedOptions, "name" | "maxNodes">;
7
+
8
+ /**
9
+ * This function gives the highways that the router `shared` decides. The edges of one key go one by one through the corner graph.
10
+ * A link that the key uses already costs less, so the ways of a key come together. Each stretch that two edges or more share becomes one highway.
11
+ * The function does not change the router: it reads its runs.
12
+ */
13
+ export function sharedWaysPlan(input: HighwayInput, options: SharedWaysOptions): HighwayPlan {
14
+ return planOfResult(sharedRouter({ ...options, name: "shared-ways" }).route(input.graph));
15
+ }
package/src/index.ts CHANGED
@@ -27,3 +27,5 @@ export * from "./bench/measures/index.ts";
27
27
  export * from "./bench/suite.ts";
28
28
  export * from "./strategies/index.ts";
29
29
  export * from "./bench/compare.ts";
30
+ export * from "./bench/screen/index.ts";
31
+ export * from "./highways/index.ts";
@@ -1,7 +1,9 @@
1
1
  import { arcCurve, type ArcOptions } from "../curves/arc/index.ts";
2
2
  import { clothoidCurve, type ClothoidOptions } from "../curves/clothoid/index.ts";
3
- import { highwayCurve, type JogStyle } from "../curves/highway/index.ts";
3
+ import { highwayCurve, highwayCurves, type JogStyle, type LaneCurve } from "../curves/highway/index.ts";
4
+ import type { CurveSegment } from "../curves/model.ts";
4
5
  import { roundedCurve, type RoundedOptions } from "../curves/rounded/index.ts";
6
+ import type { CurveSpace } from "../curves/space.ts";
5
7
  import { smoothCurve, type SmoothOptions } from "../curves/smooth/index.ts";
6
8
  import { splineCurve, type SplineOptions } from "../curves/spline/index.ts";
7
9
  import { straightCurve } from "../curves/straight/index.ts";
@@ -9,6 +11,10 @@ import { CUT_DEFAULTS, cutGroups, type CutOptions } from "../groups/cut/index.ts
9
11
  import { KD_DEFAULTS, kdGroups, type KdOptions } from "../groups/kd/index.ts";
10
12
  import { byDataGroup, keyGroups, type KeyOptions } from "../groups/keys/index.ts";
11
13
  import { quadtreeGroups } from "../groups/quadtree/index.ts";
14
+ import { CORRIDOR_DEFAULTS, corridorPlan, type CorridorOptions } from "../highways/corridors/index.ts";
15
+ import { keysPlan, type KeysOptions } from "../highways/keys/index.ts";
16
+ import type { HighwayInput } from "../highways/model.ts";
17
+ import { sharedWaysPlan, type SharedWaysOptions } from "../highways/shared-ways/index.ts";
12
18
  import type { Graph, Result, Router } from "../model.ts";
13
19
  import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
14
20
  import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
@@ -19,9 +25,9 @@ import { sharedRouter, type SharedOptions } from "../routers/shared.ts";
19
25
  import { straightRouter } from "../routers/straight.ts";
20
26
  import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunks.ts";
21
27
  import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
22
- import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type Registry, type Strategy } from "./model.ts";
28
+ import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
23
29
  import type { OptionSchema } from "./options.ts";
24
- import { CLOTHOID_SCHEMA, CORNERS_SCHEMA, CUT_SCHEMA, HIGHWAY_SCHEMA, KD_SCHEMA, KEY_SCHEMA, LEVEL_ROOM_SCHEMA, RADIUS_SCHEMA, ROOM_SCHEMA, SECTION_SCHEMA, SECTIONS_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, TENSION_SCHEMA, TRUNKS_SCHEMA } from "./schemas.ts";
30
+ import { CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CUT_SCHEMA, HIGHWAY_SCHEMA, KD_SCHEMA, KEY_SCHEMA, LEVEL_ROOM_SCHEMA, RADIUS_SCHEMA, ROOM_SCHEMA, SECTION_SCHEMA, SECTIONS_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, TENSION_SCHEMA, TRUNKS_SCHEMA } from "./schemas.ts";
25
31
 
26
32
  /** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
27
33
  export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
@@ -130,7 +136,10 @@ export interface TensionOptions {
130
136
  readonly tension: number;
131
137
  }
132
138
 
133
- /** The options of the curve strategy `highway`. */
139
+ /**
140
+ * The options of the curve strategy `highway`. The options `minRadius`, `jog` and `jogPieces` change only the lanes of a bundle.
141
+ * So they change the curve only when the input has a `lane` on a board with open segments.
142
+ */
134
143
  export interface HighwayStrategyOptions {
135
144
  /** The radius of the center line of a bundle at a bend, and of each lone corner. The default is 12. */
136
145
  readonly radius: number;
@@ -142,6 +151,33 @@ export interface HighwayStrategyOptions {
142
151
  readonly jogPieces: number;
143
152
  }
144
153
 
154
+ /** The curves of the lanes of each board, for the last options and the last free space. The strategy `highway` draws one lane in each call. */
155
+ const laneCurves = new WeakMap<LaneBoard, { readonly key: string; readonly space: CurveSpace | undefined; readonly curves: LaneCurve[] }>();
156
+
157
+ /**
158
+ * This function gives the curve of one lane of a board with `highwayCurves` and each option of the strategy `highway`.
159
+ * The lanes of a bundle bend as one. So the function draws all the lanes of the board one time, and keeps them for the next lane.
160
+ */
161
+ function highwayLane(input: CurveInput & Required<Pick<CurveInput, "lane">>, o: Readonly<HighwayStrategyOptions>): CurveSegment[] {
162
+ const { board, index } = input.lane;
163
+ const key = JSON.stringify([o.radius, o.minRadius, o.jog, o.jogPieces]);
164
+ let hit = laneCurves.get(board);
165
+ if (!hit || hit.key !== key || hit.space !== input.space) {
166
+ const curves = highwayCurves(board.graph, board.open, board.width, {
167
+ radius: o.radius,
168
+ minRadius: o.minRadius,
169
+ jog: o.jog,
170
+ jogPieces: o.jogPieces,
171
+ ...(board.maxWidth === undefined ? {} : { maxWidth: board.maxWidth }),
172
+ ...(input.space ? { space: input.space } : {}),
173
+ });
174
+ hit = { key, space: input.space, curves };
175
+ laneCurves.set(board, hit);
176
+ }
177
+ const lane = hit.curves[index];
178
+ return lane ? [...lane.segments] : highwayCurve(input.points, { radius: o.radius, ...(input.space ? { space: input.space } : {}) });
179
+ }
180
+
145
181
  /** The options of the curve strategy `smooth`. */
146
182
  export interface SmoothStrategyOptions {
147
183
  /** The radius of the arc of each corner. The default is 12. */
@@ -162,7 +198,7 @@ export interface ClothoidStrategyOptions {
162
198
 
163
199
  /**
164
200
  * The built-in curve strategies. The first six are in the order of `CURVE_TYPES`, and each one gives the same segments as `curveOf` with its type.
165
- * The strategy `highway` gives the curve of `arc` for one polyline. `highwayCurves` bends the lanes of a bundle as one.
201
+ * The strategy `highway` gives the curve of `arc` for one polyline. For a polyline with a `lane`, it bends the lanes of each bundle as one with `highwayCurves`.
166
202
  */
167
203
  export const curveStrategies: {
168
204
  readonly straight: Strategy<"curve", object>;
@@ -227,7 +263,8 @@ export const curveStrategies: {
227
263
  describe: "The lanes of a bundle bend as one, with concentric arcs at the lane spacing. One lane alone gets the curve of `arc`.",
228
264
  defaults: { radius: 12, minRadius: 4, jog: "biarc" as JogStyle, jogPieces: 4 },
229
265
  schema: HIGHWAY_SCHEMA,
230
- run: (input: CurveInput, o: Readonly<HighwayStrategyOptions>) => highwayCurve(input.points, { radius: o.radius, ...(input.space ? { space: input.space } : {}) }),
266
+ run: (input: CurveInput, o: Readonly<HighwayStrategyOptions>) =>
267
+ input.lane ? highwayLane({ ...input, lane: input.lane }, o) : highwayCurve(input.points, { radius: o.radius, ...(input.space ? { space: input.space } : {}) }),
231
268
  }),
232
269
  });
233
270
 
@@ -303,10 +340,49 @@ export const groupStrategies: {
303
340
  }),
304
341
  });
305
342
 
343
+ /**
344
+ * The built-in highway strategies: the decisions of the routers `trunks` and `shared` as plans, and corridor signatures (HWY-031).
345
+ * `follow` routes the edges along a plan, and `highwayRouter` gives a router that does both.
346
+ */
347
+ export const highwayStrategies: {
348
+ readonly keys: Strategy<"highway", KeysOptions>;
349
+ readonly "shared-ways": Strategy<"highway", SharedWaysOptions>;
350
+ readonly corridors: Strategy<"highway", CorridorOptions>;
351
+ } = Object.freeze({
352
+ keys: defineStrategy({
353
+ step: "highway",
354
+ name: "keys",
355
+ describe: "The highways of the router `trunks`. The edges from one plug with one key share a trunk, and the edges that go on together share a run past it.",
356
+ defaults: { rule: byCollection } as KeysOptions,
357
+ schema: TRUNKS_SCHEMA,
358
+ maxNodes: 10_000,
359
+ run: (input: HighwayInput, o: Readonly<KeysOptions>) => keysPlan(input, o),
360
+ }),
361
+ "shared-ways": defineStrategy({
362
+ step: "highway",
363
+ name: "shared-ways",
364
+ describe: "The highways of the router `shared`. The edges of one key share their ways through the corner graph, and each shared stretch is a highway.",
365
+ defaults: { rule: byCollection } as SharedWaysOptions,
366
+ schema: SHARED_SCHEMA,
367
+ maxNodes: 10_000,
368
+ run: (input: HighwayInput, o: Readonly<SharedWaysOptions>) => sharedWaysPlan(input, o),
369
+ }),
370
+ corridors: defineStrategy({
371
+ step: "highway",
372
+ name: "corridors",
373
+ describe: "Corridor signatures. Edges whose first ways share long links make a cluster, found with MinHash. The longest common run of each cluster is its highway.",
374
+ defaults: { ...CORRIDOR_DEFAULTS } as CorridorOptions,
375
+ schema: CORRIDOR_SCHEMA,
376
+ maxNodes: 10_000,
377
+ run: (input: HighwayInput, o: Readonly<CorridorOptions>) => corridorPlan(input, o),
378
+ }),
379
+ });
380
+
306
381
  /** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */
307
382
  export const BUILT_IN_STRATEGIES: readonly AnyStrategy[] = Object.freeze([
308
383
  ...Object.values(groupStrategies),
309
384
  ...Object.values(routerStrategies),
385
+ ...Object.values(highwayStrategies),
310
386
  ...Object.values(curveStrategies),
311
387
  ...Object.values(roomStrategies),
312
388
  ]);
@@ -1,9 +1,11 @@
1
1
  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
+ import type { HighwayInput, HighwayPlan } from "../highways/model.ts";
4
5
  import type { Graph, Result, Router } from "../model.ts";
5
6
  import type { Boxed, Gap, RoomLink } from "../room/index.ts";
6
7
  import type { Section } from "../sections.ts";
8
+ import type { SegmentGraph } from "../segments.ts";
7
9
  import type { OptionSchema } from "./options.ts";
8
10
 
9
11
  /** The input of a curve strategy: one polyline and the context of its route. */
@@ -16,6 +18,23 @@ export interface CurveInput {
16
18
  readonly endNormal?: Point;
17
19
  /** The boxes that the curve must not go through, from `curveSpace`. */
18
20
  readonly space?: CurveSpace;
21
+ /**
22
+ * The lane of the polyline on its board, for a strategy that bends the lanes of a bundle as one. Then `points` is the route of `laneRoutes` with the index `index`.
23
+ * The other strategies do not read it.
24
+ */
25
+ readonly lane?: { readonly board: LaneBoard; readonly index: number };
26
+ }
27
+
28
+ /** The lanes of the bundles of one board: the input of `laneRoutes` and `highwayCurves`. */
29
+ export interface LaneBoard {
30
+ /** The shared segments and the paths of the routes, from `segmentsOf`. */
31
+ readonly graph: SegmentGraph;
32
+ /** The keys of the open segments, from `runKey`. Each edge of an open segment gets its own lane. */
33
+ readonly open: ReadonlySet<string>;
34
+ /** The distance between two lanes of an open segment. */
35
+ readonly width: number;
36
+ /** The largest width of an open segment, from its outer lane to its outer lane. The default is `Infinity`. */
37
+ readonly maxWidth?: number;
19
38
  }
20
39
 
21
40
  /** The input of a room strategy: the boxes of a board and the links between them. */
@@ -34,12 +53,14 @@ export interface RoomInput {
34
53
  *
35
54
  * - `group`: a graph in, the tree of sections out. The router `sections` makes its hubs and its zoom levels from this tree.
36
55
  * - `router`: a graph in, the routes (and the `levels`, `bundles` and `runs`) out.
56
+ * - `highway`: a graph and optionally its first routes in, a highway plan out. `follow` routes the edges along the plan.
37
57
  * - `curve`: a polyline in, the segments of a curve out.
38
58
  * - `room`: the boxes and links of a board in, the gaps of the board with their need out.
39
59
  */
40
60
  export interface StepTypes {
41
61
  readonly group: { readonly input: Graph; readonly output: Section };
42
62
  readonly router: { readonly input: Graph; readonly output: Result };
63
+ readonly highway: { readonly input: HighwayInput; readonly output: HighwayPlan };
43
64
  readonly curve: { readonly input: CurveInput; readonly output: CurveSegment[] };
44
65
  readonly room: { readonly input: RoomInput; readonly output: Gap[] };
45
66
  }
@@ -54,7 +75,7 @@ export type StepInput<S extends Step> = StepTypes[S]["input"];
54
75
  export type StepOutput<S extends Step> = StepTypes[S]["output"];
55
76
 
56
77
  /** The steps that have strategies, in the order of the pipeline. */
57
- export const STEPS: readonly Step[] = ["group", "router", "curve", "room"];
78
+ export const STEPS: readonly Step[] = ["group", "router", "highway", "curve", "room"];
58
79
 
59
80
  /**
60
81
  * A strategy: one way to do one step, with a name and a description.
@@ -126,25 +147,34 @@ export function defineStrategy<S extends Step, O extends object>(definition: Str
126
147
  /**
127
148
  * A registry: the strategies of each step, in order. A registry is data. No import changes it.
128
149
  * A consumer makes its own registry with `createRegistry`, and adds a strategy with `register`.
129
- * The list `group` is optional in the type, so a registry from version 0.1.5 stays a registry. `createRegistry` always gives it.
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.
130
151
  */
131
- export type Registry = { readonly [S in Exclude<Step, "group">]: readonly Strategy<S>[] } & { readonly group?: readonly Strategy<"group">[] };
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
+ };
132
156
 
133
157
  /** 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. */
134
158
  export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
135
- const out: { [S in Step]: Strategy<S>[] } = { group: [], router: [], curve: [], room: [] };
159
+ const out: { [S in Step]: Strategy<S>[] } = { group: [], router: [], highway: [], curve: [], room: [] };
136
160
  for (const s of strategies) {
137
161
  const list = out[s.step] as AnyStrategy[] | undefined;
138
162
  if (!list) throw new Error(`There is no step "${String(s.step)}". The steps are ${STEPS.join(", ")}.`);
139
163
  if (list.some((t) => t.name === s.name)) throw new Error(`The step "${s.step}" has two strategies with the name "${s.name}".`);
140
164
  list.push(s);
141
165
  }
142
- return Object.freeze({ group: Object.freeze(out.group), router: Object.freeze(out.router), curve: Object.freeze(out.curve), room: Object.freeze(out.room) });
166
+ return Object.freeze({
167
+ group: Object.freeze(out.group),
168
+ router: Object.freeze(out.router),
169
+ highway: Object.freeze(out.highway),
170
+ curve: Object.freeze(out.curve),
171
+ room: Object.freeze(out.room),
172
+ });
143
173
  }
144
174
 
145
175
  /** This function gives a new registry with the strategies of `registry` and then `strategies`. It does not change `registry`. */
146
176
  export function register(registry: Registry, ...strategies: readonly AnyStrategy[]): Registry {
147
- return createRegistry([...(registry.group ?? []), ...registry.router, ...registry.curve, ...registry.room, ...strategies]);
177
+ return createRegistry([...(registry.group ?? []), ...registry.router, ...(registry.highway ?? []), ...registry.curve, ...registry.room, ...strategies]);
148
178
  }
149
179
 
150
180
  /** This function gives the strategies of one step of a registry, in order. */
@@ -1,6 +1,7 @@
1
1
  import type { CutOptions } from "../groups/cut/index.ts";
2
2
  import type { KdOptions } from "../groups/kd/index.ts";
3
3
  import type { KeyOptions } from "../groups/keys/index.ts";
4
+ import type { CorridorOptions } from "../highways/corridors/index.ts";
4
5
  import type { CornersOptions } from "../routers/corners.ts";
5
6
  import type { SectionsOptions } from "../routers/sections.ts";
6
7
  import type { SharedOptions } from "../routers/shared.ts";
@@ -64,6 +65,18 @@ export const SHARED_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
64
65
  laneOrder: LANE_ORDER,
65
66
  };
66
67
 
68
+ /** The declarations of the options of the highway strategy `corridors` (HWY-031). */
69
+ export const CORRIDOR_SCHEMA: OptionSchema<CorridorOptions> = {
70
+ margin: WAY_SCHEMA.margin,
71
+ k: WAY_SCHEMA.k,
72
+ rho: { type: "number", min: 0.1, max: 0.8, step: 0.05, default: 0.3, describe: "Two edges join when their common links are this share of the shorter way, or longer." },
73
+ minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A highway has this number of edges or more." },
74
+ hashes: { type: "integer", min: 16, max: 128, step: 16, scale: "log", marks: [16, 32, 64, 128], default: 64, describe: "The number of hash functions of the signature of a way." },
75
+ bands: { type: "integer", min: 4, max: 32, step: 4, scale: "log", marks: [4, 8, 16, 32], default: 32, describe: "The number of bands of the signature. Two ways with one equal band are a candidate pair." },
76
+ near: { type: "number", min: 0, max: 8, step: 1, default: 0, describe: "Two link ends nearer than this grid size are one point. With 0, only equal points are one point." },
77
+ lane: { type: "number", min: 1, max: 16, step: 1, default: 4, describe: "The gap between two lanes. A common stretch shorter than the break-even overlap for this gap does not join two edges." },
78
+ };
79
+
67
80
  /** The declarations of the limits of the tree of sections (`SectionOptions`). */
68
81
  export const SECTION_SCHEMA: OptionSchema<Required<SectionOptions>> = {
69
82
  cap: { type: "integer", min: 1, max: 32, step: 1, scale: "log", marks: [4, 6, 8, 12, 16], default: 8, describe: "The section limit. A section with more nodes splits in four." },