@mark1russell7/highways 0.1.5 → 0.3.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.
Files changed (56) hide show
  1. package/README.md +293 -8
  2. package/build/index.d.ts +1774 -236
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +9307 -3090
  5. package/build/index.js.map +1 -1
  6. package/package.json +2 -1
  7. package/src/bench/compare.ts +145 -0
  8. package/src/bench/curves.ts +514 -2
  9. package/src/bench/links.ts +28 -6
  10. package/src/bench/measures/bundles.ts +119 -0
  11. package/src/bench/measures/curves.ts +211 -0
  12. package/src/bench/measures/groups.ts +122 -0
  13. package/src/bench/measures/index.ts +9 -0
  14. package/src/bench/measures/levels.ts +103 -0
  15. package/src/bench/measures/pareto.ts +37 -0
  16. package/src/bench/measures/room.ts +43 -0
  17. package/src/bench/measures/routes.ts +226 -0
  18. package/src/bench/measures/stability.ts +104 -0
  19. package/src/bench/measures/stats.ts +114 -0
  20. package/src/bench/properties.ts +18 -2
  21. package/src/bench/room.ts +209 -2
  22. package/src/bench/run.ts +86 -11
  23. package/src/bench/sections.ts +121 -0
  24. package/src/bench/suite.ts +781 -0
  25. package/src/cases.ts +41 -5
  26. package/src/curves/clothoid/index.ts +150 -0
  27. package/src/curves/corner.ts +116 -0
  28. package/src/curves/fit.ts +79 -0
  29. package/src/curves/highway/index.ts +636 -0
  30. package/src/curves/index.ts +16 -1
  31. package/src/curves/model.ts +4 -2
  32. package/src/curves/smooth/index.ts +50 -0
  33. package/src/curves/space.ts +76 -0
  34. package/src/groups/cut/index.ts +292 -0
  35. package/src/groups/index.ts +4 -0
  36. package/src/groups/kd/index.ts +207 -0
  37. package/src/groups/keys/index.ts +81 -0
  38. package/src/groups/quadtree/index.ts +10 -0
  39. package/src/index.ts +7 -0
  40. package/src/levels.ts +178 -9
  41. package/src/model.ts +2 -0
  42. package/src/pick.ts +36 -0
  43. package/src/room/bends.ts +203 -0
  44. package/src/room/index.ts +28 -0
  45. package/src/room/levels/index.ts +155 -0
  46. package/src/routers/sections.ts +78 -55
  47. package/src/routers/shared.ts +7 -2
  48. package/src/routers/trunks.ts +131 -6
  49. package/src/sections.ts +93 -4
  50. package/src/segments.ts +81 -31
  51. package/src/strategies/builtins.ts +153 -7
  52. package/src/strategies/index.ts +1 -0
  53. package/src/strategies/model.ts +29 -7
  54. package/src/strategies/options.ts +296 -0
  55. package/src/strategies/schemas.ts +221 -0
  56. package/src/visibility.ts +190 -1
@@ -0,0 +1,296 @@
1
+ import type { Step, Strategy } from "./model.ts";
2
+
3
+ /** The scale of a number option. A sweep on the scale `log` gives values with equal ratios, not with equal differences. */
4
+ export type OptionScale = "linear" | "log";
5
+
6
+ /** The fields of each option declaration. */
7
+ interface OptionBase {
8
+ /** One short sentence that tells what the option sets. The site and the documents show it. */
9
+ readonly describe: string;
10
+ }
11
+
12
+ /**
13
+ * The declaration of a number option. The range from `min` to `max` is the range of a control and of a sweep.
14
+ * The strategy can take a value outside the range. `checkOptions` reports such a value, and `clampOptions` moves it into the range.
15
+ */
16
+ export interface NumberOption extends OptionBase {
17
+ /** The type `integer` takes only whole numbers. */
18
+ readonly type: "number" | "integer";
19
+ readonly min: number;
20
+ readonly max: number;
21
+ /** The grain of a control and of a sweep. A value off the step is valid. Without it, the grain is free (or 1 for an integer). */
22
+ readonly step?: number;
23
+ /** The scale of a control and of a sweep. The default is `linear`. The scale `log` needs a `min` above 0. */
24
+ readonly scale?: OptionScale;
25
+ /** These values are valid outside the range. For example, `Infinity` sets no limit. */
26
+ readonly special?: readonly number[];
27
+ /** These values are the notable values of the option: the default and the values of the sweeps of the benchmark. */
28
+ readonly marks?: readonly number[];
29
+ /** The strategy uses this value when the caller and `defaults` give no value. */
30
+ readonly default?: number;
31
+ }
32
+
33
+ /** The declaration of a boolean option. */
34
+ export interface BooleanOption extends OptionBase {
35
+ readonly type: "boolean";
36
+ /** The strategy uses this value when the caller and `defaults` give no value. */
37
+ readonly default?: boolean;
38
+ }
39
+
40
+ /** The declaration of an option with a fixed list of string values. */
41
+ export interface EnumOption<V extends string = string> extends OptionBase {
42
+ readonly type: "enum";
43
+ /** The values of the option, in the order of a control. */
44
+ readonly values: readonly V[];
45
+ /** The strategy uses this value when the caller and `defaults` give no value. */
46
+ readonly default?: V;
47
+ }
48
+
49
+ /**
50
+ * The declaration of an option that a control or a sweep cannot set from a range.
51
+ * The type `function` is a rule, `strategy` is a strategy of the step `step`, `list` is an array and `object` is any other object.
52
+ */
53
+ export interface ValueOption<V = unknown> extends OptionBase {
54
+ readonly type: "function" | "strategy" | "list" | "object";
55
+ /** The step of the strategy, for the type `strategy`. A control lists the strategies of this step of a registry. */
56
+ readonly step?: Step;
57
+ /** The strategy uses this value when the caller and `defaults` give no value. */
58
+ readonly default?: V;
59
+ }
60
+
61
+ /** The declaration of one option of any type. */
62
+ export type AnyOption = NumberOption | BooleanOption | EnumOption | ValueOption;
63
+
64
+ /** The declaration that fits the type of an option value: numbers, booleans, string unions or other values. */
65
+ export type OptionDeclaration<V> = [V] extends [number] ? NumberOption : [V] extends [boolean] ? BooleanOption : [V] extends [string] ? EnumOption<V> : ValueOption<V>;
66
+
67
+ /** 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
+ export type OptionSchema<O> = { readonly [K in keyof O]-?: OptionDeclaration<NonNullable<O[K]>> };
69
+
70
+ /** One problem of an option value against its declaration. */
71
+ export interface OptionIssue {
72
+ /** The key of the option. */
73
+ readonly key: string;
74
+ readonly value: unknown;
75
+ /** The kind of the problem: no declaration, the wrong type, out of the range, not a whole number or not one of the values. */
76
+ readonly problem: "unknown" | "type" | "range" | "integer" | "value";
77
+ /** One sentence that tells the problem. */
78
+ readonly message: string;
79
+ }
80
+
81
+ /** One setting of a sweep: a name for the rows of a table, and the options of the strategy. */
82
+ export interface OptionSetting<O extends object> {
83
+ /** The key and the value of each option of the setting, for example `stretch 1.5`. */
84
+ readonly name: string;
85
+ readonly options: Partial<O>;
86
+ }
87
+
88
+ /** The options of `sweepValues`, `sweepOf` and `sweepGrid`. */
89
+ export interface SweepOptions {
90
+ /**
91
+ * The number of values on the range of a number option. Without it, the sweep gives the `marks` of the declaration.
92
+ * A declaration without `marks` gets 5 values.
93
+ */
94
+ readonly count?: number;
95
+ /** With `true`, a sweep of a number option on the range also gives the `special` values. The default is `false`. */
96
+ readonly special?: boolean;
97
+ }
98
+
99
+ /** The number of values of a sweep on a range when the call and the declaration give no number. */
100
+ const SWEEP_COUNT = 5;
101
+
102
+ /** This function gives the declarations of a strategy as a record. A strategy without a schema gives an empty record. */
103
+ function schemaOf(strategy: Strategy<Step, object>): Readonly<Record<string, AnyOption>> {
104
+ return (strategy.schema ?? {}) as Readonly<Record<string, AnyOption>>;
105
+ }
106
+
107
+ /** This function gives the declared options of a strategy, in the order of its schema, with the effective default of each one. */
108
+ export function listOptions<S extends Step, O extends object>(
109
+ strategy: Strategy<S, O>,
110
+ ): { readonly key: keyof O & string; readonly declaration: AnyOption; readonly value: unknown }[] {
111
+ const defaults = optionDefaults(strategy) as Record<string, unknown>;
112
+ return Object.entries(schemaOf(strategy)).map(([key, declaration]) => ({ key: key as keyof O & string, declaration, value: defaults[key] }));
113
+ }
114
+
115
+ /**
116
+ * This function gives the effective default of each declared option: the value of `defaults`, else the `default` of the declaration.
117
+ * An option without either value stays out. The strategy gives the same output with this object as with no options.
118
+ */
119
+ export function optionDefaults<S extends Step, O extends object>(strategy: Strategy<S, O>): Partial<O> {
120
+ const out: Record<string, unknown> = {};
121
+ const defaults = strategy.defaults as Readonly<Record<string, unknown>>;
122
+ for (const [key, declaration] of Object.entries(schemaOf(strategy))) {
123
+ if (key in defaults) out[key] = defaults[key];
124
+ else if (declaration.default !== undefined) out[key] = declaration.default;
125
+ }
126
+ for (const key of Object.keys(defaults)) if (!(key in out)) out[key] = defaults[key];
127
+ return out as Partial<O>;
128
+ }
129
+
130
+ /** This function gives `true` if the number is one of the special values of the declaration. */
131
+ const isSpecial = (d: NumberOption, v: number): boolean => (d.special ?? []).some((s) => Object.is(s, v) || s === v);
132
+
133
+ /** This function gives the problem of one value against its declaration, or `undefined` when the value fits. */
134
+ function issueOf(key: string, value: unknown, d: AnyOption): OptionIssue | undefined {
135
+ const issue = (problem: OptionIssue["problem"], message: string): OptionIssue => ({ key, value, problem, message });
136
+ if (value === undefined) return undefined;
137
+ switch (d.type) {
138
+ case "number":
139
+ case "integer": {
140
+ if (typeof value !== "number" || Number.isNaN(value)) return issue("type", `The option "${key}" takes a number.`);
141
+ if (isSpecial(d, value)) return undefined;
142
+ 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
+ if (d.type === "integer" && !Number.isInteger(value)) return issue("integer", `The option "${key}" takes a whole number, not ${value}.`);
144
+ return undefined;
145
+ }
146
+ case "boolean":
147
+ return typeof value === "boolean" ? undefined : issue("type", `The option "${key}" takes true or false.`);
148
+ case "enum":
149
+ return d.values.includes(value as string) ? undefined : issue("value", `The option "${key}" takes ${d.values.join(", ")}, not ${String(value)}.`);
150
+ case "function":
151
+ return typeof value === "function" ? undefined : issue("type", `The option "${key}" takes a function.`);
152
+ case "list":
153
+ return Array.isArray(value) ? undefined : issue("type", `The option "${key}" takes a list.`);
154
+ default:
155
+ return typeof value === "object" && value !== null ? undefined : issue("type", `The option "${key}" takes an object.`);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * This function examines option values against the schema of a strategy. It gives one issue for each key without a declaration.
161
+ * It also gives one issue for each value of the wrong type, out of the range, not whole or not one of the values.
162
+ * It changes no value.
163
+ * A strategy without a schema gives no issue.
164
+ */
165
+ export function checkOptions<S extends Step, O extends object>(strategy: Strategy<S, O>, options: Partial<O>): OptionIssue[] {
166
+ if (strategy.schema === undefined) return [];
167
+ const schema = schemaOf(strategy);
168
+ const out: OptionIssue[] = [];
169
+ for (const [key, value] of Object.entries(options)) {
170
+ const d = schema[key];
171
+ const issue = d ? issueOf(key, value, d) : { key, value, problem: "unknown" as const, message: `The strategy "${strategy.name}" has no option "${key}".` };
172
+ if (issue) out.push(issue);
173
+ }
174
+ return out;
175
+ }
176
+
177
+ /**
178
+ * 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.
180
+ */
181
+ export function clampOptions<S extends Step, O extends object>(strategy: Strategy<S, O>, options: Partial<O>): Partial<O> {
182
+ const schema = schemaOf(strategy);
183
+ const out: Record<string, unknown> = { ...options };
184
+ for (const [key, value] of Object.entries(options)) {
185
+ const d = schema[key];
186
+ if (!d || (d.type !== "number" && d.type !== "integer") || typeof value !== "number" || Number.isNaN(value) || isSpecial(d, value)) continue;
187
+ const v = Math.min(d.max, Math.max(d.min, value));
188
+ out[key] = d.type === "integer" ? Math.min(d.max, Math.max(d.min, Math.round(v))) : v;
189
+ }
190
+ return out as Partial<O>;
191
+ }
192
+
193
+ /** This function gives `count` values from `min` to `max` on the scale, on the grain of the step, without repeats. */
194
+ function rangeValues(d: NumberOption, count: number): number[] {
195
+ const n = Math.max(1, Math.floor(count));
196
+ const log = d.scale === "log" && d.min > 0;
197
+ const at = (t: number): number => (log ? d.min * (d.max / d.min) ** t : d.min + (d.max - d.min) * t);
198
+ const step = d.step ?? (d.type === "integer" ? 1 : undefined);
199
+ const snap = (v: number): number => {
200
+ const s = step === undefined ? v : d.min + Math.round((v - d.min) / step) * step;
201
+ const r = Number(s.toPrecision(12));
202
+ return Math.min(d.max, Math.max(d.min, d.type === "integer" ? Math.round(r) : r));
203
+ };
204
+ const out: number[] = [];
205
+ for (let i = 0; i < n; i++) {
206
+ const v = snap(at(n === 1 ? 0 : i / (n - 1)));
207
+ if (!out.includes(v)) out.push(v);
208
+ }
209
+ return out;
210
+ }
211
+
212
+ /**
213
+ * 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.
215
+ * An option of another type has no sweep, so the function throws an error.
216
+ */
217
+ export function sweepValues(declaration: AnyOption, options: SweepOptions = {}): unknown[] {
218
+ switch (declaration.type) {
219
+ case "number":
220
+ case "integer": {
221
+ if (options.count === undefined && declaration.marks !== undefined) return [...declaration.marks];
222
+ const values = rangeValues(declaration, options.count ?? SWEEP_COUNT);
223
+ return options.special ? [...values, ...(declaration.special ?? []).filter((s) => !values.includes(s))] : values;
224
+ }
225
+ case "boolean":
226
+ return [false, true];
227
+ case "enum":
228
+ return [...declaration.values];
229
+ default:
230
+ throw new Error(`An option of the type "${declaration.type}" has no sweep. A sweep takes a number, an integer, a boolean or an enum.`);
231
+ }
232
+ }
233
+
234
+ /** This function gives the declaration of one option of a strategy. When the strategy has no such declaration, the function throws an error. */
235
+ function declarationOf(strategy: Strategy<Step, object>, key: string): AnyOption {
236
+ const d = schemaOf(strategy)[key];
237
+ if (!d) throw new Error(`The strategy "${strategy.name}" has no declaration of the option "${key}".`);
238
+ return d;
239
+ }
240
+
241
+ /**
242
+ * This function gives the settings of a sweep of one option of a strategy, from its declaration only.
243
+ * The name of each setting is the key and the value, for example `stretch 1.5`.
244
+ */
245
+ export function sweepOf<S extends Step, O extends object>(strategy: Strategy<S, O>, key: keyof O & string, options: SweepOptions = {}): OptionSetting<O>[] {
246
+ return sweepValues(declarationOf(strategy, key), options).map((value) => ({ name: `${key} ${String(value)}`, options: { [key]: value } as Partial<O> }));
247
+ }
248
+
249
+ /**
250
+ * This function gives the settings of a grid of options: each combination of the values of the sweeps of the keys.
251
+ * The name of a setting joins the names of its values with commas, in the order of the keys.
252
+ */
253
+ export function sweepGrid<S extends Step, O extends object>(strategy: Strategy<S, O>, keys: readonly (keyof O & string)[], options: SweepOptions = {}): OptionSetting<O>[] {
254
+ let out: OptionSetting<O>[] = [{ name: "", options: {} }];
255
+ for (const key of keys) {
256
+ const sweep = sweepOf(strategy, key, options);
257
+ out = out.flatMap((s) => sweep.map((t) => ({ name: s.name === "" ? t.name : `${s.name}, ${t.name}`, options: { ...s.options, ...t.options } })));
258
+ }
259
+ return keys.length === 0 ? [] : out;
260
+ }
261
+
262
+ /** This function gives the problems of one declaration: a bad range, a bad step, or marks and a default that do not fit. */
263
+ function declarationIssues(key: string, d: AnyOption): string[] {
264
+ const out: string[] = [];
265
+ if (d.describe.trim().length === 0) out.push(`The option "${key}" has no description.`);
266
+ if (d.type === "number" || d.type === "integer") {
267
+ if (!Number.isFinite(d.min) || !Number.isFinite(d.max) || d.min > d.max) out.push(`The option "${key}" has the bad range ${d.min} to ${d.max}.`);
268
+ 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
+ if (d.step !== undefined && !(d.step > 0)) out.push(`The option "${key}" has the step ${d.step}. A step is more than 0.`);
270
+ 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.`);
271
+ for (const v of [...(d.marks ?? []), ...(d.default === undefined ? [] : [d.default])]) {
272
+ const issue = issueOf(key, v, d);
273
+ if (issue) out.push(issue.message);
274
+ }
275
+ } else if (d.type === "enum") {
276
+ if (d.values.length === 0) out.push(`The enum option "${key}" has no values.`);
277
+ if (d.default !== undefined && !d.values.includes(d.default)) out.push(`The default of the option "${key}" is not one of its values.`);
278
+ } else if (d.type === "boolean") {
279
+ if (d.default !== undefined && typeof d.default !== "boolean") out.push(`The default of the option "${key}" is not true or false.`);
280
+ }
281
+ return out;
282
+ }
283
+
284
+ /**
285
+ * This function examines the schema of a strategy. It gives one message for each option of `defaults` without a declaration.
286
+ * It also gives one message for each bad declaration, and for each value of `defaults` that does not fit its declaration.
287
+ * A good schema gives an empty list.
288
+ */
289
+ export function checkSchema(strategy: Strategy<Step, object>): string[] {
290
+ const schema = schemaOf(strategy);
291
+ const out: string[] = [];
292
+ for (const key of Object.keys(strategy.defaults)) if (!(key in schema)) out.push(`The strategy "${strategy.name}" has the option "${key}" in its defaults, without a declaration.`);
293
+ for (const [key, d] of Object.entries(schema)) out.push(...declarationIssues(key, d).map((m) => `The strategy "${strategy.name}": ${m}`));
294
+ out.push(...checkOptions(strategy, strategy.defaults).filter((i) => i.problem !== "unknown").map((i) => `The strategy "${strategy.name}": ${i.message}`));
295
+ return out;
296
+ }
@@ -0,0 +1,221 @@
1
+ import type { CutOptions } from "../groups/cut/index.ts";
2
+ import type { KdOptions } from "../groups/kd/index.ts";
3
+ import type { KeyOptions } from "../groups/keys/index.ts";
4
+ import type { CornersOptions } from "../routers/corners.ts";
5
+ import type { SectionsOptions } from "../routers/sections.ts";
6
+ import type { SharedOptions } from "../routers/shared.ts";
7
+ import type { TrunksOptions } from "../routers/trunks.ts";
8
+ import type { SectionOptions } from "../sections.ts";
9
+ import type { RunOrder } from "../segments.ts";
10
+ import type { ClothoidStrategyOptions, HighwayStrategyOptions, LevelRoomPreset, RadiusOptions, RoomPreset, RouterPreset, SmoothStrategyOptions, TensionOptions } from "./builtins.ts";
11
+ import type { EnumOption, OptionSchema } from "./options.ts";
12
+
13
+ // The declarations of the options of the built-in strategies. Each `default` is the value that the function uses without the option.
14
+ // The test of the defaults runs each strategy with `optionDefaults` and with no option, and compares the output.
15
+
16
+ /** The options of the way finder (`WayOptions`), for each router that goes around the nodes. */
17
+ const WAY_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> = {
18
+ margin: { type: "number", min: 1, max: 20, step: 1, default: 5, describe: "The distance from a box to the corners of the graph." },
19
+ k: { type: "integer", min: 2, max: 16, step: 1, default: 8, describe: "The number of visible neighbours of each corner." },
20
+ smooth: { type: "boolean", default: false, describe: "With `true`, a way in clear view uses the inflection rule, and the other ways get round corners." },
21
+ };
22
+
23
+ /** The declaration of the lane order of the shared ways of the routers `trunks` and `shared`. */
24
+ const LANE_ORDER: EnumOption<RunOrder> = {
25
+ type: "enum",
26
+ values: ["bundle", "run"],
27
+ default: "bundle",
28
+ describe: "With `bundle`, two edges keep one lane order on each shared way of their common stretch. With `run`, each shared way gets its order alone.",
29
+ };
30
+
31
+ /** The declaration of the lane order of the runs of the router `sections`. The default keeps the lane order of version 0.2.0. */
32
+ const RUN_LANE_ORDER: EnumOption<RunOrder> = { ...LANE_ORDER, default: "run" };
33
+
34
+ /** The declarations of the options of the routers `corners` and `corners-smooth`. */
35
+ export const CORNERS_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> = WAY_SCHEMA;
36
+
37
+ /** The declarations of the options of the router `trunks`. */
38
+ export const TRUNKS_SCHEMA: OptionSchema<RouterPreset<TrunksOptions>> = {
39
+ ...WAY_SCHEMA,
40
+ rule: { type: "function", describe: "The grouping rule. The edges from one plug with one key share a trunk." },
41
+ 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." },
42
+ minSize: { type: "integer", min: 1, max: 8, step: 1, default: 2, describe: "A group with fewer edges stays unbundled." },
43
+ laneOrder: LANE_ORDER,
44
+ leave: {
45
+ type: "enum",
46
+ values: ["part", "split"],
47
+ default: "part",
48
+ 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.",
49
+ },
50
+ };
51
+
52
+ /** The declarations of the options of the routers `shared` and `shared-all`. */
53
+ export const SHARED_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
54
+ ...WAY_SCHEMA,
55
+ rule: { type: "function", describe: "The edges with one key share their ways. Without a rule, all edges share." },
56
+ discount: {
57
+ type: "number",
58
+ min: 0.05,
59
+ max: 1,
60
+ step: 0.05,
61
+ default: 0.5,
62
+ describe: "The cost of a link that an edge with the same key uses already, as a share of its length.",
63
+ },
64
+ laneOrder: LANE_ORDER,
65
+ };
66
+
67
+ /** The declarations of the limits of the tree of sections (`SectionOptions`). */
68
+ export const SECTION_SCHEMA: OptionSchema<Required<SectionOptions>> = {
69
+ cap: { type: "integer", min: 1, max: 32, step: 1, scale: "log", marks: [4, 6, 8, 12, 16], default: 8, describe: "The section limit. A section with more nodes splits in four." },
70
+ maxDepth: { type: "integer", min: 1, max: 32, step: 1, default: 16, describe: "The depth limit of the tree." },
71
+ collapse: { type: "integer", min: 1, max: 64, step: 1, default: 32, describe: "A cell shrinks to one quarter at most this many times without a split." },
72
+ };
73
+
74
+ /** The declarations of the options of the routers `sections` and `sections-tree`. */
75
+ export const SECTIONS_SCHEMA: OptionSchema<RouterPreset<SectionsOptions>> = {
76
+ ...WAY_SCHEMA,
77
+ ...SECTION_SCHEMA,
78
+ hubs: {
79
+ type: "integer",
80
+ min: 0,
81
+ max: 8,
82
+ step: 1,
83
+ special: [Infinity],
84
+ default: Infinity,
85
+ describe: "The number of hubs on each side of the lowest common section that a way passes. `Infinity` passes each hub.",
86
+ },
87
+ stretch: {
88
+ type: "number",
89
+ min: 1,
90
+ max: 4,
91
+ step: 0.25,
92
+ special: [Infinity],
93
+ marks: [1.25, 1.5, 2, 3, Infinity],
94
+ default: 2,
95
+ describe: "The hub limit. A way through the hubs is at most this many times the straight line. `Infinity` keeps each such way.",
96
+ },
97
+ stretchPad: { type: "number", min: 0, max: 16, step: 1, default: 4, describe: "A way through the hubs can be this many margins longer than the hub limit." },
98
+ hubSlack: {
99
+ type: "number",
100
+ min: 0,
101
+ max: 320,
102
+ step: 10,
103
+ special: [Infinity],
104
+ marks: [0, 40, 80, 160, Infinity],
105
+ default: 80,
106
+ describe: "A way skips a hub that is this many units farther from the target than the way was before.",
107
+ },
108
+ tightMargin: { type: "number", min: 0, max: 5, step: 0.5, default: 1, describe: "The margin of the second corner graph of the level links." },
109
+ plugInset: { type: "number", min: 0, max: 12, step: 1, default: 6, describe: "A plug of a level link stays this far from the corners of its side." },
110
+ throughCost: {
111
+ type: "number",
112
+ min: 0,
113
+ max: 16,
114
+ step: 1,
115
+ marks: [0, 2, 4, 8, 16],
116
+ default: 8,
117
+ describe: "The cost of a level link through a far box, in mean box sizes. The value 0 gives the links of version 0.1.5.",
118
+ },
119
+ group: { type: "strategy", step: "group", describe: "The grouping rule: a strategy of the step `group`. Without it, the router uses the quadtree." },
120
+ groupOptions: { type: "object", describe: "The options of the strategy `group`." },
121
+ levels: { type: "list", describe: "The depths of the tree that get a level with groups. Without it, each depth gets a level." },
122
+ laneOrder: RUN_LANE_ORDER,
123
+ };
124
+
125
+ /** The declarations of the option of the curves `rounded` and `arc`. */
126
+ export const RADIUS_SCHEMA: OptionSchema<RadiusOptions> = {
127
+ radius: { type: "number", min: 0, max: 48, step: 1, describe: "The radius of the corners." },
128
+ };
129
+
130
+ /** The declarations of the options of the curve `highway`. */
131
+ export const HIGHWAY_SCHEMA: OptionSchema<HighwayStrategyOptions> = {
132
+ ...RADIUS_SCHEMA,
133
+ minRadius: { type: "number", min: 0, max: 24, step: 1, describe: "The smallest radius of a lane at a bend of a bundle." },
134
+ jog: {
135
+ type: "enum",
136
+ values: ["biarc", "cubic", "quintic"],
137
+ default: "biarc",
138
+ describe: "The shape of the S curve where a lane changes its offset. A `quintic` S curve has no jump of the curvature.",
139
+ },
140
+ jogPieces: { type: "integer", min: 1, max: 8, step: 1, default: 4, describe: "The number of cubic pieces of a `quintic` S curve." },
141
+ };
142
+
143
+ /** The declarations of the options of the curve `smooth`. */
144
+ export const SMOOTH_SCHEMA: OptionSchema<SmoothStrategyOptions> = {
145
+ ...RADIUS_SCHEMA,
146
+ smoothing: { type: "number", min: 0, max: 1, step: 0.05, describe: "The share of the turn of a corner in its two spirals. The value 0 gives the curve `arc`." },
147
+ };
148
+
149
+ /** The declarations of the options of the curve `clothoid`. */
150
+ export const CLOTHOID_SCHEMA: OptionSchema<ClothoidStrategyOptions> = {
151
+ ...RADIUS_SCHEMA,
152
+ spiral: { type: "number", min: 0, max: 1, step: 0.05, describe: "The share of the turn of a corner in its two clothoids. The value 0 gives the curve `arc`." },
153
+ tolerance: { type: "number", min: 0.05, max: 1, step: 0.05, describe: "The largest distance from the cubic pieces to the true clothoid." },
154
+ };
155
+
156
+ /** The declarations of the option of the curve `spline`. */
157
+ export const TENSION_SCHEMA: OptionSchema<TensionOptions> = {
158
+ tension: { type: "number", min: 0, max: 1, step: 0.05, describe: "The tension of the spline, from 0 (the spline) to 1 (the polyline)." },
159
+ };
160
+
161
+ /** The declarations of the options of the room strategies: the options of `roomDemand`, without the method. */
162
+ export const ROOM_SCHEMA: OptionSchema<RoomPreset> = {
163
+ gutter: { type: "number", min: 0, max: 32, step: 1, marks: [4, 8, 12, 16], default: 8, describe: "The smallest width of each gap." },
164
+ spacing: { type: "number", min: 1, max: 12, step: 1, marks: [2, 4, 6, 8], default: 4, describe: "The width of one lane: the distance between two lines that run side by side." },
165
+ maxGrowth: {
166
+ type: "number",
167
+ min: 0,
168
+ max: 1,
169
+ step: 0.01,
170
+ special: [Infinity],
171
+ marks: [0, 0.06, 0.09, 0.12, 0.15, 0.18, 0.21, 0.25, 0.3, 0.35, 0.5, Infinity],
172
+ default: Infinity,
173
+ describe: "The largest growth of the board on each axis, as a share of its size. `Infinity` sets no limit.",
174
+ },
175
+ router: { type: "object", describe: "The router of the method `routes`. Without it, the links go as the links of a zoom level." },
176
+ lanes: { type: "enum", values: ["link", "count"], default: "link", describe: "With `count`, a link takes one lane for each of its edges. With `link`, a link takes one lane." },
177
+ snap: { type: "number", min: 0, max: 4, step: 0.25, default: 0.5, describe: "Two box edges nearer than this distance make one boundary of the sweep." },
178
+ passes: { type: "integer", min: 1, max: 4, step: 1, marks: [1, 2], default: 1, describe: "The number of passes of the demand. Each pass after the first finds the lanes on the open board." },
179
+ bends: {
180
+ type: "object",
181
+ describe: "The room for the bends of the bundles: a router that bundles, and the lane options of `highwayCurves`. Without it, the bends get no room.",
182
+ },
183
+ };
184
+
185
+ /** The declarations of the options of the room strategy `levels`. They are the options of the room, the levels with room and the limits of the quadtree. */
186
+ export const LEVEL_ROOM_SCHEMA: OptionSchema<LevelRoomPreset> = {
187
+ ...ROOM_SCHEMA,
188
+ depths: { type: "list", describe: "The depths of the tree that get room. Without it, each depth with groups gets room." },
189
+ nodes: { type: "boolean", default: false, describe: "With `true`, the last level, with each node alone, also gets room." },
190
+ board: { type: "enum", values: ["shared", "each"], default: "shared", describe: "With `shared`, each level uses the board of the nearest level. With `each`, each level has its own board." },
191
+ ...SECTION_SCHEMA,
192
+ };
193
+
194
+ /** The declarations of the options of the grouping rule `keys`. */
195
+ export const KEY_SCHEMA: OptionSchema<KeyOptions> = {
196
+ key: { type: "function", describe: "This function gives the key of the group of each node." },
197
+ split: { type: "object", describe: "With these limits of the quadtree, a large group splits into cells. Without them, each group is one leaf." },
198
+ };
199
+
200
+ /** The declarations of the limits of the tree of the grouping rules `kd` and `cut`. */
201
+ const SPLIT_TREE_SCHEMA: OptionSchema<Required<Pick<KdOptions, "cap" | "maxDepth" | "fanout">>> = {
202
+ cap: { ...SECTION_SCHEMA.cap, describe: "The section limit. A section with more nodes splits." },
203
+ maxDepth: SECTION_SCHEMA.maxDepth,
204
+ fanout: { type: "integer", min: 2, max: 4, step: 2, default: 4, describe: "The number of children of a section. With 4, a section splits two times on one level." },
205
+ };
206
+
207
+ /** The declarations of the options of the grouping rule `kd`. */
208
+ export const KD_SCHEMA: OptionSchema<KdOptions> = {
209
+ ...SPLIT_TREE_SCHEMA,
210
+ line: { type: "enum", values: ["median", "middle", "slide"], default: "slide", describe: "The place of the line in the cell: the median node, the middle, or the middle that slides to the nearest node." },
211
+ axis: { type: "enum", values: ["longer", "alternate"], default: "longer", describe: "The axis of each split: across the longer side of the cell, or x and y in turn." },
212
+ };
213
+
214
+ /** The declarations of the options of the grouping rule `cut`. */
215
+ export const CUT_SCHEMA: OptionSchema<CutOptions> = {
216
+ ...SPLIT_TREE_SCHEMA,
217
+ balance: { type: "number", min: 0.05, max: 0.5, step: 0.05, marks: [0.1, 0.25, 0.4], default: 0.25, describe: "The smallest share of the nodes in each part of a split." },
218
+ objective: { type: "enum", values: ["ratio", "normalized", "sparsest"], default: "ratio", describe: "The cost of a line: the ratio cut, the normalized cut or the sparsest cut." },
219
+ 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." },
220
+ guard: { type: "boolean", default: true, describe: "With `true`, each section also tests the quadtree split, and keeps the split that cuts fewer edges." },
221
+ };