@mark1russell7/highways 0.4.0 → 0.4.2
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 +67 -1
- package/build/index.d.ts +954 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5914 -1419
- package/build/index.js.map +1 -1
- package/package.json +2 -1
- package/src/bench/measures/bundles.ts +101 -2
- package/src/bench/measures/highways.ts +93 -0
- package/src/bench/measures/index.ts +2 -0
- package/src/bench/measures/order.ts +97 -0
- package/src/bench/room.ts +126 -9
- package/src/bench/screen/measures.ts +17 -0
- package/src/bench/screen/screen.ts +87 -9
- package/src/highways/base.ts +126 -0
- package/src/highways/flow-field/index.ts +49 -0
- package/src/highways/gates/ambiguity/index.ts +110 -0
- package/src/highways/gates/breakeven/index.ts +62 -0
- package/src/highways/gates/hysteresis/index.ts +48 -0
- package/src/highways/gates/ids/index.ts +59 -0
- package/src/highways/gates/index.ts +6 -0
- package/src/highways/gates/model.ts +129 -0
- package/src/highways/gates/stretch/index.ts +39 -0
- package/src/highways/grouped.ts +123 -0
- package/src/highways/index.ts +15 -0
- package/src/highways/level-links/index.ts +114 -0
- package/src/highways/meet/index.ts +84 -0
- package/src/highways/model.ts +5 -0
- package/src/highways/od-cells/index.ts +50 -0
- package/src/highways/quickbundles/index.ts +117 -0
- package/src/highways/road-classes/index.ts +74 -0
- package/src/highways/router.ts +1 -1
- package/src/highways/select/index.ts +162 -0
- package/src/highways/stretches.ts +141 -0
- package/src/highways/stubs/index.ts +117 -0
- package/src/highways/weiszfeld/index.ts +105 -0
- package/src/highways/wspd/index.ts +130 -0
- package/src/highways/zipper/index.ts +168 -0
- package/src/index.ts +2 -0
- package/src/order/index.ts +65 -0
- package/src/plugs/justify.ts +48 -0
- package/src/plugs/side.ts +138 -0
- package/src/repair/index.ts +377 -0
- package/src/repair/walk.ts +220 -0
- package/src/room/lanes.ts +109 -10
- package/src/routers/corners.ts +73 -6
- package/src/routers/fan.ts +145 -0
- package/src/routers/index.ts +2 -2
- package/src/routers/shared.ts +107 -8
- package/src/routers/snell.ts +140 -0
- package/src/routers/trunks.ts +43 -2
- package/src/strategies/builtins.ts +149 -7
- package/src/strategies/schemas.ts +173 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { dist, type Point } from "../geom.ts";
|
|
2
|
+
import type { Way } from "./shared.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This function gives the Snell merge point of a route (NAT-035).
|
|
6
|
+
* The route comes from `from`, reaches a run at `at` and goes on along the run to `next`.
|
|
7
|
+
* A unit of length on the run costs `d` against 1 off it. The cheapest route meets the run at the angle `acos(d)`.
|
|
8
|
+
* The function gives the distance from `at` toward `next` of that merge point. A value of 0 or less tells that the turn at `at` is already at most `acos(d)`.
|
|
9
|
+
*/
|
|
10
|
+
export function snellOffset(from: Point, at: Point, next: Point, d: number): number {
|
|
11
|
+
const len = dist(at, next);
|
|
12
|
+
if (len <= 0) return 0;
|
|
13
|
+
const ux = (next.x - at.x) / len;
|
|
14
|
+
const uy = (next.y - at.y) / len;
|
|
15
|
+
const vx = from.x - at.x;
|
|
16
|
+
const vy = from.y - at.y;
|
|
17
|
+
// The foot of `from` on the line of the run is at `along` from `at`, and `from` is `h` from the line.
|
|
18
|
+
const along = vx * ux + vy * uy;
|
|
19
|
+
const h = Math.abs(vx * uy - vy * ux);
|
|
20
|
+
const c = Math.min(0.999, Math.max(0, d));
|
|
21
|
+
return along + (h * c) / Math.sqrt(1 - c * c);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The turn, in radians from 0 to π, at `b` on the way from `a` through `b` to `c`. */
|
|
25
|
+
function turnAt(a: Point, b: Point, c: Point): number {
|
|
26
|
+
const ux = b.x - a.x;
|
|
27
|
+
const uy = b.y - a.y;
|
|
28
|
+
const vx = c.x - b.x;
|
|
29
|
+
const vy = c.y - b.y;
|
|
30
|
+
return Math.abs(Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This function moves the merge points of the ways of the router `shared` to the Snell angle (NAT-035). It changes the ways in place.
|
|
35
|
+
*
|
|
36
|
+
* A way joins a run at a corner `J` when it comes alone from the corner before.
|
|
37
|
+
* The link from `J` to the next corner has other edges of its key.
|
|
38
|
+
* When the way turns at `J` by more than `acos(d)`, it goes directly to a new point `J*` on that link.
|
|
39
|
+
* At `J*`, the way meets the link at the angle `acos(d)`.
|
|
40
|
+
* Each way on the link gets `J*` as a new corner, so the run keeps the same points in each route. Then the same rule moves the exits, on the reversed ways.
|
|
41
|
+
*
|
|
42
|
+
* `firstId` is the first free id of a corner. `clear` tells if a line keeps out of the boxes. `delta` gives the cost share `d` of a link with `f` other edges.
|
|
43
|
+
*/
|
|
44
|
+
export function snellMerges(ways: readonly Way[], firstId: number, clear: (a: Point, b: Point) => boolean, delta: (f: number) => number): void {
|
|
45
|
+
let next = firstId;
|
|
46
|
+
const keys = new Map<string, Way[]>();
|
|
47
|
+
for (const w of ways) {
|
|
48
|
+
if (!w.found) continue;
|
|
49
|
+
const list = keys.get(w.key);
|
|
50
|
+
if (list) list.push(w);
|
|
51
|
+
else keys.set(w.key, [w]);
|
|
52
|
+
}
|
|
53
|
+
for (const group of keys.values()) {
|
|
54
|
+
if (group.length < 2) continue;
|
|
55
|
+
const linkOf = (a: number, b: number): string => (a < b ? `${a}:${b}` : `${b}:${a}`);
|
|
56
|
+
const on = new Map<string, Set<Way>>();
|
|
57
|
+
const add = (k: string, w: Way): void => {
|
|
58
|
+
const set = on.get(k);
|
|
59
|
+
if (set) set.add(w);
|
|
60
|
+
else on.set(k, new Set([w]));
|
|
61
|
+
};
|
|
62
|
+
const drop = (k: string, w: Way): void => {
|
|
63
|
+
const set = on.get(k);
|
|
64
|
+
if (!set) return;
|
|
65
|
+
set.delete(w);
|
|
66
|
+
if (set.size === 0) on.delete(k);
|
|
67
|
+
};
|
|
68
|
+
for (const w of group) for (let i = 1; i < w.ids.length; i++) add(linkOf(w.ids[i - 1]!, w.ids[i]!), w);
|
|
69
|
+
// First the merges, then the exits: an exit is a merge of the reversed way.
|
|
70
|
+
for (const back of [false, true]) {
|
|
71
|
+
for (const w of group) {
|
|
72
|
+
if (back) {
|
|
73
|
+
w.seq.reverse();
|
|
74
|
+
w.ids.reverse();
|
|
75
|
+
}
|
|
76
|
+
for (let i = 1; i < w.ids.length - 1; i++) {
|
|
77
|
+
const before = on.get(linkOf(w.ids[i - 1]!, w.ids[i]!));
|
|
78
|
+
const after = on.get(linkOf(w.ids[i]!, w.ids[i + 1]!));
|
|
79
|
+
if (!before || !after || before.size !== 1 || after.size < 2) continue;
|
|
80
|
+
const P = w.seq[i - 1]!;
|
|
81
|
+
const J = w.seq[i]!;
|
|
82
|
+
const N = w.seq[i + 1]!;
|
|
83
|
+
const d = delta(after.size - 1);
|
|
84
|
+
const turn = turnAt(P, J, N);
|
|
85
|
+
if (turn <= Math.acos(d) + 1e-3) continue;
|
|
86
|
+
const len = dist(J, N);
|
|
87
|
+
let t = Math.min(len, snellOffset(P, J, N, d));
|
|
88
|
+
// A line from P to the merge point that goes through a box moves the point back toward J, at most three times.
|
|
89
|
+
const pointAt = (s: number): Point => ({ x: J.x + ((N.x - J.x) * s) / len, y: J.y + ((N.y - J.y) * s) / len });
|
|
90
|
+
let tries = 0;
|
|
91
|
+
while (t >= 1 && !clear(P, pointAt(t)) && tries < 3) {
|
|
92
|
+
t /= 2;
|
|
93
|
+
tries++;
|
|
94
|
+
}
|
|
95
|
+
if (t < 1 || !clear(P, pointAt(t))) continue;
|
|
96
|
+
const a = w.ids[i - 1]!;
|
|
97
|
+
const j = w.ids[i]!;
|
|
98
|
+
const n = w.ids[i + 1]!;
|
|
99
|
+
if (t >= len - 1e-6) {
|
|
100
|
+
// The merge point is the next corner: the way skips J.
|
|
101
|
+
drop(linkOf(a, j), w);
|
|
102
|
+
drop(linkOf(j, n), w);
|
|
103
|
+
w.seq.splice(i, 1);
|
|
104
|
+
w.ids.splice(i, 1);
|
|
105
|
+
add(linkOf(a, n), w);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const star = pointAt(t);
|
|
109
|
+
const id = next++;
|
|
110
|
+
const users = [...after];
|
|
111
|
+
on.delete(linkOf(j, n));
|
|
112
|
+
// Each way on the link from J to N gets the new corner between them.
|
|
113
|
+
for (const o of users) {
|
|
114
|
+
for (let k = 0; k < o.ids.length - 1; k++) {
|
|
115
|
+
const x = o.ids[k]!;
|
|
116
|
+
const y = o.ids[k + 1]!;
|
|
117
|
+
if ((x === j && y === n) || (x === n && y === j)) {
|
|
118
|
+
o.seq.splice(k + 1, 0, star);
|
|
119
|
+
o.ids.splice(k + 1, 0, id);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
add(linkOf(j, id), o);
|
|
124
|
+
add(linkOf(id, n), o);
|
|
125
|
+
}
|
|
126
|
+
// The way itself goes from P directly to the new corner.
|
|
127
|
+
drop(linkOf(a, j), w);
|
|
128
|
+
drop(linkOf(j, id), w);
|
|
129
|
+
w.seq.splice(i, 1);
|
|
130
|
+
w.ids.splice(i, 1);
|
|
131
|
+
add(linkOf(a, id), w);
|
|
132
|
+
}
|
|
133
|
+
if (back) {
|
|
134
|
+
w.seq.reverse();
|
|
135
|
+
w.ids.reverse();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/routers/trunks.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { nodeIndex, type Bundle, type Edge, type Graph, type GroupRule, type Nod
|
|
|
3
3
|
import { placePlug, type Plug } from "../plugs.ts";
|
|
4
4
|
import { orderLanes, type RunOrder } from "../segments.ts";
|
|
5
5
|
import { WayFinder, type WayOptions } from "../ways.ts";
|
|
6
|
+
import { balancedFan, fanPath, gilbertPoint } from "./fan.ts";
|
|
6
7
|
|
|
7
8
|
/** The options of the trunk router. */
|
|
8
9
|
export interface TrunksOptions extends WayOptions {
|
|
@@ -21,6 +22,20 @@ export interface TrunksOptions extends WayOptions {
|
|
|
21
22
|
* A lane that turns back over the trunk leaves at an earlier point of the trunk. With `split`, each lane leaves at the split point, as in version 0.2.0.
|
|
22
23
|
*/
|
|
23
24
|
readonly leave?: TrunkLeave;
|
|
25
|
+
/**
|
|
26
|
+
* The place of the split point. With `share`, the default, it is at `split` of the way to the center of the targets.
|
|
27
|
+
* With `gilbert`, it is where the flow cost of the trunk and its branches is least, with the exponent `alpha` (EDA-030).
|
|
28
|
+
*/
|
|
29
|
+
readonly splitAt?: TrunkSplit;
|
|
30
|
+
/** The exponent of the flow cost of the split `gilbert`, from 0 to 1. The default is 0.5. */
|
|
31
|
+
readonly alpha?: number;
|
|
32
|
+
/**
|
|
33
|
+
* The shape of each group. With `split`, the default, the edges share a trunk to a split point.
|
|
34
|
+
* With `tree`, the edges grow a balancing-factor tree from the plug, with the factor `bf` (NAT-017).
|
|
35
|
+
*/
|
|
36
|
+
readonly fan?: TrunkFan;
|
|
37
|
+
/** The balancing factor of the fan `tree`, from 0 to 1. 0 gives the least ink, and a larger factor gives more direct routes. The default is 0.4. */
|
|
38
|
+
readonly bf?: number;
|
|
24
39
|
/** The benchmark skips a fixture with more nodes. */
|
|
25
40
|
readonly maxNodes?: number;
|
|
26
41
|
}
|
|
@@ -28,6 +43,12 @@ export interface TrunksOptions extends WayOptions {
|
|
|
28
43
|
/** The place where the lanes of a trunk leave it: where their ways part, or at the split point. */
|
|
29
44
|
export type TrunkLeave = "part" | "split";
|
|
30
45
|
|
|
46
|
+
/** The place of the split point of a trunk: at a share of the way, or at the Gilbert point of the flow cost (EDA-030). */
|
|
47
|
+
export type TrunkSplit = "share" | "gilbert";
|
|
48
|
+
|
|
49
|
+
/** The shape of a group of the router `trunks`: a trunk and a split point, or a balancing-factor tree (NAT-017). */
|
|
50
|
+
export type TrunkFan = "split" | "tree";
|
|
51
|
+
|
|
31
52
|
/** The rule that groups the edges by the collection of their target, in `data.collection`. */
|
|
32
53
|
export const byCollection: GroupRule = (e) => {
|
|
33
54
|
const c = e.data?.["collection"];
|
|
@@ -188,7 +209,28 @@ export function trunksRouter(options: TrunksOptions): Router {
|
|
|
188
209
|
const c = { x: centers.reduce((a, p) => a + p.x, 0) / centers.length, y: centers.reduce((a, p) => a + p.y, 0) / centers.length };
|
|
189
210
|
const plug: Plug = placePlug(s, first.source, c);
|
|
190
211
|
const start = ways.out(plug);
|
|
191
|
-
const
|
|
212
|
+
const key = id.split("\u0000")[2]!;
|
|
213
|
+
if (options.fan === "tree") {
|
|
214
|
+
// Each edge follows the tree from the plug to its target. The routes share the points of the tree.
|
|
215
|
+
const pts = group.map((e, i) => placePlug(targets[i]!, e.target, start));
|
|
216
|
+
const bf = Math.min(1, Math.max(0, options.bf ?? 0.4));
|
|
217
|
+
const fan = balancedFan(start, pts.map((pt) => ways.out(pt)), bf, (from, t) => ways.toPlug(from, pts[t]!) ?? [from, pts[t]!.point]);
|
|
218
|
+
const own: Route[] = group.map((e, i) => ({ edge: e.id, points: dedupe([plug.point, ...fanPath(fan, fan.leaf[i]!)]) }));
|
|
219
|
+
for (const r of own) points.set(r.edge, r.points);
|
|
220
|
+
const order = options.laneOrder ?? "bundle";
|
|
221
|
+
const common = own[0]!.points.slice(0, commonStart(own, 0));
|
|
222
|
+
bundles.push({ id: `b${bundles.length}`, key, node: s.id, edges: orderLanes(common, own, order), trunk: common });
|
|
223
|
+
for (const way of sharedWays(own, common.length)) {
|
|
224
|
+
const users = own.filter((r) => way.edges.includes(r.edge));
|
|
225
|
+
runs.push({ id: `r${runs.length}`, key, points: way.points, edges: orderLanes(way.points, users, order) });
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const free = (p: Point): boolean => !ways.obstacles.inside(p, ways.margin);
|
|
230
|
+
const split =
|
|
231
|
+
options.splitAt === "gilbert"
|
|
232
|
+
? gilbertPoint(start, centers, options.alpha ?? 0.5, free, ways.margin)
|
|
233
|
+
: splitPoint(start, centers, options.split ?? 0.5, free, ways.margin);
|
|
192
234
|
const way = ways.fromPlug(plug, split);
|
|
193
235
|
if (!way) continue;
|
|
194
236
|
const trunk = dedupe(way);
|
|
@@ -211,7 +253,6 @@ export function trunksRouter(options: TrunksOptions): Router {
|
|
|
211
253
|
const exit = leaveTrunk(trunk, branch, clear, 2 * ways.margin);
|
|
212
254
|
if (exit) exits.push({ edge: e.id, exit });
|
|
213
255
|
});
|
|
214
|
-
const key = id.split("\u0000")[2]!;
|
|
215
256
|
if (!part) {
|
|
216
257
|
for (const r of own) points.set(r.edge, r.points);
|
|
217
258
|
const edges = options.laneOrder === "run" || trunk.length < 2 ? laneOrder(dir, end, branches) : orderLanes(trunk, own, "bundle");
|
|
@@ -12,9 +12,26 @@ import { KD_DEFAULTS, kdGroups, type KdOptions } from "../groups/kd/index.ts";
|
|
|
12
12
|
import { byDataGroup, keyGroups, type KeyOptions } from "../groups/keys/index.ts";
|
|
13
13
|
import { quadtreeGroups } from "../groups/quadtree/index.ts";
|
|
14
14
|
import { CORRIDOR_DEFAULTS, corridorPlan, type CorridorOptions } from "../highways/corridors/index.ts";
|
|
15
|
+
import { ambiguityGate, type AmbiguityGuardOptions } from "../highways/gates/ambiguity/index.ts";
|
|
16
|
+
import { breakEvenGate, type BreakEvenOptions } from "../highways/gates/breakeven/index.ts";
|
|
17
|
+
import { hysteresisGate, type HysteresisOptions } from "../highways/gates/hysteresis/index.ts";
|
|
18
|
+
import { stableIdGate, type StableIdOptions } from "../highways/gates/ids/index.ts";
|
|
19
|
+
import { gateStrategy } from "../highways/gates/model.ts";
|
|
20
|
+
import { stretchGate, type StretchOptions } from "../highways/gates/stretch/index.ts";
|
|
21
|
+
import { FLOW_FIELD_DEFAULTS, flowFieldPlan, type FlowFieldOptions } from "../highways/flow-field/index.ts";
|
|
15
22
|
import { keysPlan, type KeysOptions } from "../highways/keys/index.ts";
|
|
23
|
+
import { LEVEL_HIGHWAY_DEFAULTS, levelHighwayPlan, type LevelHighwayOptions } from "../highways/level-links/index.ts";
|
|
24
|
+
import { MEET_DEFAULTS, meetPlan, type MeetOptions } from "../highways/meet/index.ts";
|
|
16
25
|
import type { HighwayInput } from "../highways/model.ts";
|
|
26
|
+
import { OD_CELL_DEFAULTS, odCellPlan, type OdCellOptions } from "../highways/od-cells/index.ts";
|
|
27
|
+
import { QUICKBUNDLES_DEFAULTS, quickBundlesPlan, type QuickBundlesOptions } from "../highways/quickbundles/index.ts";
|
|
28
|
+
import { ROAD_CLASS_DEFAULTS, roadClassPlan, type RoadClassOptions } from "../highways/road-classes/index.ts";
|
|
29
|
+
import { SELECT_DEFAULTS, selectPlan, type SelectOptions } from "../highways/select/index.ts";
|
|
17
30
|
import { sharedWaysPlan, type SharedWaysOptions } from "../highways/shared-ways/index.ts";
|
|
31
|
+
import { STUB_DEFAULTS, stubPlan, type StubOptions } from "../highways/stubs/index.ts";
|
|
32
|
+
import { WEISZFELD_DEFAULTS, weiszfeldPlan, type WeiszfeldOptions } from "../highways/weiszfeld/index.ts";
|
|
33
|
+
import { WSPD_DEFAULTS, wspdPlan, type WspdOptions } from "../highways/wspd/index.ts";
|
|
34
|
+
import { ZIPPER_DEFAULTS, zipperPlan, type ZipperOptions } from "../highways/zipper/index.ts";
|
|
18
35
|
import type { Graph, Result, Router } from "../model.ts";
|
|
19
36
|
import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
|
|
20
37
|
import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
|
|
@@ -27,7 +44,7 @@ import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunk
|
|
|
27
44
|
import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
|
|
28
45
|
import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
|
|
29
46
|
import type { OptionSchema } from "./options.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";
|
|
47
|
+
import { CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CUT_SCHEMA, FLOW_FIELD_SCHEMA, HIGHWAY_SCHEMA, KD_SCHEMA, KEY_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, MEET_SCHEMA, OD_CELL_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA } from "./schemas.ts";
|
|
31
48
|
|
|
32
49
|
/** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
|
|
33
50
|
export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
|
|
@@ -340,14 +357,43 @@ export const groupStrategies: {
|
|
|
340
357
|
}),
|
|
341
358
|
});
|
|
342
359
|
|
|
360
|
+
/** The highway strategy `corridors` (HWY-031): the base of the built-in gates. */
|
|
361
|
+
const corridors: Strategy<"highway", CorridorOptions> = defineStrategy({
|
|
362
|
+
step: "highway",
|
|
363
|
+
name: "corridors",
|
|
364
|
+
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.",
|
|
365
|
+
defaults: { ...CORRIDOR_DEFAULTS } as CorridorOptions,
|
|
366
|
+
schema: CORRIDOR_SCHEMA,
|
|
367
|
+
maxNodes: 10_000,
|
|
368
|
+
run: (input: HighwayInput, o: Readonly<CorridorOptions>) => corridorPlan(input, o),
|
|
369
|
+
});
|
|
370
|
+
|
|
343
371
|
/**
|
|
344
372
|
* The built-in highway strategies: the decisions of the routers `trunks` and `shared` as plans, and corridor signatures (HWY-031).
|
|
373
|
+
* The strategies `corridors+<gate>` put a gate of BUILD-W2A after `corridors`: `breakeven` (HWY-047), `stretch` (HWY-048), `ambiguity` (HWY-049),
|
|
374
|
+
* `hysteresis` (HWY-046) and `ids` (HWY-054). `gateStrategy` puts a gate after any other plan.
|
|
345
375
|
* `follow` routes the edges along a plan, and `highwayRouter` gives a router that does both.
|
|
346
376
|
*/
|
|
347
377
|
export const highwayStrategies: {
|
|
348
378
|
readonly keys: Strategy<"highway", KeysOptions>;
|
|
349
379
|
readonly "shared-ways": Strategy<"highway", SharedWaysOptions>;
|
|
350
380
|
readonly corridors: Strategy<"highway", CorridorOptions>;
|
|
381
|
+
readonly meet: Strategy<"highway", MeetOptions>;
|
|
382
|
+
readonly weiszfeld: Strategy<"highway", WeiszfeldOptions>;
|
|
383
|
+
readonly stubs: Strategy<"highway", StubOptions>;
|
|
384
|
+
readonly zipper: Strategy<"highway", ZipperOptions>;
|
|
385
|
+
readonly "level-links": Strategy<"highway", LevelHighwayOptions>;
|
|
386
|
+
readonly quickbundles: Strategy<"highway", QuickBundlesOptions>;
|
|
387
|
+
readonly "road-classes": Strategy<"highway", RoadClassOptions>;
|
|
388
|
+
readonly "flow-field": Strategy<"highway", FlowFieldOptions>;
|
|
389
|
+
readonly wspd: Strategy<"highway", WspdOptions>;
|
|
390
|
+
readonly "od-cells": Strategy<"highway", OdCellOptions>;
|
|
391
|
+
readonly select: Strategy<"highway", SelectOptions>;
|
|
392
|
+
readonly "corridors+breakeven": Strategy<"highway", CorridorOptions & BreakEvenOptions>;
|
|
393
|
+
readonly "corridors+stretch": Strategy<"highway", CorridorOptions & StretchOptions>;
|
|
394
|
+
readonly "corridors+ambiguity": Strategy<"highway", CorridorOptions & AmbiguityGuardOptions>;
|
|
395
|
+
readonly "corridors+hysteresis": Strategy<"highway", CorridorOptions & HysteresisOptions>;
|
|
396
|
+
readonly "corridors+ids": Strategy<"highway", CorridorOptions & StableIdOptions>;
|
|
351
397
|
} = Object.freeze({
|
|
352
398
|
keys: defineStrategy({
|
|
353
399
|
step: "highway",
|
|
@@ -367,15 +413,111 @@ export const highwayStrategies: {
|
|
|
367
413
|
maxNodes: 10_000,
|
|
368
414
|
run: (input: HighwayInput, o: Readonly<SharedWaysOptions>) => sharedWaysPlan(input, o),
|
|
369
415
|
}),
|
|
370
|
-
corridors
|
|
416
|
+
corridors,
|
|
417
|
+
meet: defineStrategy({
|
|
418
|
+
step: "highway",
|
|
419
|
+
name: "meet",
|
|
420
|
+
describe: "Meet points (HWY-050). Each member of the plan `from` joins its spine where its first route meets the spine, and leaves where they part.",
|
|
421
|
+
defaults: { ...MEET_DEFAULTS } as MeetOptions,
|
|
422
|
+
schema: MEET_SCHEMA,
|
|
423
|
+
maxNodes: 10_000,
|
|
424
|
+
run: (input: HighwayInput, o: Readonly<MeetOptions>) => meetPlan(input, o),
|
|
425
|
+
}),
|
|
426
|
+
weiszfeld: defineStrategy({
|
|
427
|
+
step: "highway",
|
|
428
|
+
name: "weiszfeld",
|
|
429
|
+
describe: "Weiszfeld meeting points (HWY-043). Each highway of the plan `from` gets the merge and split points with the least ink, with a weighted trunk.",
|
|
430
|
+
defaults: { ...WEISZFELD_DEFAULTS } as WeiszfeldOptions,
|
|
431
|
+
schema: WEISZFELD_SCHEMA,
|
|
432
|
+
maxNodes: 10_000,
|
|
433
|
+
run: (input: HighwayInput, o: Readonly<WeiszfeldOptions>) => weiszfeldPlan(input, o),
|
|
434
|
+
}),
|
|
435
|
+
stubs: defineStrategy({
|
|
436
|
+
step: "highway",
|
|
437
|
+
name: "stubs",
|
|
438
|
+
describe: "Stubs (HWY-015, WILD-005). The ends on one side of a box that go in near directions share one plug and one short stub.",
|
|
439
|
+
defaults: { ...STUB_DEFAULTS } as StubOptions,
|
|
440
|
+
schema: STUB_SCHEMA,
|
|
441
|
+
maxNodes: 10_000,
|
|
442
|
+
run: (input: HighwayInput, o: Readonly<StubOptions>) => stubPlan(input, o),
|
|
443
|
+
}),
|
|
444
|
+
zipper: defineStrategy({
|
|
445
|
+
step: "highway",
|
|
446
|
+
name: "zipper",
|
|
447
|
+
describe: "The axon zipper (NAT-022, WILD-037). Two near, nearly parallel legs zip into one line when the shared length is more than the jogs.",
|
|
448
|
+
defaults: { ...ZIPPER_DEFAULTS } as ZipperOptions,
|
|
449
|
+
schema: ZIPPER_SCHEMA,
|
|
450
|
+
maxNodes: 10_000,
|
|
451
|
+
run: (input: HighwayInput, o: Readonly<ZipperOptions>) => zipperPlan(input, o),
|
|
452
|
+
}),
|
|
453
|
+
"level-links": defineStrategy({
|
|
454
|
+
step: "highway",
|
|
455
|
+
name: "level-links",
|
|
456
|
+
describe: "Level links as highways (HWY-056, WILD-034). The edges between two items of a zoom level ride the link of the level.",
|
|
457
|
+
defaults: { ...LEVEL_HIGHWAY_DEFAULTS } as LevelHighwayOptions,
|
|
458
|
+
schema: LEVEL_HIGHWAY_SCHEMA,
|
|
459
|
+
maxNodes: 10_000,
|
|
460
|
+
run: (input: HighwayInput, o: Readonly<LevelHighwayOptions>) => levelHighwayPlan(input, o),
|
|
461
|
+
}),
|
|
462
|
+
quickbundles: defineStrategy({
|
|
463
|
+
step: "highway",
|
|
464
|
+
name: "quickbundles",
|
|
465
|
+
describe: "QuickBundles (HWY-026). One pass puts each edge in the nearest cluster of resampled lines. Each cluster gets highways.",
|
|
466
|
+
defaults: { ...QUICKBUNDLES_DEFAULTS } as QuickBundlesOptions,
|
|
467
|
+
schema: QUICKBUNDLES_SCHEMA,
|
|
468
|
+
maxNodes: 10_000,
|
|
469
|
+
run: (input: HighwayInput, o: Readonly<QuickBundlesOptions>) => quickBundlesPlan(input, o),
|
|
470
|
+
}),
|
|
471
|
+
"road-classes": defineStrategy({
|
|
472
|
+
step: "highway",
|
|
473
|
+
name: "road-classes",
|
|
474
|
+
describe: "Road classes (HWY-034, EDA-050). The links that most ways use become main roads, and a second pass routes the edges onto them.",
|
|
475
|
+
defaults: { ...ROAD_CLASS_DEFAULTS } as RoadClassOptions,
|
|
476
|
+
schema: ROAD_CLASS_SCHEMA,
|
|
477
|
+
maxNodes: 10_000,
|
|
478
|
+
run: (input: HighwayInput, o: Readonly<RoadClassOptions>) => roadClassPlan(input, o),
|
|
479
|
+
}),
|
|
480
|
+
"flow-field": defineStrategy({
|
|
481
|
+
step: "highway",
|
|
482
|
+
name: "flow-field",
|
|
483
|
+
describe: "A flow field for each target (EDA-046, HWY-037). The edges into one side of a box merge like streams on shortest ways to one root.",
|
|
484
|
+
defaults: { ...FLOW_FIELD_DEFAULTS } as FlowFieldOptions,
|
|
485
|
+
schema: FLOW_FIELD_SCHEMA,
|
|
486
|
+
maxNodes: 10_000,
|
|
487
|
+
run: (input: HighwayInput, o: Readonly<FlowFieldOptions>) => flowFieldPlan(input, o),
|
|
488
|
+
}),
|
|
489
|
+
wspd: defineStrategy({
|
|
490
|
+
step: "highway",
|
|
491
|
+
name: "wspd",
|
|
492
|
+
describe: "Well-separated pairs (HWY-018). The edges between the two sets of one pair share one highway.",
|
|
493
|
+
defaults: { ...WSPD_DEFAULTS } as WspdOptions,
|
|
494
|
+
schema: WSPD_SCHEMA,
|
|
495
|
+
maxNodes: 10_000,
|
|
496
|
+
run: (input: HighwayInput, o: Readonly<WspdOptions>) => wspdPlan(input, o),
|
|
497
|
+
}),
|
|
498
|
+
"od-cells": defineStrategy({
|
|
499
|
+
step: "highway",
|
|
500
|
+
name: "od-cells",
|
|
501
|
+
describe: "Origin and destination cells (EDA-033, WILD-032). The edges between two cells of a zoom level share one trunk.",
|
|
502
|
+
defaults: { ...OD_CELL_DEFAULTS } as OdCellOptions,
|
|
503
|
+
schema: OD_CELL_SCHEMA,
|
|
504
|
+
maxNodes: 10_000,
|
|
505
|
+
run: (input: HighwayInput, o: Readonly<OdCellOptions>) => odCellPlan(input, o),
|
|
506
|
+
}),
|
|
507
|
+
select: defineStrategy({
|
|
371
508
|
step: "highway",
|
|
372
|
-
name: "
|
|
373
|
-
describe: "
|
|
374
|
-
defaults: { ...
|
|
375
|
-
schema:
|
|
509
|
+
name: "select",
|
|
510
|
+
describe: "Candidate generation and selection (HWY-045). Some strategies propose highways, and a greedy pass with swaps keeps the best set.",
|
|
511
|
+
defaults: { ...SELECT_DEFAULTS } as SelectOptions,
|
|
512
|
+
schema: SELECT_SCHEMA,
|
|
376
513
|
maxNodes: 10_000,
|
|
377
|
-
run: (input: HighwayInput, o: Readonly<
|
|
514
|
+
run: (input: HighwayInput, o: Readonly<SelectOptions>) => selectPlan(input, o),
|
|
378
515
|
}),
|
|
516
|
+
"corridors+breakeven": gateStrategy(corridors, breakEvenGate),
|
|
517
|
+
"corridors+stretch": gateStrategy(corridors, stretchGate),
|
|
518
|
+
"corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
|
|
519
|
+
"corridors+hysteresis": gateStrategy(corridors, hysteresisGate),
|
|
520
|
+
"corridors+ids": gateStrategy(corridors, stableIdGate),
|
|
379
521
|
});
|
|
380
522
|
|
|
381
523
|
/** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */
|
|
@@ -1,12 +1,26 @@
|
|
|
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 { HighwayBase } from "../highways/base.ts";
|
|
4
5
|
import type { CorridorOptions } from "../highways/corridors/index.ts";
|
|
6
|
+
import type { FlowFieldOptions } from "../highways/flow-field/index.ts";
|
|
7
|
+
import type { GroupSpine } from "../highways/grouped.ts";
|
|
8
|
+
import type { LevelHighwayOptions } from "../highways/level-links/index.ts";
|
|
9
|
+
import type { MeetOptions } from "../highways/meet/index.ts";
|
|
10
|
+
import type { OdCellOptions } from "../highways/od-cells/index.ts";
|
|
11
|
+
import type { QuickBundlesOptions } from "../highways/quickbundles/index.ts";
|
|
12
|
+
import type { RoadClassOptions } from "../highways/road-classes/index.ts";
|
|
13
|
+
import type { SelectOptions } from "../highways/select/index.ts";
|
|
14
|
+
import type { StubOptions } from "../highways/stubs/index.ts";
|
|
15
|
+
import type { WeiszfeldOptions } from "../highways/weiszfeld/index.ts";
|
|
16
|
+
import type { WspdOptions } from "../highways/wspd/index.ts";
|
|
17
|
+
import type { ZipperOptions } from "../highways/zipper/index.ts";
|
|
5
18
|
import type { CornersOptions } from "../routers/corners.ts";
|
|
6
19
|
import type { SectionsOptions } from "../routers/sections.ts";
|
|
7
20
|
import type { SharedOptions } from "../routers/shared.ts";
|
|
8
21
|
import type { TrunksOptions } from "../routers/trunks.ts";
|
|
9
22
|
import type { SectionOptions } from "../sections.ts";
|
|
23
|
+
import type { WayOptions } from "../ways.ts";
|
|
10
24
|
import type { RunOrder } from "../segments.ts";
|
|
11
25
|
import type { ClothoidStrategyOptions, HighwayStrategyOptions, LevelRoomPreset, RadiusOptions, RoomPreset, RouterPreset, SmoothStrategyOptions, TensionOptions } from "./builtins.ts";
|
|
12
26
|
import type { EnumOption, OptionSchema } from "./options.ts";
|
|
@@ -15,7 +29,7 @@ import type { EnumOption, OptionSchema } from "./options.ts";
|
|
|
15
29
|
// The test of the defaults runs each strategy with `optionDefaults` and with no option, and compares the output.
|
|
16
30
|
|
|
17
31
|
/** The options of the way finder (`WayOptions`), for each router that goes around the nodes. */
|
|
18
|
-
const WAY_SCHEMA: OptionSchema<
|
|
32
|
+
const WAY_SCHEMA: OptionSchema<WayOptions> = {
|
|
19
33
|
margin: { type: "number", min: 1, max: 20, step: 1, default: 5, describe: "The distance from a box to the corners of the graph." },
|
|
20
34
|
k: { type: "integer", min: 2, max: 16, step: 1, default: 8, describe: "The number of visible neighbours of each corner." },
|
|
21
35
|
smooth: { type: "boolean", default: false, describe: "With `true`, a way in clear view uses the inflection rule, and the other ways get round corners." },
|
|
@@ -33,7 +47,23 @@ const LANE_ORDER: EnumOption<RunOrder> = {
|
|
|
33
47
|
const RUN_LANE_ORDER: EnumOption<RunOrder> = { ...LANE_ORDER, default: "run" };
|
|
34
48
|
|
|
35
49
|
/** The declarations of the options of the routers `corners` and `corners-smooth`. */
|
|
36
|
-
export const CORNERS_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> =
|
|
50
|
+
export const CORNERS_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> = {
|
|
51
|
+
...WAY_SCHEMA,
|
|
52
|
+
plugs: {
|
|
53
|
+
type: "enum",
|
|
54
|
+
values: ["facing", "side"],
|
|
55
|
+
default: "facing",
|
|
56
|
+
describe: "With `facing`, a free plug faces the other end. With `side`, the search starts from the whole free side, and the plug goes where the way leaves (WILD-001).",
|
|
57
|
+
},
|
|
58
|
+
plugGap: {
|
|
59
|
+
type: "number",
|
|
60
|
+
min: 0,
|
|
61
|
+
max: 24,
|
|
62
|
+
step: 1,
|
|
63
|
+
default: 0,
|
|
64
|
+
describe: "With a gap above 0, the free plugs of a side keep their order and at least this gap (WILD-002). 0 keeps each plug where it is.",
|
|
65
|
+
},
|
|
66
|
+
};
|
|
37
67
|
|
|
38
68
|
/** The declarations of the options of the router `trunks`. */
|
|
39
69
|
export const TRUNKS_SCHEMA: OptionSchema<RouterPreset<TrunksOptions>> = {
|
|
@@ -48,6 +78,20 @@ export const TRUNKS_SCHEMA: OptionSchema<RouterPreset<TrunksOptions>> = {
|
|
|
48
78
|
default: "part",
|
|
49
79
|
describe: "With `part`, a lane leaves the trunk where its way parts from the other ways. With `split`, each lane leaves at the split point.",
|
|
50
80
|
},
|
|
81
|
+
splitAt: {
|
|
82
|
+
type: "enum",
|
|
83
|
+
values: ["share", "gilbert"],
|
|
84
|
+
default: "share",
|
|
85
|
+
describe: "With `share`, the split point is at `split` of the way. With `gilbert`, it is where the flow cost is least (EDA-030).",
|
|
86
|
+
},
|
|
87
|
+
alpha: { type: "number", min: 0, max: 1, step: 0.05, default: 0.5, describe: "The exponent of the flow cost of the split `gilbert`. A smaller value moves the split toward the targets." },
|
|
88
|
+
fan: {
|
|
89
|
+
type: "enum",
|
|
90
|
+
values: ["split", "tree"],
|
|
91
|
+
default: "split",
|
|
92
|
+
describe: "With `split`, the edges of a group share a trunk to a split point. With `tree`, they grow a balancing-factor tree (NAT-017).",
|
|
93
|
+
},
|
|
94
|
+
bf: { type: "number", min: 0, max: 1, step: 0.05, default: 0.4, describe: "The balancing factor of the fan `tree`. 0 gives the least ink, and a larger factor gives more direct routes." },
|
|
51
95
|
};
|
|
52
96
|
|
|
53
97
|
/** The declarations of the options of the routers `shared` and `shared-all`. */
|
|
@@ -63,6 +107,26 @@ export const SHARED_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
|
|
|
63
107
|
describe: "The cost of a link that an edge with the same key uses already, as a share of its length.",
|
|
64
108
|
},
|
|
65
109
|
laneOrder: LANE_ORDER,
|
|
110
|
+
cost: {
|
|
111
|
+
type: "enum",
|
|
112
|
+
values: ["fixed", "flow"],
|
|
113
|
+
default: "fixed",
|
|
114
|
+
describe: "With `fixed`, a shared link costs `discount` of its length. With `flow`, a link with `f` edges costs `len * f^alpha` (EDA-029).",
|
|
115
|
+
},
|
|
116
|
+
alpha: { type: "number", min: 0.1, max: 1, step: 0.05, default: 0.6, describe: "The exponent of the cost `flow`. A smaller value gives more sharing." },
|
|
117
|
+
passes: { type: "integer", min: 1, max: 5, step: 1, default: 1, describe: "In each pass after the first, each edge finds its way again with the ways of the other edges fixed (HWY-039)." },
|
|
118
|
+
order: {
|
|
119
|
+
type: "enum",
|
|
120
|
+
values: ["span", "shortest", "load", "input"],
|
|
121
|
+
default: "span",
|
|
122
|
+
describe: "The order of the edges of a key: the longest first, the shortest first, the most edges between two sections first, or the input order (NAT-024).",
|
|
123
|
+
},
|
|
124
|
+
merge: {
|
|
125
|
+
type: "enum",
|
|
126
|
+
values: ["corner", "snell"],
|
|
127
|
+
default: "corner",
|
|
128
|
+
describe: "With `corner`, a route joins a run at a corner of the graph. With `snell`, it joins at the angle `acos(d)` (NAT-035).",
|
|
129
|
+
},
|
|
66
130
|
};
|
|
67
131
|
|
|
68
132
|
/** The declarations of the options of the highway strategy `corridors` (HWY-031). */
|
|
@@ -232,3 +296,110 @@ export const CUT_SCHEMA: OptionSchema<CutOptions> = {
|
|
|
232
296
|
pull: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 0.5, 1, 2, 4], default: 0.5, describe: "The cost of a line grows with its distance from the middle of the cell, by this factor." },
|
|
233
297
|
guard: { type: "boolean", default: true, describe: "With `true`, each section also tests the quadtree split, and keeps the split that cuts fewer edges." },
|
|
234
298
|
};
|
|
299
|
+
|
|
300
|
+
/** The declaration of the plan that a post step of the step `highway` changes (BUILD-W2B). */
|
|
301
|
+
const HIGHWAY_BASE: EnumOption<HighwayBase> = {
|
|
302
|
+
type: "enum",
|
|
303
|
+
values: ["none", "shared-ways", "corridors"],
|
|
304
|
+
default: "shared-ways",
|
|
305
|
+
describe: "The plan that the strategy changes: no highway, the highways of `shared-ways`, or the highways of `corridors`.",
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/** The declarations of the options of the highway strategy `meet` (HWY-050). */
|
|
309
|
+
export const MEET_SCHEMA: OptionSchema<MeetOptions> = {
|
|
310
|
+
from: HIGHWAY_BASE,
|
|
311
|
+
near: { type: "number", min: 2, max: 40, step: 2, default: 8, describe: "A first route meets the spine where it is this distance from the spine, or nearer." },
|
|
312
|
+
maxTurn: { type: "number", min: 10, max: 90, step: 5, default: 45, describe: "A first route meets the spine only where it runs at this angle to the spine, or less, in degrees." },
|
|
313
|
+
minRun: { type: "number", min: 0, max: 80, step: 5, default: 10, describe: "A member keeps its places when the stretch where its first route meets the spine is shorter than this length." },
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/** The declarations of the options of the highway strategy `weiszfeld` (HWY-043). */
|
|
317
|
+
export const WEISZFELD_SCHEMA: OptionSchema<WeiszfeldOptions> = {
|
|
318
|
+
from: HIGHWAY_BASE,
|
|
319
|
+
alpha: { type: "number", min: 0, max: 1, step: 0.1, default: 0.5, describe: "The weight of the trunk is `k^alpha` for `k` members." },
|
|
320
|
+
steps: { type: "integer", min: 1, max: 50, step: 1, marks: [10, 20, 50], default: 20, describe: "The number of steps of Weiszfeld." },
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/** The declarations of the options of the highway strategy `stubs` (HWY-015 with WILD-005). */
|
|
324
|
+
export const STUB_SCHEMA: OptionSchema<StubOptions> = {
|
|
325
|
+
from: HIGHWAY_BASE,
|
|
326
|
+
omega: { type: "number", min: 10, max: 60, step: 5, default: 30, describe: "The ends of one side of a box whose directions are in a window of this angle share a stub, in degrees." },
|
|
327
|
+
sigma: { type: "number", min: 0.1, max: 0.4, step: 0.05, default: 0.25, describe: "The length of a stub is this share of the distance to the nearest other end." },
|
|
328
|
+
ends: { type: "enum", values: ["source", "target", "both"], default: "both", describe: "The ends that get stubs." },
|
|
329
|
+
minSize: { type: "integer", min: 2, max: 8, step: 1, default: 2, describe: "A stub has this number of edges or more." },
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
/** The declarations of the options of the highway strategy `zipper` (NAT-022 with WILD-037). */
|
|
333
|
+
export const ZIPPER_SCHEMA: OptionSchema<ZipperOptions> = {
|
|
334
|
+
from: HIGHWAY_BASE,
|
|
335
|
+
near: { type: "number", min: 0.5, max: 3, step: 0.25, default: 1, describe: "Two legs zip when they are this distance apart or nearer, in units of the board." },
|
|
336
|
+
maxAngle: { type: "number", min: 5, max: 30, step: 5, default: 15, describe: "Two legs zip when the angle between them is this value or less, in degrees." },
|
|
337
|
+
adhesion: { type: "number", min: 0, max: 1, step: 0.1, default: 0.3, describe: "Two legs zip when this value times their overlap is more than the length that the zip adds." },
|
|
338
|
+
minOverlap: { type: "number", min: 1, max: 5, step: 0.5, default: 1, describe: "Two legs zip when they overlap along this length or more, in units of the board." },
|
|
339
|
+
passes: { type: "integer", min: 1, max: 5, step: 1, default: 1, describe: "The number of passes. Each pass routes the plan and zips the legs again." },
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/** The declarations of the options of the highway strategy `level-links` (HWY-056 with WILD-034). */
|
|
343
|
+
export const LEVEL_HIGHWAY_SCHEMA: OptionSchema<LevelHighwayOptions> = {
|
|
344
|
+
from: HIGHWAY_BASE,
|
|
345
|
+
depth: { type: "integer", min: 1, max: 3, step: 1, default: 1, describe: "The depth of the level in the tree of sections." },
|
|
346
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A level link becomes a highway when this number of edges or more ride it." },
|
|
347
|
+
stretch: { type: "number", min: 1.2, max: 2, step: 0.1, default: 1.5, describe: "An edge rides the link only when its way along the link is this many times its first route, or shorter." },
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/** The declaration of the spine of a group of edges: the shared ways of the group, or one trunk between two terminals. */
|
|
351
|
+
const GROUP_SPINE: EnumOption<GroupSpine> = {
|
|
352
|
+
type: "enum",
|
|
353
|
+
values: ["shared", "trunk"],
|
|
354
|
+
default: "shared",
|
|
355
|
+
describe: "With `shared`, the edges of a group share their ways through the corner graph. With `trunk`, a group gets one trunk between two terminals.",
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/** The declarations of the options of the highway strategy `quickbundles` (HWY-026). */
|
|
359
|
+
export const QUICKBUNDLES_SCHEMA: OptionSchema<QuickBundlesOptions> = {
|
|
360
|
+
theta: { type: "number", min: 1, max: 10, step: 0.5, marks: [1.5, 3, 6], default: 3, describe: "An edge joins a cluster when its distance to the centroid is this value or less, in units of the board." },
|
|
361
|
+
points: { type: "integer", min: 6, max: 24, step: 2, default: 12, describe: "The number of points of each line after the resampling." },
|
|
362
|
+
line: { type: "enum", values: ["routes", "lines"], default: "routes", describe: "The line of each edge: its first route, or the straight line between its plugs." },
|
|
363
|
+
order: { type: "enum", values: ["input", "length"], default: "input", describe: "The order of the edges in the pass: the order of the graph, or the longest line first." },
|
|
364
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A cluster with this number of edges or more gets highways." },
|
|
365
|
+
spine: GROUP_SPINE,
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/** The declarations of the options of the highway strategy `road-classes` (HWY-034 with EDA-050). */
|
|
369
|
+
export const ROAD_CLASS_SCHEMA: OptionSchema<RoadClassOptions> = {
|
|
370
|
+
margin: WAY_SCHEMA.margin,
|
|
371
|
+
k: WAY_SCHEMA.k,
|
|
372
|
+
quantile: { type: "number", min: 0.5, max: 0.99, step: 0.05, marks: [0.8, 0.9, 0.95], default: 0.9, describe: "A link is a highway link when its use is at this quantile of the use of the links, or higher." },
|
|
373
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A highway link and a highway have this number of edges or more." },
|
|
374
|
+
discount: { type: "number", min: 0.1, max: 0.8, step: 0.1, default: 0.5, describe: "The second pass gives each highway link this discount, as a share of its length." },
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/** The declarations of the options of the highway strategy `flow-field` (EDA-046 with HWY-037). */
|
|
378
|
+
export const FLOW_FIELD_SCHEMA: OptionSchema<FlowFieldOptions> = {
|
|
379
|
+
margin: WAY_SCHEMA.margin,
|
|
380
|
+
k: WAY_SCHEMA.k,
|
|
381
|
+
minFanIn: { type: "integer", min: 2, max: 10, step: 1, default: 2, describe: "An end with this number of edges or more on one side of its box gets a field." },
|
|
382
|
+
end: { type: "enum", values: ["target", "source"], default: "target", describe: "The end that gets a field: the target of a fan-in, or the source of a fan-out." },
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** The declarations of the options of the highway strategy `wspd` (HWY-018). */
|
|
386
|
+
export const WSPD_SCHEMA: OptionSchema<WspdOptions> = {
|
|
387
|
+
separation: { type: "number", min: 1, max: 8, step: 0.5, marks: [1, 2, 4], default: 2, describe: "The gap between the two sets of a pair is at least this value times the radius of the larger set." },
|
|
388
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 3, describe: "A pair with this number of edges or more gets a highway." },
|
|
389
|
+
spine: { ...GROUP_SPINE, default: "trunk" },
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/** The declarations of the options of the highway strategy `od-cells` (EDA-033 with WILD-032). */
|
|
393
|
+
export const OD_CELL_SCHEMA: OptionSchema<OdCellOptions> = {
|
|
394
|
+
level: { type: "integer", min: 1, max: 4, step: 1, default: 2, describe: "The depth of the cells in the tree of sections." },
|
|
395
|
+
minGroup: { type: "integer", min: 2, max: 8, step: 1, default: 3, describe: "A pair of cells with this number of edges or more gets a trunk." },
|
|
396
|
+
legMax: { type: "number", min: 0.1, max: 1, step: 0.05, marks: [0.2, 0.3, 0.4], default: 0.3, describe: "An edge rides the trunk only when each of its legs is this share of the span or shorter." },
|
|
397
|
+
spine: { ...GROUP_SPINE, default: "trunk" },
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/** The declarations of the options of the highway strategy `select` (HWY-045). */
|
|
401
|
+
export const SELECT_SCHEMA: OptionSchema<SelectOptions> = {
|
|
402
|
+
generators: { type: "list", default: ["corridors", "quickbundles", "wspd", "road-classes"], describe: "The highway strategies that propose the candidates." },
|
|
403
|
+
lambda: { type: "number", min: 0, max: 4, step: 0.25, marks: [0.5, 1, 2], default: 1, describe: "The weight of the added length of the edges against the ink that a highway saves." },
|
|
404
|
+
swaps: { type: "integer", min: 0, max: 5, step: 1, default: 2, describe: "The number of passes of the local search with one swap." },
|
|
405
|
+
};
|