@mark1russell7/highways 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -8
- package/build/index.d.ts +705 -183
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3458 -1979
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/bench/compare.ts +174 -5
- package/src/bench/exact/index.ts +4 -0
- package/src/bench/properties.ts +8 -2
- package/src/bench/room.ts +1 -1
- package/src/bench/run.ts +38 -3
- package/src/bench/screen/generators.ts +11 -41
- package/src/bench/screen/screen.ts +16 -10
- package/src/bench/screen/set.ts +156 -49
- package/src/bench/suite.ts +110 -12
- package/src/cases.ts +64 -18
- package/src/families/cases.ts +108 -0
- package/src/families/fixtures.ts +101 -0
- package/src/families/index.ts +36 -0
- package/src/families/model.ts +55 -0
- package/src/fixtures.ts +8 -2
- package/src/highways/soft-keys/index.ts +8 -4
- package/src/highways/stretches.ts +28 -7
- package/src/index.ts +8 -0
- package/src/lane-order.ts +42 -2
- package/src/lane-orders/stretch/index.ts +486 -0
- package/src/lane-orders/stretch/router.ts +22 -0
- package/src/levels/depths/index.ts +57 -0
- package/src/plugs/facing/index.ts +22 -0
- package/src/portfolio/router.ts +44 -0
- package/src/routers/corners.ts +27 -3
- package/src/routers/sections.ts +22 -40
- package/src/routers/shared.ts +51 -19
- package/src/routers/trunks.ts +21 -3
- package/src/segments.ts +8 -26
- package/src/strategies/builtins.ts +54 -2
- package/src/strategies/model.ts +55 -10
- package/src/strategies/options.ts +36 -4
- package/src/strategies/schemas.ts +68 -5
- package/src/whole/pipeline/index.ts +66 -0
package/src/strategies/model.ts
CHANGED
|
@@ -2,10 +2,12 @@ import type { CurveSegment } from "../curves/model.ts";
|
|
|
2
2
|
import type { CurveSpace } from "../curves/space.ts";
|
|
3
3
|
import type { Point } from "../geom.ts";
|
|
4
4
|
import type { HighwayInput, HighwayPlan } from "../highways/model.ts";
|
|
5
|
-
import type { Graph, Result, Router } from "../model.ts";
|
|
5
|
+
import type { Graph, Level, Result, Route, Router } from "../model.ts";
|
|
6
|
+
import type { Plug } from "../plugs.ts";
|
|
6
7
|
import type { Boxed, Gap, RoomLink } from "../room/index.ts";
|
|
7
8
|
import type { Section } from "../sections.ts";
|
|
8
9
|
import type { SegmentGraph } from "../segments.ts";
|
|
10
|
+
import type { WayFinder } from "../ways.ts";
|
|
9
11
|
import type { OptionSchema } from "./options.ts";
|
|
10
12
|
|
|
11
13
|
/** The input of a curve strategy: one polyline and the context of its route. */
|
|
@@ -48,21 +50,57 @@ export interface RoomInput {
|
|
|
48
50
|
readonly sections?: Section;
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
/** The input of a plug strategy: the graph, and optionally the way finder of the router that reads the plugs. */
|
|
54
|
+
export interface PlugInput {
|
|
55
|
+
readonly graph: Graph;
|
|
56
|
+
/** The ways around the nodes of the graph, for a strategy that reads the free space. The default strategy `facing` does not read it. */
|
|
57
|
+
readonly ways?: WayFinder;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The plugs of the two ends of one edge: the output of a plug strategy for that edge. */
|
|
61
|
+
export interface PlugPair {
|
|
62
|
+
readonly edge: string;
|
|
63
|
+
readonly source: Plug;
|
|
64
|
+
readonly target: Plug;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The input of a level strategy: the graph, the tree of sections of its grouping rule and the routes of the router. */
|
|
68
|
+
export interface LevelInput {
|
|
69
|
+
readonly graph: Graph;
|
|
70
|
+
/** The tree of sections. The indices of its nodes are the indices of `graph.nodes`. */
|
|
71
|
+
readonly root: Section;
|
|
72
|
+
/** The routes of the router. The default strategy `depths` gives the links of the last level the points of these routes. */
|
|
73
|
+
readonly routes: readonly Route[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The output of a strategy of the step `whole`: the board that the routes go on, and the routes. */
|
|
77
|
+
export interface WholeOutput {
|
|
78
|
+
/** The board: the graph, or the graph with the gaps that a room strategy opened. It has the edges of the input. */
|
|
79
|
+
readonly graph: Graph;
|
|
80
|
+
readonly result: Result;
|
|
81
|
+
}
|
|
82
|
+
|
|
51
83
|
/**
|
|
52
84
|
* The steps that have strategies, with the fixed input and output type of each step.
|
|
53
85
|
*
|
|
54
86
|
* - `group`: a graph in, the tree of sections out. The router `sections` makes its hubs and its zoom levels from this tree.
|
|
87
|
+
* - `plugs`: a graph in, the plugs of the two ends of each edge out. The router `corners` routes between these plugs.
|
|
55
88
|
* - `router`: a graph in, the routes (and the `levels`, `bundles` and `runs`) out.
|
|
56
89
|
* - `highway`: a graph and optionally its first routes in, a highway plan out. `follow` routes the edges along the plan.
|
|
90
|
+
* - `levels`: a graph, its tree of sections and its routes in, the zoom levels out. The router `sections` gives them in its result.
|
|
57
91
|
* - `curve`: a polyline in, the segments of a curve out.
|
|
58
92
|
* - `room`: the boxes and links of a board in, the gaps of the board with their need out.
|
|
93
|
+
* - `whole`: a graph in, the board and its routes out. A strategy of this step runs the other steps.
|
|
59
94
|
*/
|
|
60
95
|
export interface StepTypes {
|
|
61
96
|
readonly group: { readonly input: Graph; readonly output: Section };
|
|
97
|
+
readonly plugs: { readonly input: PlugInput; readonly output: PlugPair[] };
|
|
62
98
|
readonly router: { readonly input: Graph; readonly output: Result };
|
|
63
99
|
readonly highway: { readonly input: HighwayInput; readonly output: HighwayPlan };
|
|
100
|
+
readonly levels: { readonly input: LevelInput; readonly output: Level[] };
|
|
64
101
|
readonly curve: { readonly input: CurveInput; readonly output: CurveSegment[] };
|
|
65
102
|
readonly room: { readonly input: RoomInput; readonly output: Gap[] };
|
|
103
|
+
readonly whole: { readonly input: Graph; readonly output: WholeOutput };
|
|
66
104
|
}
|
|
67
105
|
|
|
68
106
|
/** The name of a step that has strategies. */
|
|
@@ -74,8 +112,12 @@ export type StepInput<S extends Step> = StepTypes[S]["input"];
|
|
|
74
112
|
/** The output type of a step. */
|
|
75
113
|
export type StepOutput<S extends Step> = StepTypes[S]["output"];
|
|
76
114
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
115
|
+
/**
|
|
116
|
+
* The steps that have strategies, in the order of the pipeline.
|
|
117
|
+
* The plugs come before the router, because the router searches its ways between them. The levels come after the routes, because the last level reads them.
|
|
118
|
+
* The step `whole` runs the other steps, so it comes last.
|
|
119
|
+
*/
|
|
120
|
+
export const STEPS: readonly Step[] = ["group", "plugs", "router", "highway", "levels", "curve", "room", "whole"];
|
|
79
121
|
|
|
80
122
|
/**
|
|
81
123
|
* A strategy: one way to do one step, with a name and a description.
|
|
@@ -147,16 +189,16 @@ export function defineStrategy<S extends Step, O extends object>(definition: Str
|
|
|
147
189
|
/**
|
|
148
190
|
* A registry: the strategies of each step, in order. A registry is data. No import changes it.
|
|
149
191
|
* A consumer makes its own registry with `createRegistry`, and adds a strategy with `register`.
|
|
150
|
-
* The lists `group` and `
|
|
192
|
+
* The lists `group`, `highway`, `plugs`, `levels` and `whole` are optional in the type, so a registry of an older version stays a registry. `createRegistry` always gives them.
|
|
151
193
|
*/
|
|
152
|
-
export type Registry = { readonly [S in Exclude<Step,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
194
|
+
export type Registry = { readonly [S in Exclude<Step, OptionalStep>]: readonly Strategy<S>[] } & { readonly [S in OptionalStep]?: readonly Strategy<S>[] };
|
|
195
|
+
|
|
196
|
+
/** The steps whose lists are optional in a `Registry`: the steps that came after version 0.1.5. */
|
|
197
|
+
type OptionalStep = "group" | "highway" | "plugs" | "levels" | "whole";
|
|
156
198
|
|
|
157
199
|
/** This function makes a registry from a list of strategies. It keeps their order. Two strategies with one step and one name are an error. */
|
|
158
200
|
export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
|
|
159
|
-
const out: { [S in Step]: Strategy<S>[] } = { group: [], router: [], highway: [], curve: [], room: [] };
|
|
201
|
+
const out: { [S in Step]: Strategy<S>[] } = { group: [], plugs: [], router: [], highway: [], levels: [], curve: [], room: [], whole: [] };
|
|
160
202
|
for (const s of strategies) {
|
|
161
203
|
const list = out[s.step] as AnyStrategy[] | undefined;
|
|
162
204
|
if (!list) throw new Error(`There is no step "${String(s.step)}". The steps are ${STEPS.join(", ")}.`);
|
|
@@ -165,16 +207,19 @@ export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
|
|
|
165
207
|
}
|
|
166
208
|
return Object.freeze({
|
|
167
209
|
group: Object.freeze(out.group),
|
|
210
|
+
plugs: Object.freeze(out.plugs),
|
|
168
211
|
router: Object.freeze(out.router),
|
|
169
212
|
highway: Object.freeze(out.highway),
|
|
213
|
+
levels: Object.freeze(out.levels),
|
|
170
214
|
curve: Object.freeze(out.curve),
|
|
171
215
|
room: Object.freeze(out.room),
|
|
216
|
+
whole: Object.freeze(out.whole),
|
|
172
217
|
});
|
|
173
218
|
}
|
|
174
219
|
|
|
175
220
|
/** This function gives a new registry with the strategies of `registry` and then `strategies`. It does not change `registry`. */
|
|
176
221
|
export function register(registry: Registry, ...strategies: readonly AnyStrategy[]): Registry {
|
|
177
|
-
return createRegistry([...(
|
|
222
|
+
return createRegistry([...STEPS.flatMap((step) => listStrategies(registry, step) as readonly AnyStrategy[]), ...strategies]);
|
|
178
223
|
}
|
|
179
224
|
|
|
180
225
|
/** This function gives the strategies of one step of a registry, in order. */
|
|
@@ -26,6 +26,11 @@ export interface NumberOption extends OptionBase {
|
|
|
26
26
|
readonly special?: readonly number[];
|
|
27
27
|
/** These values are the notable values of the option: the default and the values of the sweeps of the benchmark. */
|
|
28
28
|
readonly marks?: readonly number[];
|
|
29
|
+
/**
|
|
30
|
+
* These values are the only valid values of the option, in the order of a control. A control shows them as a list, and a sweep gives them.
|
|
31
|
+
* Without them, each value on the range is valid.
|
|
32
|
+
*/
|
|
33
|
+
readonly values?: readonly number[];
|
|
29
34
|
/** The strategy uses this value when the caller and `defaults` give no value. */
|
|
30
35
|
readonly default?: number;
|
|
31
36
|
}
|
|
@@ -42,6 +47,11 @@ export interface EnumOption<V extends string = string> extends OptionBase {
|
|
|
42
47
|
readonly type: "enum";
|
|
43
48
|
/** The values of the option, in the order of a control. */
|
|
44
49
|
readonly values: readonly V[];
|
|
50
|
+
/**
|
|
51
|
+
* With `function`, the option also takes a rule of the caller in place of a name. A control sets only the names.
|
|
52
|
+
* Each name is a known rule of the strategy.
|
|
53
|
+
*/
|
|
54
|
+
readonly custom?: "function";
|
|
45
55
|
/** The strategy uses this value when the caller and `defaults` give no value. */
|
|
46
56
|
readonly default?: V;
|
|
47
57
|
}
|
|
@@ -61,8 +71,19 @@ export interface ValueOption<V = unknown> extends OptionBase {
|
|
|
61
71
|
/** The declaration of one option of any type. */
|
|
62
72
|
export type AnyOption = NumberOption | BooleanOption | EnumOption | ValueOption;
|
|
63
73
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The declaration that fits the type of an option value: numbers, booleans, string unions or other values.
|
|
76
|
+
* A union of strings and other values takes an enum of the strings or a value declaration. An example is a rule or the name of a rule.
|
|
77
|
+
*/
|
|
78
|
+
export type OptionDeclaration<V> = [V] extends [number]
|
|
79
|
+
? NumberOption
|
|
80
|
+
: [V] extends [boolean]
|
|
81
|
+
? BooleanOption
|
|
82
|
+
: [V] extends [string]
|
|
83
|
+
? EnumOption<V>
|
|
84
|
+
: [Extract<V, string>] extends [never]
|
|
85
|
+
? ValueOption<V>
|
|
86
|
+
: EnumOption<Extract<V, string>> | ValueOption<V>;
|
|
66
87
|
|
|
67
88
|
/** The declarations of the options of a strategy. The schema has one declaration for each key of the options type, and no other key. */
|
|
68
89
|
export type OptionSchema<O> = { readonly [K in keyof O]-?: OptionDeclaration<NonNullable<O[K]>> };
|
|
@@ -139,6 +160,7 @@ function issueOf(key: string, value: unknown, d: AnyOption): OptionIssue | undef
|
|
|
139
160
|
case "integer": {
|
|
140
161
|
if (typeof value !== "number" || Number.isNaN(value)) return issue("type", `The option "${key}" takes a number.`);
|
|
141
162
|
if (isSpecial(d, value)) return undefined;
|
|
163
|
+
if (d.values !== undefined && !d.values.includes(value)) return issue("value", `The option "${key}" takes ${d.values.join(", ")}, not ${value}.`);
|
|
142
164
|
if (value < d.min || value > d.max) return issue("range", `The option "${key}" is ${value}, out of the range ${d.min} to ${d.max}.`);
|
|
143
165
|
if (d.type === "integer" && !Number.isInteger(value)) return issue("integer", `The option "${key}" takes a whole number, not ${value}.`);
|
|
144
166
|
return undefined;
|
|
@@ -146,6 +168,7 @@ function issueOf(key: string, value: unknown, d: AnyOption): OptionIssue | undef
|
|
|
146
168
|
case "boolean":
|
|
147
169
|
return typeof value === "boolean" ? undefined : issue("type", `The option "${key}" takes true or false.`);
|
|
148
170
|
case "enum":
|
|
171
|
+
if (d.custom === "function" && typeof value === "function") return undefined;
|
|
149
172
|
return d.values.includes(value as string) ? undefined : issue("value", `The option "${key}" takes ${d.values.join(", ")}, not ${String(value)}.`);
|
|
150
173
|
case "function":
|
|
151
174
|
return typeof value === "function" ? undefined : issue("type", `The option "${key}" takes a function.`);
|
|
@@ -176,7 +199,7 @@ export function checkOptions<S extends Step, O extends object>(strategy: Strateg
|
|
|
176
199
|
|
|
177
200
|
/**
|
|
178
201
|
* This function moves each number option into the range of its declaration, for a control. An integer option gets the nearest whole number.
|
|
179
|
-
* A special value stays. The function keeps each other value as it is. `checkOptions` reports the values that this function cannot correct.
|
|
202
|
+
* An option with a list of `values` gets the nearest value of the list. A special value stays. The function keeps each other value as it is. `checkOptions` reports the values that this function cannot correct.
|
|
180
203
|
*/
|
|
181
204
|
export function clampOptions<S extends Step, O extends object>(strategy: Strategy<S, O>, options: Partial<O>): Partial<O> {
|
|
182
205
|
const schema = schemaOf(strategy);
|
|
@@ -184,6 +207,10 @@ export function clampOptions<S extends Step, O extends object>(strategy: Strateg
|
|
|
184
207
|
for (const [key, value] of Object.entries(options)) {
|
|
185
208
|
const d = schema[key];
|
|
186
209
|
if (!d || (d.type !== "number" && d.type !== "integer") || typeof value !== "number" || Number.isNaN(value) || isSpecial(d, value)) continue;
|
|
210
|
+
if (d.values !== undefined && d.values.length > 0) {
|
|
211
|
+
out[key] = d.values.reduce((best, v) => (Math.abs(v - value) < Math.abs(best - value) ? v : best));
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
187
214
|
const v = Math.min(d.max, Math.max(d.min, value));
|
|
188
215
|
out[key] = d.type === "integer" ? Math.min(d.max, Math.max(d.min, Math.round(v))) : v;
|
|
189
216
|
}
|
|
@@ -211,7 +238,7 @@ function rangeValues(d: NumberOption, count: number): number[] {
|
|
|
211
238
|
|
|
212
239
|
/**
|
|
213
240
|
* This function gives the values of a sweep of one option declaration.
|
|
214
|
-
* A number option gives its `marks`, or `count` values on its range and its scale. A boolean gives `false` and `true`. An enum gives its values.
|
|
241
|
+
* A number option gives its `marks`, or its list of `values`, or `count` values on its range and its scale. A boolean gives `false` and `true`. An enum gives its values.
|
|
215
242
|
* An option of another type has no sweep, so the function throws an error.
|
|
216
243
|
*/
|
|
217
244
|
export function sweepValues(declaration: AnyOption, options: SweepOptions = {}): unknown[] {
|
|
@@ -219,6 +246,7 @@ export function sweepValues(declaration: AnyOption, options: SweepOptions = {}):
|
|
|
219
246
|
case "number":
|
|
220
247
|
case "integer": {
|
|
221
248
|
if (options.count === undefined && declaration.marks !== undefined) return [...declaration.marks];
|
|
249
|
+
if (declaration.values !== undefined) return [...declaration.values];
|
|
222
250
|
const values = rangeValues(declaration, options.count ?? SWEEP_COUNT);
|
|
223
251
|
return options.special ? [...values, ...(declaration.special ?? []).filter((s) => !values.includes(s))] : values;
|
|
224
252
|
}
|
|
@@ -268,6 +296,10 @@ function declarationIssues(key: string, d: AnyOption): string[] {
|
|
|
268
296
|
if (d.type === "integer" && (!Number.isInteger(d.min) || !Number.isInteger(d.max))) out.push(`The integer option "${key}" has a range that is not whole.`);
|
|
269
297
|
if (d.step !== undefined && !(d.step > 0)) out.push(`The option "${key}" has the step ${d.step}. A step is more than 0.`);
|
|
270
298
|
if (d.scale === "log" && !(d.min > 0)) out.push(`The option "${key}" has the scale log and the min ${d.min}. The scale log needs a min above 0.`);
|
|
299
|
+
if (d.values !== undefined && d.values.length === 0) out.push(`The number option "${key}" has an empty list of values.`);
|
|
300
|
+
for (const v of d.values ?? []) {
|
|
301
|
+
if (v < d.min || v > d.max || (d.type === "integer" && !Number.isInteger(v))) out.push(`The value ${v} of the option "${key}" is not on its range ${d.min} to ${d.max}.`);
|
|
302
|
+
}
|
|
271
303
|
for (const v of [...(d.marks ?? []), ...(d.default === undefined ? [] : [d.default])]) {
|
|
272
304
|
const issue = issueOf(key, v, d);
|
|
273
305
|
if (issue) out.push(issue.message);
|
|
@@ -49,12 +49,15 @@ import type { TraclusOptions } from "../highways/traclus/index.ts";
|
|
|
49
49
|
import type { WeiszfeldOptions } from "../highways/weiszfeld/index.ts";
|
|
50
50
|
import type { WspdOptions } from "../highways/wspd/index.ts";
|
|
51
51
|
import type { ZipperOptions } from "../highways/zipper/index.ts";
|
|
52
|
+
import type { LaneStretchOptions } from "../lane-orders/stretch/index.ts";
|
|
53
|
+
import type { DepthLevelOptions } from "../levels/depths/index.ts";
|
|
52
54
|
import type { CornersOptions } from "../routers/corners.ts";
|
|
53
55
|
import type { SectionsOptions } from "../routers/sections.ts";
|
|
54
56
|
import type { SharedOptions } from "../routers/shared.ts";
|
|
55
|
-
import type
|
|
57
|
+
import { GROUP_RULE_NAMES, type GroupRuleName, type TrunksOptions } from "../routers/trunks.ts";
|
|
56
58
|
import type { SectionOptions } from "../sections.ts";
|
|
57
59
|
import type { WayOptions } from "../ways.ts";
|
|
60
|
+
import type { PipelineOptions } from "../whole/pipeline/index.ts";
|
|
58
61
|
import type { RunOrder } from "../segments.ts";
|
|
59
62
|
import type { ClothoidStrategyOptions, HighwayStrategyOptions, LevelRoomPreset, RadiusOptions, RoomPreset, RouterPreset, SmoothStrategyOptions, TensionOptions } from "./builtins.ts";
|
|
60
63
|
import type { EnumOption, NumberOption, OptionSchema } from "./options.ts";
|
|
@@ -69,6 +72,24 @@ const WAY_SCHEMA: OptionSchema<WayOptions> = {
|
|
|
69
72
|
smooth: { type: "boolean", default: false, describe: "With `true`, a way in clear view uses the inflection rule, and the other ways get round corners." },
|
|
70
73
|
};
|
|
71
74
|
|
|
75
|
+
/** The declarations of the options of the stretch order (LANE-STRETCH): the method and the passes of `sifting`. A generator can list each value. */
|
|
76
|
+
export const LANE_STRETCH_SCHEMA: OptionSchema<LaneStretchOptions> = {
|
|
77
|
+
method: {
|
|
78
|
+
type: "enum",
|
|
79
|
+
values: ["none", "consensus", "periphery", "sifting"],
|
|
80
|
+
default: "sifting",
|
|
81
|
+
describe: "The order of each stretch: the order of each run alone, the order of the pair relation, the order of the largest run, or the order of the pair relation after sifting.",
|
|
82
|
+
},
|
|
83
|
+
passes: { type: "integer", min: 1, max: 4, step: 1, default: 2, describe: "The number of passes of the method `sifting`. In each pass, each edge moves to its best place." },
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The declaration of the grouping rule of the edges: a name of `GROUP_RULE_NAMES`. A new name of the list needs no change here.
|
|
88
|
+
* The option also takes a rule of the caller (`custom`), so the old value form stays valid.
|
|
89
|
+
* The `defaults` of a strategy keep the rule `byCollection`, and the declaration gives its name `collection` as its `default`.
|
|
90
|
+
*/
|
|
91
|
+
const EDGE_RULE: EnumOption<GroupRuleName> = { type: "enum", values: GROUP_RULE_NAMES, custom: "function", describe: "The grouping rule." };
|
|
92
|
+
|
|
72
93
|
/** The declaration of the lane order of the shared ways of the routers `trunks` and `shared`. */
|
|
73
94
|
const LANE_ORDER: EnumOption<RunOrder> = {
|
|
74
95
|
type: "enum",
|
|
@@ -97,12 +118,14 @@ export const CORNERS_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> = {
|
|
|
97
118
|
default: 0,
|
|
98
119
|
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.",
|
|
99
120
|
},
|
|
121
|
+
plug: { type: "strategy", step: "plugs", describe: "The plug rule: a strategy of the step `plugs`. Without it, the option `plugs` places the plugs." },
|
|
122
|
+
plugOptions: { type: "object", describe: "The options of the strategy `plug`." },
|
|
100
123
|
};
|
|
101
124
|
|
|
102
125
|
/** The declarations of the options of the router `trunks`. */
|
|
103
126
|
export const TRUNKS_SCHEMA: OptionSchema<RouterPreset<TrunksOptions>> = {
|
|
104
127
|
...WAY_SCHEMA,
|
|
105
|
-
rule: {
|
|
128
|
+
rule: { ...EDGE_RULE, default: "collection", describe: "The grouping rule. The edges from one plug with one key share a trunk. With `all`, each edge has one key." },
|
|
106
129
|
split: { type: "number", min: 0.1, max: 0.9, step: 0.05, default: 0.5, describe: "The split point is at this share of the way to the center of the targets." },
|
|
107
130
|
minSize: { type: "integer", min: 1, max: 8, step: 1, default: 2, describe: "A group with fewer edges stays unbundled." },
|
|
108
131
|
laneOrder: LANE_ORDER,
|
|
@@ -128,10 +151,10 @@ export const TRUNKS_SCHEMA: OptionSchema<RouterPreset<TrunksOptions>> = {
|
|
|
128
151
|
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." },
|
|
129
152
|
};
|
|
130
153
|
|
|
131
|
-
/** The declarations of the options of the
|
|
154
|
+
/** The declarations of the options of the router `shared` and of the highway strategy `shared-ways`. */
|
|
132
155
|
export const SHARED_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
|
|
133
156
|
...WAY_SCHEMA,
|
|
134
|
-
rule: {
|
|
157
|
+
rule: { ...EDGE_RULE, default: "collection", describe: "The edges with one key share their ways. With `collection`, the key is `data.collection`. With `all`, all edges share." },
|
|
135
158
|
discount: {
|
|
136
159
|
type: "number",
|
|
137
160
|
min: 0.05,
|
|
@@ -179,6 +202,18 @@ export const SHARED_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
|
|
|
179
202
|
default: "corner",
|
|
180
203
|
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).",
|
|
181
204
|
},
|
|
205
|
+
stretchOrder: {
|
|
206
|
+
...LANE_STRETCH_SCHEMA.method,
|
|
207
|
+
default: "none",
|
|
208
|
+
describe: "With `none`, each run gets its lane order alone. With the other values, the runs that follow each other share one order (LANE-STRETCH).",
|
|
209
|
+
},
|
|
210
|
+
siftPasses: LANE_STRETCH_SCHEMA.passes,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/** The declarations of the options of the router `shared-all`. It sets no rule, so all edges share. Thus its rule has no `default`. */
|
|
214
|
+
export const SHARED_ALL_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
|
|
215
|
+
...SHARED_SCHEMA,
|
|
216
|
+
rule: { ...EDGE_RULE, describe: "The edges with one key share their ways. Without a rule, or with `all`, all edges share. With `collection`, the key is `data.collection`." },
|
|
182
217
|
};
|
|
183
218
|
|
|
184
219
|
/** The declarations of the options of the highway strategy `corridors` (HWY-031). */
|
|
@@ -248,6 +283,8 @@ export const SECTIONS_SCHEMA: OptionSchema<RouterPreset<SectionsOptions>> = {
|
|
|
248
283
|
group: { type: "strategy", step: "group", describe: "The grouping rule: a strategy of the step `group`. Without it, the router uses the quadtree." },
|
|
249
284
|
groupOptions: { type: "object", describe: "The options of the strategy `group`." },
|
|
250
285
|
levels: { type: "list", describe: "The depths of the tree that get a level with groups. Without it, each depth gets a level." },
|
|
286
|
+
level: { type: "strategy", step: "levels", describe: "The level rule: a strategy of the step `levels`. Without it, the router uses the strategy `depths`." },
|
|
287
|
+
levelOptions: { type: "object", describe: "The options of the strategy `level`. They go over the level options of the router." },
|
|
251
288
|
laneOrder: RUN_LANE_ORDER,
|
|
252
289
|
};
|
|
253
290
|
|
|
@@ -553,7 +590,7 @@ export const EDGE_PATH_SCHEMA: OptionSchema<EdgePathOptions> = {
|
|
|
553
590
|
|
|
554
591
|
/** The declarations of the options of the highway strategy `soft-keys` (HWY-019). */
|
|
555
592
|
export const SOFT_KEY_SCHEMA: OptionSchema<SoftKeyOptions> = {
|
|
556
|
-
rule: {
|
|
593
|
+
rule: { ...EDGE_RULE, default: "collection", describe: "The rule that gives the key of each edge. Two different keys add the distance `lambda`. With `all`, the keys add no distance." },
|
|
557
594
|
lambda: { type: "number", min: 0, max: 2, step: 0.05, marks: [0, 0.25, 1], default: 0.25, describe: "The distance that two different keys add. A very large value keeps the keys apart." },
|
|
558
595
|
tau: { type: "number", min: 0.1, max: 1.5, step: 0.05, marks: [0.3, 0.5, 1], default: 0.5, describe: "Two clusters join while their distance is this value or less." },
|
|
559
596
|
attributes: { type: "list", default: [], describe: "The names of number fields of `data` that add a distance." },
|
|
@@ -862,3 +899,29 @@ export const SCALE_SPACE_SCHEMA: OptionSchema<ScaleSpaceOptions> = {
|
|
|
862
899
|
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A cluster with this number of edges or more gets highways." },
|
|
863
900
|
spine: GROUP_SPINE,
|
|
864
901
|
};
|
|
902
|
+
|
|
903
|
+
/** The declarations of the options of the level strategy `depths`: the limits of the level links, and the depths that get a level. */
|
|
904
|
+
export const DEPTHS_SCHEMA: OptionSchema<DepthLevelOptions> = {
|
|
905
|
+
margin: WAY_SCHEMA.margin,
|
|
906
|
+
k: WAY_SCHEMA.k,
|
|
907
|
+
tightMargin: SECTIONS_SCHEMA.tightMargin,
|
|
908
|
+
plugInset: SECTIONS_SCHEMA.plugInset,
|
|
909
|
+
throughCost: SECTIONS_SCHEMA.throughCost,
|
|
910
|
+
levels: SECTIONS_SCHEMA.levels,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
/** The declarations of the options of the whole strategy `pipeline`. */
|
|
914
|
+
export const PIPELINE_SCHEMA: OptionSchema<PipelineOptions> = {
|
|
915
|
+
router: { type: "strategy", step: "router", describe: "The router. A highway plan takes its place." },
|
|
916
|
+
routerOptions: { type: "object", describe: "The options of the router." },
|
|
917
|
+
highway: { type: "strategy", step: "highway", describe: "The highway strategy. With it, the plan and `follow` give the routes. Without it, the router routes." },
|
|
918
|
+
highwayOptions: { type: "object", describe: "The options of the highway strategy." },
|
|
919
|
+
group: { type: "strategy", step: "group", describe: "The grouping rule of a router that reads one, for example `sections`." },
|
|
920
|
+
groupOptions: { type: "object", describe: "The options of the grouping rule." },
|
|
921
|
+
plugs: { type: "strategy", step: "plugs", describe: "The plug rule of a router that reads one, for example `corners`." },
|
|
922
|
+
plugOptions: { type: "object", describe: "The options of the plug rule." },
|
|
923
|
+
levels: { type: "strategy", step: "levels", describe: "The level rule of a router that reads one, for example `sections`." },
|
|
924
|
+
levelOptions: { type: "object", describe: "The options of the level rule." },
|
|
925
|
+
room: { type: "strategy", step: "room", describe: "The room strategy. With it, the gaps of the board open before the routes. Without it, the board stays." },
|
|
926
|
+
roomOptions: { type: "object", describe: "The options of the room strategy." },
|
|
927
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { highwayRouterStrategy } from "../../highways/router.ts";
|
|
2
|
+
import type { Graph } from "../../model.ts";
|
|
3
|
+
import { openGaps, roomLinks } from "../../room/index.ts";
|
|
4
|
+
import { asRouter, type Step, type Strategy, type WholeOutput } from "../../strategies/model.ts";
|
|
5
|
+
import { listOptions } from "../../strategies/options.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The options of the whole strategy `pipeline`: one strategy of each step, and the options of each one.
|
|
9
|
+
* A rule of the steps `group`, `plugs` and `levels` acts only on a router that reads a rule of that step (`strategyKey`).
|
|
10
|
+
*/
|
|
11
|
+
export interface PipelineOptions {
|
|
12
|
+
/** The router. A highway plan takes its place. */
|
|
13
|
+
readonly router: Strategy<"router">;
|
|
14
|
+
/** The options of the router. */
|
|
15
|
+
readonly routerOptions?: object;
|
|
16
|
+
/** The highway strategy. With it, the plan of the strategy and `follow` give the routes, not the router. */
|
|
17
|
+
readonly highway?: Strategy<"highway">;
|
|
18
|
+
/** The options of the highway strategy. */
|
|
19
|
+
readonly highwayOptions?: object;
|
|
20
|
+
/** The grouping rule of a router that reads one, for example `sections`. */
|
|
21
|
+
readonly group?: Strategy<"group">;
|
|
22
|
+
/** The options of the grouping rule. */
|
|
23
|
+
readonly groupOptions?: object;
|
|
24
|
+
/** The plug rule of a router that reads one, for example `corners`. */
|
|
25
|
+
readonly plugs?: Strategy<"plugs">;
|
|
26
|
+
/** The options of the plug rule. */
|
|
27
|
+
readonly plugOptions?: object;
|
|
28
|
+
/** The level rule of a router that reads one, for example `sections`. */
|
|
29
|
+
readonly levels?: Strategy<"levels">;
|
|
30
|
+
/** The options of the level rule. */
|
|
31
|
+
readonly levelOptions?: object;
|
|
32
|
+
/** The room strategy. With it, the pipeline opens the gaps of the board before the routes (`openGaps`). */
|
|
33
|
+
readonly room?: Strategy<"room">;
|
|
34
|
+
/** The options of the room strategy. */
|
|
35
|
+
readonly roomOptions?: object;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* This function gives the key of the option of a router that takes a strategy of one step.
|
|
40
|
+
* It gives `undefined` when the router has no such option.
|
|
41
|
+
* The schema of the router declares the option with the type `strategy` and the step. The options of that strategy have the key `<key>Options`.
|
|
42
|
+
*/
|
|
43
|
+
export function strategyKey(router: Strategy<"router">, step: Step): string | undefined {
|
|
44
|
+
return listOptions(router).find((o) => o.declaration.type === "strategy" && o.declaration.step === step)?.key;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* This function runs the whole strategy `pipeline` on one graph. The room strategy opens the gaps of the board first.
|
|
49
|
+
* A highway strategy makes its plan, and `follow` routes the plan. Otherwise the router routes, with each rule of the steps `group`, `plugs` and `levels` that it reads.
|
|
50
|
+
* The output has the board and the routes. The function does not examine the `maxNodes` of the router.
|
|
51
|
+
*/
|
|
52
|
+
export function runPipeline(graph: Graph, options: PipelineOptions): WholeOutput {
|
|
53
|
+
const board = options.room ? { nodes: openGaps(graph.nodes, options.room.run({ items: graph.nodes, links: roomLinks(graph) }, options.roomOptions)), edges: graph.edges } : graph;
|
|
54
|
+
if (options.highway) return { graph: board, result: asRouter(highwayRouterStrategy(options.highway) as Strategy<"router">, options.highwayOptions).route(board) };
|
|
55
|
+
let routerOptions = options.routerOptions;
|
|
56
|
+
const rules: readonly [Step, Strategy<Step> | undefined, object | undefined][] = [
|
|
57
|
+
["group", options.group, options.groupOptions],
|
|
58
|
+
["plugs", options.plugs, options.plugOptions],
|
|
59
|
+
["levels", options.levels, options.levelOptions],
|
|
60
|
+
];
|
|
61
|
+
for (const [step, rule, own] of rules) {
|
|
62
|
+
const key = rule && strategyKey(options.router, step);
|
|
63
|
+
if (key) routerOptions = { ...routerOptions, [key]: rule, [`${key}Options`]: own };
|
|
64
|
+
}
|
|
65
|
+
return { graph: board, result: asRouter(options.router, routerOptions).route(board) };
|
|
66
|
+
}
|