@mark1russell7/highways 0.9.0 → 0.9.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 +69 -1
- package/build/index.d.ts +420 -9
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4361 -2002
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bench/compare.ts +13 -1
- package/src/bench/measures/curves.ts +55 -1
- package/src/bench/measures/groups.ts +71 -0
- package/src/bench/measures/index.ts +1 -0
- package/src/bench/measures/levels.ts +24 -0
- package/src/bench/measures/room.ts +106 -1
- package/src/bench/measures/symmetry.ts +114 -0
- package/src/bench/screen/exercise.ts +5 -0
- package/src/bench/screen/forms.ts +14 -3
- package/src/bench/screen/measures.ts +40 -3
- package/src/bench/screen/screen.ts +40 -5
- package/src/bench/suite.ts +10 -2
- package/src/highways/fixtures.test-util.ts +13 -0
- package/src/highways/follow.ts +3 -2
- package/src/highways/free-lines.ts +141 -0
- package/src/highways/gaps/index.ts +85 -0
- package/src/highways/index.ts +6 -0
- package/src/highways/interchanges/index.ts +231 -0
- package/src/highways/search/index.ts +170 -0
- package/src/highways/snap.ts +4 -2
- package/src/highways/steiner/index.ts +215 -0
- package/src/highways/streets/index.ts +78 -0
- package/src/model.ts +127 -4
- package/src/routers/attraction/index.ts +61 -0
- package/src/routers/corners.ts +15 -8
- package/src/routers/frames.ts +136 -0
- package/src/routers/index.ts +3 -1
- package/src/routers/plugs.ts +3 -2
- package/src/routers/sections.ts +3 -2
- package/src/routers/shared.ts +70 -7
- package/src/routers/straight.ts +3 -2
- package/src/routers/trunks.ts +3 -2
- package/src/strategies/builtins.ts +56 -1
- package/src/strategies/schemas.ts +69 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Graph, GroupRule, Result, Router } from "../../model.ts";
|
|
2
|
+
import { defineStrategy, type Strategy } from "../../strategies/model.ts";
|
|
3
|
+
import type { OptionSchema } from "../../strategies/options.ts";
|
|
4
|
+
import { sharedRouter } from "../shared.ts";
|
|
5
|
+
import { byCollection } from "../trunks.ts";
|
|
6
|
+
|
|
7
|
+
/** The options of the router `attraction` (HWY-044 of the note on highways: negotiated attraction, PathFinder turned around). */
|
|
8
|
+
export interface AttractionOptions {
|
|
9
|
+
/** The number of passes, from 1 to 10. Each pass after the first routes each edge again from no ways. The default is 3. */
|
|
10
|
+
readonly passes: number;
|
|
11
|
+
/** The pull `a` of the history of a link, from 0 to 1. A link then costs `1 - a h` times more. The default is 0.5. */
|
|
12
|
+
readonly attraction: number;
|
|
13
|
+
/** The cost weight `b` of each edge over the capacity of a link, from 0 to 10. The default is 1. */
|
|
14
|
+
readonly capacityWeight: number;
|
|
15
|
+
/** The width of one lane. The capacity of a link is its free width divided by this width. The default is 4. */
|
|
16
|
+
readonly laneWidth: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The default options of the router `attraction`. */
|
|
20
|
+
export const ATTRACTION_DEFAULTS: Readonly<AttractionOptions> = Object.freeze({ passes: 3, attraction: 0.5, capacityWeight: 1, laneWidth: 4 });
|
|
21
|
+
|
|
22
|
+
/** The declarations of the options of the router `attraction` (HWY-044). */
|
|
23
|
+
export const ATTRACTION_SCHEMA: OptionSchema<AttractionOptions> = {
|
|
24
|
+
passes: { type: "integer", min: 1, max: 10, step: 1, marks: [2, 3, 5], default: 3, describe: "The number of passes. Each later pass routes each edge again from no ways." },
|
|
25
|
+
attraction: { type: "number", min: 0, max: 1, step: 0.05, marks: [0.25, 0.5, 0.75], default: 0.5, describe: "The pull of the history of a link. A busy link of the passes before costs less." },
|
|
26
|
+
capacityWeight: { type: "number", min: 0, max: 10, step: 0.5, marks: [0, 1, 4], default: 1, describe: "The cost weight of each edge over the capacity of a link." },
|
|
27
|
+
laneWidth: { type: "number", min: 1, max: 12, step: 1, marks: [4, 6], default: 4, describe: "The width of one lane. The capacity of a link is its free width divided by this width." },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* This function makes the router `attraction` (HWY-044). It is the router `shared` with a negotiation over the passes.
|
|
32
|
+
* After each pass, the history `h` of each link grows by `use / m`, with `m` the edges of the key. The history stops at `0.9 / a`.
|
|
33
|
+
* In each pass, a link costs `len (1 - a h) (1 + b max(0, use - cap))` times the cost in `shared`.
|
|
34
|
+
* The busy links of the passes before thus pull the edges, and a link over its capacity pushes them away.
|
|
35
|
+
*/
|
|
36
|
+
export function attractionRouter(options: Partial<AttractionOptions> & { readonly name: string; readonly rule?: GroupRule; readonly maxNodes?: number }): Router {
|
|
37
|
+
const o = { ...ATTRACTION_DEFAULTS, ...options };
|
|
38
|
+
return sharedRouter(
|
|
39
|
+
{ name: o.name, ...(o.rule === undefined ? {} : { rule: o.rule }), ...(o.maxNodes === undefined ? {} : { maxNodes: o.maxNodes }) },
|
|
40
|
+
{
|
|
41
|
+
passes: Math.max(1, Math.floor(o.passes)),
|
|
42
|
+
attraction: Math.min(1, Math.max(0, o.attraction)),
|
|
43
|
+
capacityWeight: Math.max(0, o.capacityWeight),
|
|
44
|
+
laneWidth: Math.max(1e-6, o.laneWidth),
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The router strategy `attraction` (HWY-044), with the rule `byCollection` of `shared`. It is not a built-in router, so the benchmark and the hashes of `ROUTERS` do not change.
|
|
51
|
+
* A screen module or a consumer adds it to a registry.
|
|
52
|
+
*/
|
|
53
|
+
export const attractionStrategy: Strategy<"router", AttractionOptions> = defineStrategy({
|
|
54
|
+
step: "router",
|
|
55
|
+
name: "attraction",
|
|
56
|
+
describe: "Negotiated attraction (HWY-044). The router `shared` routes each edge again in each pass, and the busy links of the passes before pull the edges.",
|
|
57
|
+
defaults: { ...ATTRACTION_DEFAULTS },
|
|
58
|
+
schema: ATTRACTION_SCHEMA,
|
|
59
|
+
maxNodes: 10_000,
|
|
60
|
+
run: (graph: Graph, o: Readonly<AttractionOptions>): Result => attractionRouter({ ...o, name: "attraction", rule: byCollection }).route(graph),
|
|
61
|
+
});
|
package/src/routers/corners.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { center, type Point } from "../geom.ts";
|
|
2
|
-
import { nodeIndex, type Edge, type Graph, type Node, type Result, type Route, type Router } from "../model.ts";
|
|
2
|
+
import { nodeIndex, fixedPortPoint, resolvePorts, splitFrames, type Edge, type Graph, type Node, type Result, type Route, type Router } from "../model.ts";
|
|
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
6
|
import type { PlugPair, Strategy } from "../strategies/model.ts";
|
|
7
|
-
import { WayFinder
|
|
7
|
+
import { WayFinder } from "../ways.ts";
|
|
8
|
+
import { frameRoutes, type FrameOptions } from "./frames.ts";
|
|
8
9
|
|
|
9
10
|
/** The ways to place a free plug of the corner router. */
|
|
10
11
|
export type PlugPlace = "facing" | "side";
|
|
11
12
|
|
|
12
13
|
/** The options of the corner router. */
|
|
13
|
-
export interface CornersOptions extends
|
|
14
|
+
export interface CornersOptions extends FrameOptions {
|
|
14
15
|
/** The name in the benchmark. */
|
|
15
16
|
readonly name: string;
|
|
16
17
|
/** The benchmark skips a fixture with more nodes. */
|
|
@@ -38,14 +39,21 @@ export interface CornersOptions extends WayOptions {
|
|
|
38
39
|
* This function makes a router that goes around the nodes.
|
|
39
40
|
* Each edge leaves its plug along the normal of the side. It goes a shortest way (A*) through a sparse visibility graph
|
|
40
41
|
* of the box corners, without the needless corners. It enters the target plug along its normal.
|
|
42
|
+
* An inner edge (`splitFrames`) gets an inner route in the bounds of its frame (`frameRoutes`), after the outer routes.
|
|
41
43
|
*/
|
|
42
44
|
export function cornersRouter(options: CornersOptions): Router {
|
|
43
45
|
const router: Router = {
|
|
44
46
|
name: options.name,
|
|
45
|
-
route(
|
|
47
|
+
route(input: Graph): Result {
|
|
48
|
+
const { graph, inner } = splitFrames(resolvePorts(input));
|
|
46
49
|
const byId = nodeIndex(graph);
|
|
47
50
|
const ways = new WayFinder(graph.nodes, options);
|
|
48
51
|
const routes: Route[] = [];
|
|
52
|
+
// The inner routes come after the outer routes.
|
|
53
|
+
const done = (): Result => {
|
|
54
|
+
if (inner.length > 0) routes.push(...frameRoutes(graph, inner, options));
|
|
55
|
+
return { routes };
|
|
56
|
+
};
|
|
49
57
|
if (!options.plug && (options.plugs ?? "facing") === "facing" && !((options.plugGap ?? 0) > 0)) {
|
|
50
58
|
for (const e of graph.edges) {
|
|
51
59
|
const s = byId.get(e.source.node);
|
|
@@ -55,7 +63,7 @@ export function cornersRouter(options: CornersOptions): Router {
|
|
|
55
63
|
const pt = placePlug(t, e.target, center(s.box));
|
|
56
64
|
routes.push({ edge: e.id, points: ways.between(ps, pt, new Set<Node>([s, t])) });
|
|
57
65
|
}
|
|
58
|
-
return
|
|
66
|
+
return done();
|
|
59
67
|
}
|
|
60
68
|
// First the plugs of each edge, then the gaps of the plugs of each side, then the ways between the plugs.
|
|
61
69
|
const ends: [Edge, Node, Node, Plug, Plug][] = [];
|
|
@@ -74,7 +82,7 @@ export function cornersRouter(options: CornersOptions): Router {
|
|
|
74
82
|
}
|
|
75
83
|
if ((options.plugGap ?? 0) > 0) spacePlugs(ends, options.plugGap!);
|
|
76
84
|
for (const [e, s, t, ps, pt] of ends) routes.push({ edge: e.id, points: ways.between(ps, pt, new Set<Node>([s, t])) });
|
|
77
|
-
return
|
|
85
|
+
return done();
|
|
78
86
|
},
|
|
79
87
|
};
|
|
80
88
|
return options.maxNodes === undefined ? router : { ...router, maxNodes: options.maxNodes };
|
|
@@ -104,8 +112,7 @@ function spacePlugs(ends: [Edge, Node, Node, Plug, Plug][], gap: number): void {
|
|
|
104
112
|
[e.source, s, ps, pt, 3],
|
|
105
113
|
[e.target, t, pt, ps, 4],
|
|
106
114
|
] as const) {
|
|
107
|
-
|
|
108
|
-
if (port?.at !== undefined) continue;
|
|
115
|
+
if (fixedPortPoint(node, end) !== undefined) continue;
|
|
109
116
|
const key = JSON.stringify([node.id, plug.normal.x, plug.normal.y]);
|
|
110
117
|
let side = sides.get(key);
|
|
111
118
|
if (!side) sides.set(key, (side = { node, normal: plug.normal, items: [] }));
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { center, dist, type Box, type Point } from "../geom.ts";
|
|
2
|
+
import { nodeIndex, type Graph, type InnerEdge, type Node, type Route } from "../model.ts";
|
|
3
|
+
import { placePlug, type Plug } from "../plugs.ts";
|
|
4
|
+
import { WayFinder, type WayOptions } from "../ways.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The rule for a node box that is not fully inside the box of its frame.
|
|
8
|
+
* With `throw`, the router throws a `RangeError`. With `skip`, the node is not an obstacle, and its inner edges get no route.
|
|
9
|
+
* With `clamp`, the router cuts the box to the bounds of the frame.
|
|
10
|
+
*/
|
|
11
|
+
export type Outside = "throw" | "skip" | "clamp";
|
|
12
|
+
|
|
13
|
+
/** The options of the inner routes: the options of the way finder, the rule `outside` and the inset `frameInset`. */
|
|
14
|
+
export interface FrameOptions extends WayOptions {
|
|
15
|
+
/** The rule for a node box outside the box of its frame. The default is `throw`. */
|
|
16
|
+
readonly outside?: Outside;
|
|
17
|
+
/** The distance from the walls of a frame to the bounds of its inner routes. The default is the margin of the router. */
|
|
18
|
+
readonly frameInset?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The inside of one frame for the inner edges to one depth: the bounds, the way finder and the obstacles by id. */
|
|
22
|
+
interface Room {
|
|
23
|
+
readonly ways: WayFinder;
|
|
24
|
+
readonly nodes: ReadonlyMap<string, Node>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The tiny gap between the bounds and the band of a wall, so that a way along the bounds stays clear of the wall. */
|
|
28
|
+
const EPS = 1e-6;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* This function gives the inner routes of the inner edges (`splitFrames`). Each route stays in the bounds of its frame:
|
|
32
|
+
* the box of the frame, smaller by `frameInset`. It goes around the other nodes of the frame.
|
|
33
|
+
* It leaves the plug of the frame into the frame, along the normal of the wall. Each route has the id of its frame in `frame`.
|
|
34
|
+
*/
|
|
35
|
+
export function frameRoutes(graph: Graph, inner: readonly InnerEdge[], options: FrameOptions): Route[] {
|
|
36
|
+
const byId = nodeIndex(graph);
|
|
37
|
+
const children = new Map<string, Node[]>();
|
|
38
|
+
for (const n of graph.nodes) {
|
|
39
|
+
if (n.parent === undefined) continue;
|
|
40
|
+
const list = children.get(n.parent);
|
|
41
|
+
if (list) list.push(n);
|
|
42
|
+
else children.set(n.parent, [n]);
|
|
43
|
+
}
|
|
44
|
+
const margin = options.margin ?? 5;
|
|
45
|
+
const inset = options.frameInset ?? margin;
|
|
46
|
+
const rooms = new Map<string, Room>();
|
|
47
|
+
const routes: Route[] = [];
|
|
48
|
+
for (const { edge, frame, plug } of inner) {
|
|
49
|
+
const f = byId.get(frame)!;
|
|
50
|
+
const [own, far] = plug === "source" ? [edge.source, edge.target] : [edge.target, edge.source];
|
|
51
|
+
// The nodes between the frame and the far end: each of them holds the far end, so it is not an obstacle.
|
|
52
|
+
const chain: string[] = [];
|
|
53
|
+
for (let p = byId.get(far.node)!.parent!; p !== frame; p = byId.get(p)!.parent!) chain.push(p);
|
|
54
|
+
const key = JSON.stringify([frame, ...chain]);
|
|
55
|
+
let room = rooms.get(key);
|
|
56
|
+
if (!room) rooms.set(key, (room = makeRoom(f, chain, children, byId, inset, options)));
|
|
57
|
+
const end = room.nodes.get(far.node);
|
|
58
|
+
if (!end) continue;
|
|
59
|
+
const fp = placePlug(f, own, center(end.box));
|
|
60
|
+
const inward = { x: -fp.normal.x, y: -fp.normal.y };
|
|
61
|
+
// The way starts on the bounds, straight in from the plug of the wall.
|
|
62
|
+
const start: Plug = { point: { x: fp.point.x + inward.x * inset, y: fp.point.y + inward.y * inset }, normal: inward };
|
|
63
|
+
const ep = placePlug(end, far, fp.point);
|
|
64
|
+
const ends = new Set<Node>([end]);
|
|
65
|
+
const points =
|
|
66
|
+
plug === "source" ? stub(fp.point, room.ways.between(start, ep, ends)) : stub(fp.point, room.ways.between(ep, start, ends).reverse()).reverse();
|
|
67
|
+
routes.push({ edge: edge.id, points, frame });
|
|
68
|
+
}
|
|
69
|
+
return routes;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** This function puts the plug of the wall before a way that starts on the bounds. A first point on the line of the stub goes. */
|
|
73
|
+
function stub(plug: Point, way: Point[]): Point[] {
|
|
74
|
+
const [a, b] = way;
|
|
75
|
+
if (!a || dist(a, plug) === 0) return way;
|
|
76
|
+
if (b && Math.abs((a.x - plug.x) * (b.y - plug.y) - (a.y - plug.y) * (b.x - plug.x)) < 1e-9 && (a.x - plug.x) * (b.x - a.x) + (a.y - plug.y) * (b.y - a.y) > 0)
|
|
77
|
+
return [plug, ...way.slice(1)];
|
|
78
|
+
return [plug, ...way];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* This function makes the room of a frame for the far ends below the nodes of `chain` (the nearest first).
|
|
83
|
+
* The obstacles are the children of the frame and of each node of the chain, except the nodes of the chain.
|
|
84
|
+
* Four walls close the bounds, so that no way leaves them.
|
|
85
|
+
*/
|
|
86
|
+
function makeRoom(f: Node, chain: readonly string[], children: ReadonlyMap<string, readonly Node[]>, byId: ReadonlyMap<string, Node>, inset: number, options: FrameOptions): Room {
|
|
87
|
+
const margin = options.margin ?? 5;
|
|
88
|
+
const bounds = shrink(f.box, inset);
|
|
89
|
+
const skip = new Set(chain);
|
|
90
|
+
const nodes = new Map<string, Node>();
|
|
91
|
+
for (const holder of [f.id, ...chain]) {
|
|
92
|
+
for (const n of children.get(holder) ?? []) {
|
|
93
|
+
if (skip.has(n.id)) continue;
|
|
94
|
+
const box = byId.get(holder)!.box;
|
|
95
|
+
if (inside(n.box, box)) {
|
|
96
|
+
nodes.set(n.id, n);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const rule = options.outside ?? "throw";
|
|
100
|
+
if (rule === "throw")
|
|
101
|
+
throw new RangeError(`The box of the node "${n.id}" is not inside the box of its frame "${holder}". The option outside can skip or clamp it.`);
|
|
102
|
+
if (rule === "skip") continue;
|
|
103
|
+
const cut = cutBox(n.box, bounds);
|
|
104
|
+
if (cut) nodes.set(n.id, { ...n, box: cut, ...(n.ports ? { ports: n.ports.map((p) => (p.at ? { ...p, at: { x: p.at.x + n.box.x - cut.x, y: p.at.y + n.box.y - cut.y } } : p)) } : {}) });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// The band of each wall starts half a margin outside the bounds, so that a way keeps in the bounds.
|
|
108
|
+
const w = shrink(bounds, -(margin / 2 + EPS));
|
|
109
|
+
const t = 2 * margin + 1;
|
|
110
|
+
const walls: Node[] = [
|
|
111
|
+
{ x: w.x - t, y: w.y - t, w: w.w + 2 * t, h: t },
|
|
112
|
+
{ x: w.x - t, y: w.y + w.h, w: w.w + 2 * t, h: t },
|
|
113
|
+
{ x: w.x - t, y: w.y, w: t, h: w.h },
|
|
114
|
+
{ x: w.x + w.w, y: w.y, w: t, h: w.h },
|
|
115
|
+
].map((box, i) => ({ id: `\u0000wall${i}`, box }));
|
|
116
|
+
return { ways: new WayFinder([...nodes.values(), ...walls], options), nodes };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** This function gives the box, smaller by `d` on each side. A negative `d` makes it larger. */
|
|
120
|
+
function shrink(b: Box, d: number): Box {
|
|
121
|
+
return { x: b.x + d, y: b.y + d, w: Math.max(0, b.w - 2 * d), h: Math.max(0, b.h - 2 * d) };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** This function tells if the box `a` is inside the box `b`. */
|
|
125
|
+
function inside(a: Box, b: Box): boolean {
|
|
126
|
+
return a.x >= b.x && a.y >= b.y && a.x + a.w <= b.x + b.w && a.y + a.h <= b.y + b.h;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** This function gives the part of the box `a` inside the box `b`, or `undefined` when they have no common area. */
|
|
130
|
+
function cutBox(a: Box, b: Box): Box | undefined {
|
|
131
|
+
const x = Math.max(a.x, b.x);
|
|
132
|
+
const y = Math.max(a.y, b.y);
|
|
133
|
+
const w = Math.min(a.x + a.w, b.x + b.w) - x;
|
|
134
|
+
const h = Math.min(a.y + a.h, b.y + b.h) - y;
|
|
135
|
+
return w > 0 && h > 0 ? { x, y, w, h } : undefined;
|
|
136
|
+
}
|
package/src/routers/index.ts
CHANGED
|
@@ -9,7 +9,9 @@ import { byCollection, trunksRouter } from "./trunks.ts";
|
|
|
9
9
|
export { boxExit, straightRouter } from "./straight.ts";
|
|
10
10
|
export { plugsRouter } from "./plugs.ts";
|
|
11
11
|
export { cornersRouter, type CornersOptions } from "./corners.ts";
|
|
12
|
-
export {
|
|
12
|
+
export { frameRoutes, type FrameOptions, type Outside } from "./frames.ts";
|
|
13
|
+
export { sharedRouter, type ShareCost, type SharedNegotiation, type SharedOptions, type ShareMerge, type ShareOrder } from "./shared.ts";
|
|
14
|
+
export { ATTRACTION_DEFAULTS, ATTRACTION_SCHEMA, attractionRouter, attractionStrategy, type AttractionOptions } from "./attraction/index.ts";
|
|
13
15
|
export { sectionsRouter, type SectionsOptions } from "./sections.ts";
|
|
14
16
|
export { byCollection, groupEdges, laneOrder, splitPoint, trunksRouter, type TrunkFan, type TrunkSplit, type TrunksOptions } from "./trunks.ts";
|
|
15
17
|
|
package/src/routers/plugs.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inflectionCurve } from "../curves.ts";
|
|
2
2
|
import { center } from "../geom.ts";
|
|
3
|
-
import { nodeIndex, type Graph, type Result, type Route, type Router } from "../model.ts";
|
|
3
|
+
import { nodeIndex, outerGraph, type Graph, type Result, type Route, type Router } from "../model.ts";
|
|
4
4
|
import { placePlug } from "../plugs.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -9,7 +9,8 @@ import { placePlug } from "../plugs.ts";
|
|
|
9
9
|
*/
|
|
10
10
|
export const plugsRouter: Router = {
|
|
11
11
|
name: "plugs",
|
|
12
|
-
route(
|
|
12
|
+
route(input: Graph): Result {
|
|
13
|
+
const graph = outerGraph(input);
|
|
13
14
|
const byId = nodeIndex(graph);
|
|
14
15
|
const routes: Route[] = [];
|
|
15
16
|
for (const e of graph.edges) {
|
package/src/routers/sections.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { BACKTRACK_MIN, goesOutAndBack } from "../bench/links.ts";
|
|
|
2
2
|
import { center, dist, pointSegmentDistance, polylineLength, type Point } from "../geom.ts";
|
|
3
3
|
import { LEVEL_LINK_DEFAULTS, type LevelLinkOptions } from "../levels.ts";
|
|
4
4
|
import { depthLevels, type DepthLevelOptions } from "../levels/depths/index.ts";
|
|
5
|
-
import { nodeIndex, type Graph, type Level, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
5
|
+
import { nodeIndex, outerGraph, type Graph, type Level, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
6
6
|
import { placePlug } from "../plugs.ts";
|
|
7
7
|
import { allSections, buildSections, checkSections, SECTION_DEFAULTS, type Section, type SectionOptions } from "../sections.ts";
|
|
8
8
|
import { orderLanes, type RunOrder } from "../segments.ts";
|
|
@@ -91,7 +91,8 @@ const dedupe = (p: readonly Point[]): Point[] => p.filter((q, i) => i === 0 || d
|
|
|
91
91
|
export function sectionsRouter(options: SectionsOptions): Router {
|
|
92
92
|
const router: Router = {
|
|
93
93
|
name: options.name,
|
|
94
|
-
route(
|
|
94
|
+
route(input: Graph): Result {
|
|
95
|
+
const graph = outerGraph(input);
|
|
95
96
|
const stretch = options.stretch ?? SECTIONS_DEFAULTS.stretch;
|
|
96
97
|
const stretchPad = options.stretchPad ?? SECTIONS_DEFAULTS.stretchPad;
|
|
97
98
|
const hubSlack = options.hubSlack ?? SECTIONS_DEFAULTS.hubSlack;
|
package/src/routers/shared.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { center, dist, type Point } from "../geom.ts";
|
|
2
|
-
import { nodeIndex, type Edge, type Graph, type GroupRule, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
2
|
+
import { nodeIndex, outerGraph, 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
5
|
import { stretchLaneOrders, type LaneStretchOrder } from "../lane-orders/stretch/index.ts";
|
|
@@ -77,6 +77,21 @@ export interface SharedOptions extends WayOptions {
|
|
|
77
77
|
readonly maxNodes?: number;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The negotiation of the router `attraction` (HWY-044, PathFinder turned around). Each pass routes each edge again from no ways.
|
|
82
|
+
* A link costs `len (1 - attraction h) (1 + weight max(0, use - cap))` more than in `shared`, with `h` the history of the link.
|
|
83
|
+
*/
|
|
84
|
+
export interface SharedNegotiation {
|
|
85
|
+
/** The number of passes, 1 or more. */
|
|
86
|
+
readonly passes: number;
|
|
87
|
+
/** The pull of the history of a link, from 0 to 1. */
|
|
88
|
+
readonly attraction: number;
|
|
89
|
+
/** The cost weight of each edge over the capacity of a link, 0 or more. */
|
|
90
|
+
readonly capacityWeight: number;
|
|
91
|
+
/** The width of one lane. The capacity of a link is its free width divided by this width, and 1 or more. */
|
|
92
|
+
readonly laneWidth: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
/** A way of one edge through the corner graph. */
|
|
81
96
|
export interface Way {
|
|
82
97
|
readonly edge: Edge;
|
|
@@ -114,7 +129,7 @@ function turn(from: Point, base: Point, to: Point): number {
|
|
|
114
129
|
* Thus the edges of a key come together on common ways. Each stretch that several edges share has the same points in each route.
|
|
115
130
|
* The result gives each stretch as a run, with the order of its lanes.
|
|
116
131
|
*/
|
|
117
|
-
export function sharedRouter(options: SharedOptions): Router {
|
|
132
|
+
export function sharedRouter(options: SharedOptions, negotiation?: SharedNegotiation): Router {
|
|
118
133
|
const discount = Math.min(1, Math.max(0.05, options.discount ?? 0.5));
|
|
119
134
|
const flow = options.cost === "flow";
|
|
120
135
|
const alpha = Math.min(1, Math.max(0.1, options.alpha ?? 0.6));
|
|
@@ -133,7 +148,8 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
133
148
|
};
|
|
134
149
|
const router: Router = {
|
|
135
150
|
name: options.name,
|
|
136
|
-
route(
|
|
151
|
+
route(input: Graph): Result {
|
|
152
|
+
const graph = outerGraph(input);
|
|
137
153
|
const byId = nodeIndex(graph);
|
|
138
154
|
const ways = new WayFinder(graph.nodes, options);
|
|
139
155
|
const corners = ways.corners;
|
|
@@ -172,8 +188,11 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
172
188
|
if (wd === undefined) widths.set(k, (wd = channelWidth(ways.obstacles, corners.points[i]!, corners.points[j]!)));
|
|
173
189
|
return wd;
|
|
174
190
|
};
|
|
175
|
-
/**
|
|
176
|
-
|
|
191
|
+
/**
|
|
192
|
+
* This function routes one way. `before` holds the edges of the key on each link in the pass before, for the option `reuse`.
|
|
193
|
+
* `history` holds the history of each link of the key, for the negotiation of `attraction`.
|
|
194
|
+
*/
|
|
195
|
+
const route = (w: Way, before?: ReadonlyMap<number, number>, history?: ReadonlyMap<number, number>): void => {
|
|
177
196
|
let used = discounted.get(w.key);
|
|
178
197
|
if (!used) discounted.set(w.key, (used = new Map()));
|
|
179
198
|
const mine = used;
|
|
@@ -192,6 +211,20 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
192
211
|
cost = (i, j, d) => plain(i, j, d) / (1 + reuse * (before.get(link(i, j)) ?? 0)) ** reuseExponent;
|
|
193
212
|
floor /= (1 + reuse * most) ** reuseExponent;
|
|
194
213
|
}
|
|
214
|
+
if (negotiation !== undefined && history !== undefined) {
|
|
215
|
+
const plain = cost;
|
|
216
|
+
const a = negotiation.attraction;
|
|
217
|
+
const b = negotiation.capacityWeight;
|
|
218
|
+
let most = 0;
|
|
219
|
+
for (const h of history.values()) most = Math.max(most, h);
|
|
220
|
+
cost = (i, j, d) => {
|
|
221
|
+
const k = link(i, j);
|
|
222
|
+
const use = (mine.get(k)?.length ?? 0) + 1;
|
|
223
|
+
const cap = Math.max(1, Math.floor(widthOf(i, j) / negotiation.laneWidth));
|
|
224
|
+
return plain(i, j, d) * (1 - a * (history.get(k) ?? 0)) * (1 + b * Math.max(0, use - cap));
|
|
225
|
+
};
|
|
226
|
+
floor *= 1 - a * most;
|
|
227
|
+
}
|
|
195
228
|
const way = corners.path(ways.out(w.ps), ways.out(w.pt), cost, floor);
|
|
196
229
|
if (!way) return;
|
|
197
230
|
w.found = true;
|
|
@@ -207,9 +240,39 @@ export function sharedRouter(options: SharedOptions): Router {
|
|
|
207
240
|
if (flow) top.set(w.key, Math.max(top.get(w.key) ?? 0, mine.get(link(a, b))!.length));
|
|
208
241
|
}
|
|
209
242
|
};
|
|
210
|
-
|
|
243
|
+
/**
|
|
244
|
+
* This function gives the passes of the negotiation (HWY-044). After each pass, the history of each link grows by `use / m`,
|
|
245
|
+
* with `m` the edges of the key, up to `0.9 / attraction`. Each later pass routes each edge again from no ways.
|
|
246
|
+
*/
|
|
247
|
+
const negotiate = (list: readonly Way[]): void => {
|
|
248
|
+
const nego = negotiation!;
|
|
249
|
+
const histories = new Map<string, Map<number, number>>();
|
|
250
|
+
const size = new Map<string, number>();
|
|
251
|
+
for (const w of list) size.set(w.key, (size.get(w.key) ?? 0) + 1);
|
|
252
|
+
const most = nego.attraction > 0 ? 0.9 / nego.attraction : Infinity;
|
|
253
|
+
for (let pass = 0; pass < Math.max(1, Math.floor(nego.passes)); pass++) {
|
|
254
|
+
if (pass > 0) {
|
|
255
|
+
for (const [key, links] of discounted) {
|
|
256
|
+
let h = histories.get(key);
|
|
257
|
+
if (!h) histories.set(key, (h = new Map()));
|
|
258
|
+
const m = size.get(key) ?? 1;
|
|
259
|
+
for (const [k, users] of links) h.set(k, Math.min(most, (h.get(k) ?? 0) + users.length / m));
|
|
260
|
+
}
|
|
261
|
+
discounted.clear();
|
|
262
|
+
top.clear();
|
|
263
|
+
for (const w of list) {
|
|
264
|
+
w.found = false;
|
|
265
|
+
w.seq = [];
|
|
266
|
+
w.ids = [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const w of list) route(w, undefined, histories.get(w.key) ?? new Map());
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
if (negotiation !== undefined) negotiate(order);
|
|
273
|
+
else for (const w of order) route(w);
|
|
211
274
|
// Each later pass takes each edge off its links and finds its way again, with the ways of the other edges fixed.
|
|
212
|
-
for (let pass = 1; pass < passes; pass++) {
|
|
275
|
+
for (let pass = 1; negotiation === undefined && pass < passes; pass++) {
|
|
213
276
|
if (reuse > 0) {
|
|
214
277
|
// Winding Roads (HWY-005): each edge finds its way again from no ways, and the busy links of the pass before pull it.
|
|
215
278
|
const before = new Map<string, Map<number, number>>();
|
package/src/routers/straight.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { center, type Box, type Point } from "../geom.ts";
|
|
2
|
-
import { fixedPortPoint, nodeIndex, type Graph, type Result, type Route, type Router } from "../model.ts";
|
|
2
|
+
import { fixedPortPoint, nodeIndex, outerGraph, type Graph, type Result, type Route, type Router } from "../model.ts";
|
|
3
3
|
|
|
4
4
|
/** This function gives the point where the ray from the center of the box to `toward` leaves the box. */
|
|
5
5
|
export function boxExit(box: Box, toward: Point): Point {
|
|
@@ -19,7 +19,8 @@ export function boxExit(box: Box, toward: Point): Point {
|
|
|
19
19
|
*/
|
|
20
20
|
export const straightRouter: Router = {
|
|
21
21
|
name: "straight",
|
|
22
|
-
route(
|
|
22
|
+
route(input: Graph): Result {
|
|
23
|
+
const graph = outerGraph(input);
|
|
23
24
|
const byId = nodeIndex(graph);
|
|
24
25
|
const routes: Route[] = [];
|
|
25
26
|
for (const e of graph.edges) {
|
package/src/routers/trunks.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { center, dist, type Point } from "../geom.ts";
|
|
2
|
-
import { nodeIndex, type Bundle, type Edge, type Graph, type GroupRule, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
2
|
+
import { nodeIndex, outerGraph, type Bundle, type Edge, type Graph, type GroupRule, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
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";
|
|
@@ -209,7 +209,8 @@ export function trunksRouter(options: TrunksOptions): Router {
|
|
|
209
209
|
const rule = groupRuleOf(options.rule);
|
|
210
210
|
const router: Router = {
|
|
211
211
|
name: options.name,
|
|
212
|
-
route(
|
|
212
|
+
route(input: Graph): Result {
|
|
213
|
+
const graph = outerGraph(input);
|
|
213
214
|
const byId = nodeIndex(graph);
|
|
214
215
|
const ways = new WayFinder(graph.nodes, options);
|
|
215
216
|
const points = new Map<string, readonly Point[]>();
|
|
@@ -44,6 +44,11 @@ import { TERRITORY_DEFAULTS, territoryPlan, type TerritoryOptions } from "../hig
|
|
|
44
44
|
import { CONSENSUS_DEFAULTS, consensusPlan, type ConsensusOptions } from "../highways/consensus/index.ts";
|
|
45
45
|
import { P_HUB_DEFAULTS, pHubPlan, type PHubOptions } from "../highways/p-hub/index.ts";
|
|
46
46
|
import { SCALE_SPACE_DEFAULTS, scaleSpacePlan, type ScaleSpaceOptions } from "../highways/scale-space/index.ts";
|
|
47
|
+
import { GAP_DEFAULTS, gapPlan, type GapOptions } from "../highways/gaps/index.ts";
|
|
48
|
+
import { INTERCHANGE_DEFAULTS, interchangePlan, type InterchangeOptions } from "../highways/interchanges/index.ts";
|
|
49
|
+
import { SEARCH_DEFAULTS, searchPlan, type SearchOptions } from "../highways/search/index.ts";
|
|
50
|
+
import { STEINER_DEFAULTS, steinerPlan, type SteinerOptions } from "../highways/steiner/index.ts";
|
|
51
|
+
import { STREET_DEFAULTS, streetPlan, type StreetOptions } from "../highways/streets/index.ts";
|
|
47
52
|
import { RIDGE_DEFAULTS, ridgePlan, type RidgeOptions } from "../highways/ridges/index.ts";
|
|
48
53
|
import { SOFT_KEY_DEFAULTS, softKeyPlan, type SoftKeyOptions } from "../highways/soft-keys/index.ts";
|
|
49
54
|
import { FLOW_FIELD_DEFAULTS, flowFieldPlan, type FlowFieldOptions } from "../highways/flow-field/index.ts";
|
|
@@ -83,7 +88,7 @@ import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type
|
|
|
83
88
|
import type { OptionSchema } from "./options.ts";
|
|
84
89
|
import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
|
|
85
90
|
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";
|
|
91
|
+
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, STEINER_SCHEMA, GAP_SCHEMA, STREET_SCHEMA, SEARCH_SCHEMA, INTERCHANGE_SCHEMA } from "./schemas.ts";
|
|
87
92
|
|
|
88
93
|
/** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
|
|
89
94
|
export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
|
|
@@ -462,6 +467,11 @@ export const highwayStrategies: {
|
|
|
462
467
|
readonly consensus: Strategy<"highway", ConsensusOptions>;
|
|
463
468
|
readonly "p-hub": Strategy<"highway", PHubOptions>;
|
|
464
469
|
readonly "scale-space": Strategy<"highway", ScaleSpaceOptions>;
|
|
470
|
+
readonly steiner: Strategy<"highway", SteinerOptions>;
|
|
471
|
+
readonly gaps: Strategy<"highway", GapOptions>;
|
|
472
|
+
readonly streets: Strategy<"highway", StreetOptions>;
|
|
473
|
+
readonly search: Strategy<"highway", SearchOptions>;
|
|
474
|
+
readonly interchanges: Strategy<"highway", InterchangeOptions>;
|
|
465
475
|
readonly "corridors+breakeven": Strategy<"highway", CorridorOptions & BreakEvenOptions>;
|
|
466
476
|
readonly "corridors+stretch": Strategy<"highway", CorridorOptions & StretchOptions>;
|
|
467
477
|
readonly "corridors+ambiguity": Strategy<"highway", CorridorOptions & AmbiguityGuardOptions>;
|
|
@@ -892,6 +902,51 @@ export const highwayStrategies: {
|
|
|
892
902
|
maxNodes: 10_000,
|
|
893
903
|
run: (input: HighwayInput, o: Readonly<ScaleSpaceOptions>) => scaleSpacePlan(input, o),
|
|
894
904
|
}),
|
|
905
|
+
steiner: defineStrategy({
|
|
906
|
+
step: "highway",
|
|
907
|
+
name: "steiner",
|
|
908
|
+
describe: "Obstacle-avoiding rectilinear Steiner trees (HWY-036 with EDA-015). Each group of edges gets one tree, and the stretches that edges share become highways.",
|
|
909
|
+
defaults: { ...STEINER_DEFAULTS } as SteinerOptions,
|
|
910
|
+
schema: STEINER_SCHEMA,
|
|
911
|
+
maxNodes: 10_000,
|
|
912
|
+
run: (input: HighwayInput, o: Readonly<SteinerOptions>) => steinerPlan(input, o),
|
|
913
|
+
}),
|
|
914
|
+
gaps: defineStrategy({
|
|
915
|
+
step: "highway",
|
|
916
|
+
name: "gaps",
|
|
917
|
+
describe: "Gap highways (HWY-057). The free middle lines of the gaps are streets, and a street that enough edges choose becomes a highway.",
|
|
918
|
+
defaults: { ...GAP_DEFAULTS } as GapOptions,
|
|
919
|
+
schema: GAP_SCHEMA,
|
|
920
|
+
maxNodes: 10_000,
|
|
921
|
+
run: (input: HighwayInput, o: Readonly<GapOptions>) => gapPlan(input, o),
|
|
922
|
+
}),
|
|
923
|
+
streets: defineStrategy({
|
|
924
|
+
step: "highway",
|
|
925
|
+
name: "streets",
|
|
926
|
+
describe: "Orthogonal streets by a greedy set cover (HWY-058). Each step takes the street that its new edges ride the most per unit of its length.",
|
|
927
|
+
defaults: { ...STREET_DEFAULTS } as StreetOptions,
|
|
928
|
+
schema: STREET_SCHEMA,
|
|
929
|
+
maxNodes: 10_000,
|
|
930
|
+
run: (input: HighwayInput, o: Readonly<StreetOptions>) => streetPlan(input, o),
|
|
931
|
+
}),
|
|
932
|
+
search: defineStrategy({
|
|
933
|
+
step: "highway",
|
|
934
|
+
name: "search",
|
|
935
|
+
describe: "A local search over the set of highways (HWY-059). The annealing adds, drops and moves highways, edges and points on a score of the bundle measures.",
|
|
936
|
+
defaults: { ...SEARCH_DEFAULTS } as SearchOptions,
|
|
937
|
+
schema: SEARCH_SCHEMA,
|
|
938
|
+
maxNodes: 10_000,
|
|
939
|
+
run: (input: HighwayInput, o: Readonly<SearchOptions>) => searchPlan(input, o),
|
|
940
|
+
}),
|
|
941
|
+
interchanges: defineStrategy({
|
|
942
|
+
step: "highway",
|
|
943
|
+
name: "interchanges",
|
|
944
|
+
describe: "A network of highways with interchanges (HWY-060). Each edge rides up to a number of highways, with a cost for each change.",
|
|
945
|
+
defaults: { ...INTERCHANGE_DEFAULTS } as InterchangeOptions,
|
|
946
|
+
schema: INTERCHANGE_SCHEMA,
|
|
947
|
+
maxNodes: 10_000,
|
|
948
|
+
run: (input: HighwayInput, o: Readonly<InterchangeOptions>) => interchangePlan(input, o),
|
|
949
|
+
}),
|
|
895
950
|
"corridors+breakeven": gateStrategy(corridors, breakEvenGate),
|
|
896
951
|
"corridors+stretch": gateStrategy(corridors, stretchGate),
|
|
897
952
|
"corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
|