@mark1russell7/highways 0.9.0 → 0.9.1

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/build/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_RNG, RECORD_EPS, RECT_SCATTERED, SPLIT_SCHEMA, canonicalJson, canonicalJson as canonicalJson$1, checkDeclared, compareMeasure, corpusHashOf, createFamilyRegistry, createFamilyRegistry as createFamilyRegistry$1, dimensionAt, dimensionUnit, exercised, familyKind, levelsOf, minEffectOf, outputGroups, outputHash, paramProblem, pickMoves, portfolio, randomInt, selectBest, sha256Hex, sha256Hex as sha256Hex$1, splitFileName } from "@mark1russell7/layout-contract";
1
+ import { DEFAULT_RNG, RECORD_EPS, RECT_SCATTERED, SPLIT_SCHEMA, canonicalJson, canonicalJson as canonicalJson$1, checkDeclared, compareMeasure, corpusHashOf, createFamilyRegistry, createFamilyRegistry as createFamilyRegistry$1, dimensionAt, dimensionUnit, exercised, familyKind, levelsOf, minEffectOf, outputGroups, outputHash, paramProblem, pickMoves, portPoint, portfolio, randomInt, selectBest, sha256Hex, sha256Hex as sha256Hex$1, splitFileName } from "@mark1russell7/layout-contract";
2
2
  // src/geom.ts
3
3
  /** This function gives the distance between two points. */
4
4
  function dist(a, b) {
@@ -331,14 +331,75 @@ function nodeIndex(g) {
331
331
  for (const n of g.nodes) m.set(n.id, n);
332
332
  return m;
333
333
  }
334
- /** This function gives the absolute place of a fixed plug, or `undefined` for a free plug or no plug. */
334
+ /** The names of the sides in `@mark1russell7/layout-contract`. */
335
+ const CONTRACT_SIDE = {
336
+ north: "top",
337
+ east: "right",
338
+ south: "bottom",
339
+ west: "left"
340
+ };
341
+ /**
342
+ * This function gives the place of a fixed plug, relative to the top-left corner of the node box, or `undefined` for a free plug.
343
+ * A plug with `at` gives `at`. A plug with `side` and `along` gives the point of `portPoint` of `@mark1russell7/layout-contract`.
344
+ */
345
+ function portOffset(box, port) {
346
+ if (port.at !== void 0) return port.at;
347
+ if (port.side === void 0 || port.along === void 0) return void 0;
348
+ return portPoint(box, {
349
+ id: port.id,
350
+ side: CONTRACT_SIDE[port.side],
351
+ along: port.along
352
+ });
353
+ }
354
+ /** This function throws a `RangeError` when the `along` of a plug is bad: not finite, outside 0 to 1, without `side` or with `at`. */
355
+ function checkPort(node, port) {
356
+ if (port.along === void 0) return;
357
+ const at = `The plug "${port.id}" of the node "${node.id}"`;
358
+ if (typeof port.along !== "number" || !Number.isFinite(port.along) || port.along < 0 || port.along > 1) throw new RangeError(`${at} has the along value ${String(port.along)}. It must be a finite number from 0 to 1.`);
359
+ if (port.side === void 0) throw new RangeError(`${at} has an along value and no side. A plug with along needs a side.`);
360
+ if (port.at !== void 0) throw new RangeError(`${at} has an along value and an at point. Give one of the two.`);
361
+ }
362
+ /**
363
+ * This function checks the plugs of a graph, and gives each plug with `side` and `along` a fixed `at`. The plug keeps its `side`.
364
+ * Each router calls it at the start of `route`, so all routers agree. A graph without `along` comes back as the same object.
365
+ * It throws a `RangeError` for a bad `along` (`checkPort`).
366
+ */
367
+ function resolvePorts(graph) {
368
+ let found = false;
369
+ for (const n of graph.nodes) for (const p of n.ports ?? []) {
370
+ checkPort(n, p);
371
+ if (p.along !== void 0) found = true;
372
+ }
373
+ if (!found) return graph;
374
+ const nodes = graph.nodes.map((n) => {
375
+ if (!n.ports?.some((p) => p.along !== void 0)) return n;
376
+ const ports = n.ports.map((p) => {
377
+ if (p.along === void 0) return p;
378
+ return {
379
+ id: p.id,
380
+ side: p.side,
381
+ at: portOffset(n.box, p)
382
+ };
383
+ });
384
+ return {
385
+ ...n,
386
+ ports
387
+ };
388
+ });
389
+ return {
390
+ ...graph,
391
+ nodes
392
+ };
393
+ }
394
+ /** This function gives the absolute place of a fixed plug, or `undefined` for a free plug or no plug. A plug with `side` and `along` is fixed. */
335
395
  function fixedPortPoint(node, end) {
336
396
  if (end.port === void 0) return void 0;
337
397
  const p = node.ports?.find((q) => q.id === end.port);
338
- if (p?.at === void 0) return void 0;
398
+ const at = p === void 0 ? void 0 : portOffset(node.box, p);
399
+ if (at === void 0) return void 0;
339
400
  return {
340
- x: node.box.x + p.at.x,
341
- y: node.box.y + p.at.y
401
+ x: node.box.x + at.x,
402
+ y: node.box.y + at.y
342
403
  };
343
404
  }
344
405
  // src/fixtures.ts
@@ -4984,7 +5045,8 @@ function sidePlugs(ways, s, es, t, et, options = {}) {
4984
5045
  function cornersRouter(options) {
4985
5046
  const router = {
4986
5047
  name: options.name,
4987
- route(graph) {
5048
+ route(input) {
5049
+ const graph = resolvePorts(input);
4988
5050
  const byId = nodeIndex(graph);
4989
5051
  const ways = new WayFinder(graph.nodes, options);
4990
5052
  const routes = [];
@@ -5065,7 +5127,7 @@ function spacePlugs(ends, gap) {
5065
5127
  ps,
5066
5128
  4
5067
5129
  ]]) {
5068
- if ((end.port === void 0 ? void 0 : node.ports?.find((p) => p.id === end.port))?.at !== void 0) continue;
5130
+ if (fixedPortPoint(node, end) !== void 0) continue;
5069
5131
  const key = JSON.stringify([
5070
5132
  node.id,
5071
5133
  plug.normal.x,
@@ -5114,7 +5176,8 @@ function spacePlugs(ends, gap) {
5114
5176
  */
5115
5177
  const plugsRouter = {
5116
5178
  name: "plugs",
5117
- route(graph) {
5179
+ route(input) {
5180
+ const graph = resolvePorts(input);
5118
5181
  const byId = nodeIndex(graph);
5119
5182
  const routes = [];
5120
5183
  for (const e of graph.edges) {
@@ -5943,7 +6006,8 @@ const dedupe$4 = (p) => p.filter((q, i) => i === 0 || dist(q, p[i - 1]) > 1e-9);
5943
6006
  function sectionsRouter(options) {
5944
6007
  const router = {
5945
6008
  name: options.name,
5946
- route(graph) {
6009
+ route(input) {
6010
+ const graph = resolvePorts(input);
5947
6011
  const stretch = options.stretch ?? SECTIONS_DEFAULTS.stretch;
5948
6012
  const stretchPad = options.stretchPad ?? SECTIONS_DEFAULTS.stretchPad;
5949
6013
  const hubSlack = options.hubSlack ?? SECTIONS_DEFAULTS.hubSlack;
@@ -6984,7 +7048,8 @@ function trunksRouter(options) {
6984
7048
  const rule = groupRuleOf(options.rule);
6985
7049
  const router = {
6986
7050
  name: options.name,
6987
- route(graph) {
7051
+ route(input) {
7052
+ const graph = resolvePorts(input);
6988
7053
  const byId = nodeIndex(graph);
6989
7054
  const ways = new WayFinder(graph.nodes, options);
6990
7055
  const points = /* @__PURE__ */ new Map();
@@ -7162,7 +7227,7 @@ function turn(from, base, to) {
7162
7227
  * Thus the edges of a key come together on common ways. Each stretch that several edges share has the same points in each route.
7163
7228
  * The result gives each stretch as a run, with the order of its lanes.
7164
7229
  */
7165
- function sharedRouter(options) {
7230
+ function sharedRouter(options, negotiation) {
7166
7231
  const discount = Math.min(1, Math.max(.05, options.discount ?? .5));
7167
7232
  const flow = options.cost === "flow";
7168
7233
  const alpha = Math.min(1, Math.max(.1, options.alpha ?? .6));
@@ -7179,7 +7244,8 @@ function sharedRouter(options) {
7179
7244
  };
7180
7245
  const router = {
7181
7246
  name: options.name,
7182
- route(graph) {
7247
+ route(input) {
7248
+ const graph = resolvePorts(input);
7183
7249
  const byId = nodeIndex(graph);
7184
7250
  const ways = new WayFinder(graph.nodes, options);
7185
7251
  const corners = ways.corners;
@@ -7221,8 +7287,11 @@ function sharedRouter(options) {
7221
7287
  if (wd === void 0) widths.set(k, wd = channelWidth(ways.obstacles, corners.points[i], corners.points[j]));
7222
7288
  return wd;
7223
7289
  };
7224
- /** This function routes one way. `before` holds the edges of the key on each link in the pass before, for the option `reuse`. */
7225
- const route = (w, before) => {
7290
+ /**
7291
+ * This function routes one way. `before` holds the edges of the key on each link in the pass before, for the option `reuse`.
7292
+ * `history` holds the history of each link of the key, for the negotiation of `attraction`.
7293
+ */
7294
+ const route = (w, before, history) => {
7226
7295
  let used = discounted.get(w.key);
7227
7296
  if (!used) discounted.set(w.key, used = /* @__PURE__ */ new Map());
7228
7297
  const mine = used;
@@ -7239,6 +7308,20 @@ function sharedRouter(options) {
7239
7308
  cost = (i, j, d) => plain(i, j, d) / (1 + reuse * (before.get(link(i, j)) ?? 0)) ** reuseExponent;
7240
7309
  floor /= (1 + reuse * most) ** reuseExponent;
7241
7310
  }
7311
+ if (negotiation !== void 0 && history !== void 0) {
7312
+ const plain = cost;
7313
+ const a = negotiation.attraction;
7314
+ const b = negotiation.capacityWeight;
7315
+ let most = 0;
7316
+ for (const h of history.values()) most = Math.max(most, h);
7317
+ cost = (i, j, d) => {
7318
+ const k = link(i, j);
7319
+ const use = (mine.get(k)?.length ?? 0) + 1;
7320
+ const cap = Math.max(1, Math.floor(widthOf(i, j) / negotiation.laneWidth));
7321
+ return plain(i, j, d) * (1 - a * (history.get(k) ?? 0)) * (1 + b * Math.max(0, use - cap));
7322
+ };
7323
+ floor *= 1 - a * most;
7324
+ }
7242
7325
  const way = corners.path(ways.out(w.ps), ways.out(w.pt), cost, floor);
7243
7326
  if (!way) return;
7244
7327
  w.found = true;
@@ -7254,8 +7337,38 @@ function sharedRouter(options) {
7254
7337
  if (flow) top.set(w.key, Math.max(top.get(w.key) ?? 0, mine.get(link(a, b)).length));
7255
7338
  }
7256
7339
  };
7257
- for (const w of order) route(w);
7258
- for (let pass = 1; pass < passes; pass++) {
7340
+ /**
7341
+ * This function gives the passes of the negotiation (HWY-044). After each pass, the history of each link grows by `use / m`,
7342
+ * with `m` the edges of the key, up to `0.9 / attraction`. Each later pass routes each edge again from no ways.
7343
+ */
7344
+ const negotiate = (list) => {
7345
+ const nego = negotiation;
7346
+ const histories = /* @__PURE__ */ new Map();
7347
+ const size = /* @__PURE__ */ new Map();
7348
+ for (const w of list) size.set(w.key, (size.get(w.key) ?? 0) + 1);
7349
+ const most = nego.attraction > 0 ? .9 / nego.attraction : Infinity;
7350
+ for (let pass = 0; pass < Math.max(1, Math.floor(nego.passes)); pass++) {
7351
+ if (pass > 0) {
7352
+ for (const [key, links] of discounted) {
7353
+ let h = histories.get(key);
7354
+ if (!h) histories.set(key, h = /* @__PURE__ */ new Map());
7355
+ const m = size.get(key) ?? 1;
7356
+ for (const [k, users] of links) h.set(k, Math.min(most, (h.get(k) ?? 0) + users.length / m));
7357
+ }
7358
+ discounted.clear();
7359
+ top.clear();
7360
+ for (const w of list) {
7361
+ w.found = false;
7362
+ w.seq = [];
7363
+ w.ids = [];
7364
+ }
7365
+ }
7366
+ for (const w of list) route(w, void 0, histories.get(w.key) ?? /* @__PURE__ */ new Map());
7367
+ }
7368
+ };
7369
+ if (negotiation !== void 0) negotiate(order);
7370
+ else for (const w of order) route(w);
7371
+ for (let pass = 1; negotiation === void 0 && pass < passes; pass++) {
7259
7372
  if (reuse > 0) {
7260
7373
  const before = /* @__PURE__ */ new Map();
7261
7374
  for (const [key, links] of discounted) before.set(key, new Map([...links].map(([k, list]) => [k, list.length])));
@@ -7497,7 +7610,8 @@ function boxExit(box, toward) {
7497
7610
  */
7498
7611
  const straightRouter = {
7499
7612
  name: "straight",
7500
- route(graph) {
7613
+ route(input) {
7614
+ const graph = resolvePorts(input);
7501
7615
  const byId = nodeIndex(graph);
7502
7616
  const routes = [];
7503
7617
  for (const e of graph.edges) {
@@ -7516,6 +7630,209 @@ const straightRouter = {
7516
7630
  return { routes };
7517
7631
  }
7518
7632
  };
7633
+ // src/strategies/model.ts
7634
+ /**
7635
+ * The steps that have strategies, in the order of the pipeline.
7636
+ * 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.
7637
+ * The step `whole` runs the other steps, so it comes last.
7638
+ */
7639
+ const STEPS = [
7640
+ "group",
7641
+ "plugs",
7642
+ "router",
7643
+ "highway",
7644
+ "levels",
7645
+ "curve",
7646
+ "room",
7647
+ "whole"
7648
+ ];
7649
+ /** This function gives a frozen copy of a schema, with each declaration frozen. */
7650
+ function freezeSchema(schema) {
7651
+ const out = {};
7652
+ for (const [key, d] of Object.entries(schema)) out[key] = Object.freeze({ ...d });
7653
+ return Object.freeze(out);
7654
+ }
7655
+ /**
7656
+ * This function makes a strategy from its definition. The strategy fills the missing options from `defaults`.
7657
+ * When the caller gives no option, the definition gets the `defaults` object itself.
7658
+ */
7659
+ function defineStrategy(definition) {
7660
+ const { step, name, describe, defaults, schema, maxNodes } = definition;
7661
+ if (name.length === 0) throw new Error(`A strategy of the step "${step}" has no name.`);
7662
+ const strategy = {
7663
+ step,
7664
+ name,
7665
+ describe,
7666
+ defaults: Object.freeze({ ...defaults }),
7667
+ ...schema === void 0 ? {} : { schema: freezeSchema(schema) },
7668
+ ...maxNodes === void 0 ? {} : { maxNodes },
7669
+ run(input, options) {
7670
+ const all = options === void 0 || Object.keys(options).length === 0 ? strategy.defaults : {
7671
+ ...strategy.defaults,
7672
+ ...options
7673
+ };
7674
+ return definition.run(input, all);
7675
+ }
7676
+ };
7677
+ return Object.freeze(strategy);
7678
+ }
7679
+ /** 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. */
7680
+ function createRegistry(strategies) {
7681
+ const out = {
7682
+ group: [],
7683
+ plugs: [],
7684
+ router: [],
7685
+ highway: [],
7686
+ levels: [],
7687
+ curve: [],
7688
+ room: [],
7689
+ whole: []
7690
+ };
7691
+ for (const s of strategies) {
7692
+ const list = out[s.step];
7693
+ if (!list) throw new Error(`There is no step "${String(s.step)}". The steps are ${STEPS.join(", ")}.`);
7694
+ if (list.some((t) => t.name === s.name)) throw new Error(`The step "${s.step}" has two strategies with the name "${s.name}".`);
7695
+ list.push(s);
7696
+ }
7697
+ return Object.freeze({
7698
+ group: Object.freeze(out.group),
7699
+ plugs: Object.freeze(out.plugs),
7700
+ router: Object.freeze(out.router),
7701
+ highway: Object.freeze(out.highway),
7702
+ levels: Object.freeze(out.levels),
7703
+ curve: Object.freeze(out.curve),
7704
+ room: Object.freeze(out.room),
7705
+ whole: Object.freeze(out.whole)
7706
+ });
7707
+ }
7708
+ /** This function gives a new registry with the strategies of `registry` and then `strategies`. It does not change `registry`. */
7709
+ function register(registry, ...strategies) {
7710
+ return createRegistry([...STEPS.flatMap((step) => listStrategies(registry, step)), ...strategies]);
7711
+ }
7712
+ /** This function gives the strategies of one step of a registry, in order. */
7713
+ function listStrategies(registry, step) {
7714
+ return registry[step] ?? [];
7715
+ }
7716
+ /** This function gives the strategy of one step with a name, or `undefined` when the registry has no such strategy. */
7717
+ function findStrategy(registry, step, name) {
7718
+ return listStrategies(registry, step).find((s) => s.name === name);
7719
+ }
7720
+ /** This function gives the strategy of one step with a name. When the registry has no such strategy, the function throws an error. */
7721
+ function getStrategy(registry, step, name) {
7722
+ const s = findStrategy(registry, step, name);
7723
+ if (!s) throw new Error(`There is no ${step} "${name}". The registry has ${listStrategies(registry, step).map((t) => t.name).join(", ")}.`);
7724
+ return s;
7725
+ }
7726
+ /** This function gives a `Router` that runs a router strategy with the options. The router keeps the name and the `maxNodes` of the strategy. */
7727
+ function asRouter(strategy, options) {
7728
+ return {
7729
+ name: strategy.name,
7730
+ ...strategy.maxNodes === void 0 ? {} : { maxNodes: strategy.maxNodes },
7731
+ route: (graph) => strategy.run(graph, options)
7732
+ };
7733
+ }
7734
+ /** This function gives the router strategies of a registry as `Router` objects, in order. The benchmark races them. */
7735
+ function routersOf(registry) {
7736
+ return listStrategies(registry, "router").map((s) => asRouter(s));
7737
+ }
7738
+ // src/routers/attraction/index.ts
7739
+ /** The default options of the router `attraction`. */
7740
+ const ATTRACTION_DEFAULTS = Object.freeze({
7741
+ passes: 3,
7742
+ attraction: .5,
7743
+ capacityWeight: 1,
7744
+ laneWidth: 4
7745
+ });
7746
+ /** The declarations of the options of the router `attraction` (HWY-044). */
7747
+ const ATTRACTION_SCHEMA = {
7748
+ passes: {
7749
+ type: "integer",
7750
+ min: 1,
7751
+ max: 10,
7752
+ step: 1,
7753
+ marks: [
7754
+ 2,
7755
+ 3,
7756
+ 5
7757
+ ],
7758
+ default: 3,
7759
+ describe: "The number of passes. Each later pass routes each edge again from no ways."
7760
+ },
7761
+ attraction: {
7762
+ type: "number",
7763
+ min: 0,
7764
+ max: 1,
7765
+ step: .05,
7766
+ marks: [
7767
+ .25,
7768
+ .5,
7769
+ .75
7770
+ ],
7771
+ default: .5,
7772
+ describe: "The pull of the history of a link. A busy link of the passes before costs less."
7773
+ },
7774
+ capacityWeight: {
7775
+ type: "number",
7776
+ min: 0,
7777
+ max: 10,
7778
+ step: .5,
7779
+ marks: [
7780
+ 0,
7781
+ 1,
7782
+ 4
7783
+ ],
7784
+ default: 1,
7785
+ describe: "The cost weight of each edge over the capacity of a link."
7786
+ },
7787
+ laneWidth: {
7788
+ type: "number",
7789
+ min: 1,
7790
+ max: 12,
7791
+ step: 1,
7792
+ marks: [4, 6],
7793
+ default: 4,
7794
+ describe: "The width of one lane. The capacity of a link is its free width divided by this width."
7795
+ }
7796
+ };
7797
+ /**
7798
+ * This function makes the router `attraction` (HWY-044). It is the router `shared` with a negotiation over the passes.
7799
+ * 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`.
7800
+ * In each pass, a link costs `len (1 - a h) (1 + b max(0, use - cap))` times the cost in `shared`.
7801
+ * The busy links of the passes before thus pull the edges, and a link over its capacity pushes them away.
7802
+ */
7803
+ function attractionRouter(options) {
7804
+ const o = {
7805
+ ...ATTRACTION_DEFAULTS,
7806
+ ...options
7807
+ };
7808
+ return sharedRouter({
7809
+ name: o.name,
7810
+ ...o.rule === void 0 ? {} : { rule: o.rule },
7811
+ ...o.maxNodes === void 0 ? {} : { maxNodes: o.maxNodes }
7812
+ }, {
7813
+ passes: Math.max(1, Math.floor(o.passes)),
7814
+ attraction: Math.min(1, Math.max(0, o.attraction)),
7815
+ capacityWeight: Math.max(0, o.capacityWeight),
7816
+ laneWidth: Math.max(1e-6, o.laneWidth)
7817
+ });
7818
+ }
7819
+ /**
7820
+ * 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.
7821
+ * A screen module or a consumer adds it to a registry.
7822
+ */
7823
+ const attractionStrategy = defineStrategy({
7824
+ step: "router",
7825
+ name: "attraction",
7826
+ 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.",
7827
+ defaults: { ...ATTRACTION_DEFAULTS },
7828
+ schema: ATTRACTION_SCHEMA,
7829
+ maxNodes: 1e4,
7830
+ run: (graph, o) => attractionRouter({
7831
+ ...o,
7832
+ name: "attraction",
7833
+ rule: byCollection
7834
+ }).route(graph)
7835
+ });
7519
7836
  // src/routers/index.ts
7520
7837
  /** The built-in routers. The registry `STRATEGIES` holds the same routers as strategies, and the benchmark races the routers of the registry. */
7521
7838
  const ROUTERS = [
@@ -8409,154 +8726,49 @@ function facingEnds(input) {
8409
8726
  }
8410
8727
  return out;
8411
8728
  }
8412
- // src/strategies/model.ts
8413
- /**
8414
- * The steps that have strategies, in the order of the pipeline.
8415
- * 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.
8416
- * The step `whole` runs the other steps, so it comes last.
8417
- */
8418
- const STEPS = [
8419
- "group",
8420
- "plugs",
8421
- "router",
8422
- "highway",
8423
- "levels",
8424
- "curve",
8425
- "room",
8426
- "whole"
8427
- ];
8428
- /** This function gives a frozen copy of a schema, with each declaration frozen. */
8429
- function freezeSchema(schema) {
8430
- const out = {};
8431
- for (const [key, d] of Object.entries(schema)) out[key] = Object.freeze({ ...d });
8432
- return Object.freeze(out);
8729
+ // src/highways/model.ts
8730
+ /** This function gives the point at a place on a polyline. A place outside the polyline gives its nearest end. */
8731
+ function pointAt(line, at) {
8732
+ if (line.length === 0) throw new Error("The polyline has no point.");
8733
+ if (at <= 0) return line[0];
8734
+ if (at >= line.length - 1) return line[line.length - 1];
8735
+ const i = Math.floor(at);
8736
+ const u = at - i;
8737
+ const a = line[i];
8738
+ if (u === 0) return a;
8739
+ const b = line[i + 1];
8740
+ return {
8741
+ x: a.x + (b.x - a.x) * u,
8742
+ y: a.y + (b.y - a.y) * u
8743
+ };
8744
+ }
8745
+ /** This function gives the length of a polyline from the place `from` to the place `to`, with `from` not larger than `to`. */
8746
+ function lengthBetween(line, from, to) {
8747
+ if (to <= from) return 0;
8748
+ let sum = 0;
8749
+ let prev = pointAt(line, from);
8750
+ for (let i = Math.floor(from) + 1; i < to; i++) {
8751
+ const p = line[i];
8752
+ sum += dist(prev, p);
8753
+ prev = p;
8754
+ }
8755
+ return sum + dist(prev, pointAt(line, to));
8756
+ }
8757
+ /** This function gives a member of a highway with its merge point and its split point at the places `on` and `off` of the spine. */
8758
+ function memberAt(spine, edge, on, off, seq = 0) {
8759
+ return {
8760
+ edge,
8761
+ on,
8762
+ off,
8763
+ merge: pointAt(spine, on),
8764
+ split: pointAt(spine, off),
8765
+ seq
8766
+ };
8433
8767
  }
8434
8768
  /**
8435
- * This function makes a strategy from its definition. The strategy fills the missing options from `defaults`.
8436
- * When the caller gives no option, the definition gets the `defaults` object itself.
8437
- */
8438
- function defineStrategy(definition) {
8439
- const { step, name, describe, defaults, schema, maxNodes } = definition;
8440
- if (name.length === 0) throw new Error(`A strategy of the step "${step}" has no name.`);
8441
- const strategy = {
8442
- step,
8443
- name,
8444
- describe,
8445
- defaults: Object.freeze({ ...defaults }),
8446
- ...schema === void 0 ? {} : { schema: freezeSchema(schema) },
8447
- ...maxNodes === void 0 ? {} : { maxNodes },
8448
- run(input, options) {
8449
- const all = options === void 0 || Object.keys(options).length === 0 ? strategy.defaults : {
8450
- ...strategy.defaults,
8451
- ...options
8452
- };
8453
- return definition.run(input, all);
8454
- }
8455
- };
8456
- return Object.freeze(strategy);
8457
- }
8458
- /** 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. */
8459
- function createRegistry(strategies) {
8460
- const out = {
8461
- group: [],
8462
- plugs: [],
8463
- router: [],
8464
- highway: [],
8465
- levels: [],
8466
- curve: [],
8467
- room: [],
8468
- whole: []
8469
- };
8470
- for (const s of strategies) {
8471
- const list = out[s.step];
8472
- if (!list) throw new Error(`There is no step "${String(s.step)}". The steps are ${STEPS.join(", ")}.`);
8473
- if (list.some((t) => t.name === s.name)) throw new Error(`The step "${s.step}" has two strategies with the name "${s.name}".`);
8474
- list.push(s);
8475
- }
8476
- return Object.freeze({
8477
- group: Object.freeze(out.group),
8478
- plugs: Object.freeze(out.plugs),
8479
- router: Object.freeze(out.router),
8480
- highway: Object.freeze(out.highway),
8481
- levels: Object.freeze(out.levels),
8482
- curve: Object.freeze(out.curve),
8483
- room: Object.freeze(out.room),
8484
- whole: Object.freeze(out.whole)
8485
- });
8486
- }
8487
- /** This function gives a new registry with the strategies of `registry` and then `strategies`. It does not change `registry`. */
8488
- function register(registry, ...strategies) {
8489
- return createRegistry([...STEPS.flatMap((step) => listStrategies(registry, step)), ...strategies]);
8490
- }
8491
- /** This function gives the strategies of one step of a registry, in order. */
8492
- function listStrategies(registry, step) {
8493
- return registry[step] ?? [];
8494
- }
8495
- /** This function gives the strategy of one step with a name, or `undefined` when the registry has no such strategy. */
8496
- function findStrategy(registry, step, name) {
8497
- return listStrategies(registry, step).find((s) => s.name === name);
8498
- }
8499
- /** This function gives the strategy of one step with a name. When the registry has no such strategy, the function throws an error. */
8500
- function getStrategy(registry, step, name) {
8501
- const s = findStrategy(registry, step, name);
8502
- if (!s) throw new Error(`There is no ${step} "${name}". The registry has ${listStrategies(registry, step).map((t) => t.name).join(", ")}.`);
8503
- return s;
8504
- }
8505
- /** This function gives a `Router` that runs a router strategy with the options. The router keeps the name and the `maxNodes` of the strategy. */
8506
- function asRouter(strategy, options) {
8507
- return {
8508
- name: strategy.name,
8509
- ...strategy.maxNodes === void 0 ? {} : { maxNodes: strategy.maxNodes },
8510
- route: (graph) => strategy.run(graph, options)
8511
- };
8512
- }
8513
- /** This function gives the router strategies of a registry as `Router` objects, in order. The benchmark races them. */
8514
- function routersOf(registry) {
8515
- return listStrategies(registry, "router").map((s) => asRouter(s));
8516
- }
8517
- // src/highways/model.ts
8518
- /** This function gives the point at a place on a polyline. A place outside the polyline gives its nearest end. */
8519
- function pointAt(line, at) {
8520
- if (line.length === 0) throw new Error("The polyline has no point.");
8521
- if (at <= 0) return line[0];
8522
- if (at >= line.length - 1) return line[line.length - 1];
8523
- const i = Math.floor(at);
8524
- const u = at - i;
8525
- const a = line[i];
8526
- if (u === 0) return a;
8527
- const b = line[i + 1];
8528
- return {
8529
- x: a.x + (b.x - a.x) * u,
8530
- y: a.y + (b.y - a.y) * u
8531
- };
8532
- }
8533
- /** This function gives the length of a polyline from the place `from` to the place `to`, with `from` not larger than `to`. */
8534
- function lengthBetween(line, from, to) {
8535
- if (to <= from) return 0;
8536
- let sum = 0;
8537
- let prev = pointAt(line, from);
8538
- for (let i = Math.floor(from) + 1; i < to; i++) {
8539
- const p = line[i];
8540
- sum += dist(prev, p);
8541
- prev = p;
8542
- }
8543
- return sum + dist(prev, pointAt(line, to));
8544
- }
8545
- /** This function gives a member of a highway with its merge point and its split point at the places `on` and `off` of the spine. */
8546
- function memberAt(spine, edge, on, off, seq = 0) {
8547
- return {
8548
- edge,
8549
- on,
8550
- off,
8551
- merge: pointAt(spine, on),
8552
- split: pointAt(spine, off),
8553
- seq
8554
- };
8555
- }
8556
- /**
8557
- * This function lists the problems of a plan against its graph. A good plan gives an empty list.
8558
- * Each highway has a spine of two points or more, and two members or more. Each member is an edge of the graph, with `on` and `off` on the spine and apart.
8559
- * An edge is a member of a highway one time at most.
8769
+ * This function lists the problems of a plan against its graph. A good plan gives an empty list.
8770
+ * Each highway has a spine of two points or more, and two members or more. Each member is an edge of the graph, with `on` and `off` on the spine and apart.
8771
+ * An edge is a member of a highway one time at most.
8560
8772
  */
8561
8773
  function checkPlan(graph, plan) {
8562
8774
  const edges = new Set(graph.edges.map((e) => e.id));
@@ -8624,7 +8836,8 @@ function slice(stops, on, off) {
8624
8836
  * The result has a shared run for each stretch of a spine where the same two edges or more go. Each run holds the points of its routes in a row.
8625
8837
  * The same graph, plan and options give the same result.
8626
8838
  */
8627
- function follow(graph, plan, options = {}) {
8839
+ function follow(input, plan, options = {}) {
8840
+ const graph = resolvePorts(input);
8628
8841
  const byId = nodeIndex(graph);
8629
8842
  const ways = new WayFinder(graph.nodes, options);
8630
8843
  const base = new Map((options.routes?.routes ?? []).map((r) => [r.edge, r.points]));
@@ -11266,9 +11479,9 @@ function snapPlan(input, lines, o) {
11266
11479
  const a = free(line.a);
11267
11480
  const b = free(line.b);
11268
11481
  if (dist(a, b) <= finder.margin) continue;
11269
- const raw = corners.path(a, b);
11482
+ const raw = o.direct === true && finder.obstacles.clear(a, b, pad) ? [a, b] : corners.path(a, b);
11270
11483
  if (!raw) continue;
11271
- const spine = corners.pull(raw);
11484
+ const spine = o.direct === true && raw.length === 2 ? raw : corners.pull(raw);
11272
11485
  if (spine.length < 2 || polylineLength(spine) <= 1e-6) continue;
11273
11486
  const riders = [];
11274
11487
  for (const m of line.members) {
@@ -15922,7 +16135,7 @@ const P_HUB_DEFAULTS = Object.freeze({
15922
16135
  minSize: 2
15923
16136
  });
15924
16137
  /** The largest number of candidate points. A larger set keeps each `k`-th point. */
15925
- const MAX_CANDIDATES = 300;
16138
+ const MAX_CANDIDATES$1 = 300;
15926
16139
  /** This function gives the centres of the cells of each section of the tree. */
15927
16140
  function sectionCentres(root) {
15928
16141
  const out = [];
@@ -15943,8 +16156,8 @@ function hubCandidates(input, candidates) {
15943
16156
  seen.add(key);
15944
16157
  return true;
15945
16158
  });
15946
- if (points.length <= MAX_CANDIDATES) return points;
15947
- const k = Math.ceil(points.length / MAX_CANDIDATES);
16159
+ if (points.length <= MAX_CANDIDATES$1) return points;
16160
+ const k = Math.ceil(points.length / MAX_CANDIDATES$1);
15948
16161
  return points.filter((_, i) => i % k === 0);
15949
16162
  }
15950
16163
  /**
@@ -16228,93 +16441,1271 @@ function scaleSpacePlan(input, o) {
16228
16441
  minSize: Math.max(2, Math.round(o.minSize))
16229
16442
  });
16230
16443
  }
16231
- // src/highways/ridges/index.ts
16232
- /** The default options of the strategy `ridges`. */
16233
- const RIDGE_DEFAULTS = Object.freeze({
16234
- source: "lines",
16235
- cell: 8,
16236
- bandwidth: 16,
16237
- strength: .15,
16238
- minLength: 60,
16239
- snap: 16,
16240
- minSize: 2
16241
- });
16242
- /** The largest angle between an edge and a ridge that it snaps to, in degrees. */
16243
- const MAX_ANGLE = 30;
16444
+ // src/highways/free-lines.ts
16445
+ /** The values of `StreetSource`. */
16446
+ const STREET_SOURCES = Object.freeze(["room", "corners"]);
16447
+ const round = (v) => Math.round(v * 2) / 2;
16448
+ /**
16449
+ * This function gives the free pieces of the line `x = c` (axis `x`) or `y = c` (axis `y`). A box closer than `pad` to the line blocks it.
16450
+ * A piece shorter than `min` is not in the list. The pieces run over the board and `2 pad` past it.
16451
+ */
16452
+ function freePieces(finder, axis, c, pad, min, lo, hi) {
16453
+ const blocks = [];
16454
+ const strip = axis === "x" ? {
16455
+ x: c - pad,
16456
+ y: lo,
16457
+ w: 2 * pad,
16458
+ h: hi - lo
16459
+ } : {
16460
+ x: lo,
16461
+ y: c - pad,
16462
+ w: hi - lo,
16463
+ h: 2 * pad
16464
+ };
16465
+ finder.obstacles.query(strip, (n) => {
16466
+ const a = axis === "x" ? n.box.x : n.box.y;
16467
+ const w = axis === "x" ? n.box.w : n.box.h;
16468
+ if (c <= a - pad || c >= a + w + pad) return;
16469
+ const b = axis === "x" ? n.box.y : n.box.x;
16470
+ const h = axis === "x" ? n.box.h : n.box.w;
16471
+ blocks.push([b - pad, b + h + pad]);
16472
+ });
16473
+ blocks.sort((p, q) => p[0] - q[0] || p[1] - q[1]);
16474
+ const out = [];
16475
+ const at = (u) => axis === "x" ? {
16476
+ x: c,
16477
+ y: u
16478
+ } : {
16479
+ x: u,
16480
+ y: c
16481
+ };
16482
+ let from = lo;
16483
+ for (const [s, e] of blocks) {
16484
+ if (s - from >= min) out.push({
16485
+ a: at(from),
16486
+ b: at(s)
16487
+ });
16488
+ from = Math.max(from, e);
16489
+ }
16490
+ if (hi - from >= min) out.push({
16491
+ a: at(from),
16492
+ b: at(hi)
16493
+ });
16494
+ return out;
16495
+ }
16244
16496
  /**
16245
- * This function gives the density of the polylines on a grid of cells, after a Gaussian blur of the width `bandwidth`.
16246
- * Each polyline adds 1 to each cell that it goes through, one time for each polyline.
16497
+ * This function gives the candidate streets of a board. Each street is an axis-parallel piece of free space.
16498
+ * The source `room` takes the middle line of each gap of `boardGaps` that is two margins wide or more.
16499
+ * The source `corners` takes the lines one margin out from each side of each box.
16500
+ * A street is two margins long or more. The list has no copies, and its order is the same each time.
16247
16501
  */
16248
- function lineDensity(lines, box, cellSize, bandwidth) {
16249
- const cell = Math.max(cellSize, box.w / 1024, box.h / 1024, 1e-6);
16250
- const pad = 3 * bandwidth;
16251
- const x0 = box.x - pad;
16252
- const y0 = box.y - pad;
16253
- const w = Math.max(1, Math.ceil((box.w + 2 * pad) / cell));
16254
- const h = Math.max(1, Math.ceil((box.h + 2 * pad) / cell));
16255
- const raw = new Float64Array(w * h);
16256
- const stamp = new Int32Array(w * h).fill(-1);
16257
- lines.forEach((p, li) => {
16258
- for (let i = 1; i < p.length; i++) {
16259
- const a = p[i - 1];
16260
- const b = p[i];
16261
- const steps = Math.max(1, Math.ceil(2 * dist(a, b) / cell));
16262
- for (let k = 0; k <= steps; k++) {
16263
- const x = Math.floor((a.x + (b.x - a.x) * k / steps - x0) / cell);
16264
- const y = Math.floor((a.y + (b.y - a.y) * k / steps - y0) / cell);
16265
- if (x < 0 || y < 0 || x >= w || y >= h) continue;
16266
- const c = y * w + x;
16267
- if (stamp[c] === li) continue;
16268
- stamp[c] = li;
16269
- raw[c] = raw[c] + 1;
16270
- }
16271
- }
16272
- });
16273
- const sigma = Math.max(1e-6, bandwidth / cell);
16274
- const r = Math.ceil(3 * sigma);
16275
- const kernel = Array.from({ length: 2 * r + 1 }, (_, i) => Math.exp(-((i - r) ** 2) / (2 * sigma * sigma)));
16276
- const sum = kernel.reduce((s, v) => s + v, 0);
16277
- const tmp = new Float64Array(w * h);
16278
- for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
16279
- let v = 0;
16280
- for (let i = -r; i <= r; i++) {
16281
- const xi = x + i;
16282
- if (xi >= 0 && xi < w) v += raw[y * w + xi] * kernel[i + r];
16283
- }
16284
- tmp[y * w + x] = v / sum;
16502
+ function freeLines(graph, finder, source) {
16503
+ const nodes = graph.nodes;
16504
+ if (nodes.length === 0) return [];
16505
+ const m = finder.margin;
16506
+ const pad = m / 2;
16507
+ let x0 = Infinity;
16508
+ let y0 = Infinity;
16509
+ let x1 = -Infinity;
16510
+ let y1 = -Infinity;
16511
+ for (const n of nodes) {
16512
+ x0 = Math.min(x0, n.box.x);
16513
+ y0 = Math.min(y0, n.box.y);
16514
+ x1 = Math.max(x1, n.box.x + n.box.w);
16515
+ y1 = Math.max(y1, n.box.y + n.box.h);
16285
16516
  }
16286
- const rho = new Float64Array(w * h);
16287
- for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
16288
- let v = 0;
16289
- for (let i = -r; i <= r; i++) {
16290
- const yi = y + i;
16291
- if (yi >= 0 && yi < h) v += tmp[yi * w + x] * kernel[i + r];
16292
- }
16293
- rho[y * w + x] = v / sum;
16517
+ const lines = [];
16518
+ if (source === "room") {
16519
+ for (const g of boardGaps(nodes)) if (g.width >= 2 * m) lines.push({
16520
+ axis: g.axis,
16521
+ c: round(g.at - g.width / 2)
16522
+ });
16523
+ } else for (const n of nodes) {
16524
+ lines.push({
16525
+ axis: "x",
16526
+ c: round(n.box.x - m)
16527
+ }, {
16528
+ axis: "x",
16529
+ c: round(n.box.x + n.box.w + m)
16530
+ });
16531
+ lines.push({
16532
+ axis: "y",
16533
+ c: round(n.box.y - m)
16534
+ }, {
16535
+ axis: "y",
16536
+ c: round(n.box.y + n.box.h + m)
16537
+ });
16538
+ }
16539
+ const seen = /* @__PURE__ */ new Set();
16540
+ const out = [];
16541
+ const sorted = [...lines].sort((p, q) => p.axis === q.axis ? p.c - q.c : p.axis < q.axis ? -1 : 1);
16542
+ for (const l of sorted) {
16543
+ const k = `${l.axis}${l.c}`;
16544
+ if (seen.has(k)) continue;
16545
+ seen.add(k);
16546
+ const [lo, hi] = l.axis === "x" ? [y0 - 2 * m, y1 + 2 * m] : [x0 - 2 * m, x1 + 2 * m];
16547
+ out.push(...freePieces(finder, l.axis, l.c, pad, 2 * m, lo, hi));
16548
+ }
16549
+ return out;
16550
+ }
16551
+ /** This function gives the edges of the input for the streets, with the points out of their plugs and the lengths of their first routes. */
16552
+ function streetEdges(input, finder) {
16553
+ const first = firstRoutes(input);
16554
+ const out = [];
16555
+ for (const p of edgePlugs(input.graph)) {
16556
+ const route = first.get(p.edge.id);
16557
+ const s = finder.out(p.ps);
16558
+ const t = finder.out(p.pt);
16559
+ out.push({
16560
+ edge: p.edge.id,
16561
+ s,
16562
+ t,
16563
+ base: route ? polylineLength(route) : dist(s, t)
16564
+ });
16565
+ }
16566
+ return out;
16567
+ }
16568
+ /** This function gives the point of the street nearest to `p`. */
16569
+ function streetPoint(line, p) {
16570
+ if (line.a.x === line.b.x) {
16571
+ const lo = Math.min(line.a.y, line.b.y);
16572
+ const hi = Math.max(line.a.y, line.b.y);
16573
+ return {
16574
+ x: line.a.x,
16575
+ y: Math.min(hi, Math.max(lo, p.y))
16576
+ };
16294
16577
  }
16578
+ const lo = Math.min(line.a.x, line.b.x);
16579
+ const hi = Math.max(line.a.x, line.b.x);
16295
16580
  return {
16296
- w,
16297
- h,
16298
- cell,
16299
- x0,
16300
- y0,
16301
- rho
16581
+ x: Math.min(hi, Math.max(lo, p.x)),
16582
+ y: line.a.y
16583
+ };
16584
+ }
16585
+ /** This function gives the ride of an edge on a street. The edge joins the street at the point nearest to `s`, and leaves it at the point nearest to `t`. */
16586
+ function streetRide(line, e) {
16587
+ const on = streetPoint(line, e.s);
16588
+ const off = streetPoint(line, e.t);
16589
+ return {
16590
+ on,
16591
+ off,
16592
+ ride: dist(on, off),
16593
+ ramps: dist(e.s, on) + dist(off, e.t)
16302
16594
  };
16303
16595
  }
16596
+ // src/highways/gaps/index.ts
16597
+ /** The default options of the strategy `gaps`. */
16598
+ const GAP_DEFAULTS = Object.freeze({
16599
+ minSize: 3,
16600
+ discount: .5,
16601
+ detour: 1.3,
16602
+ source: "room"
16603
+ });
16604
+ /** The best street of an edge, from the streets in `open`. The ride costs `discount` times its length. The way is at most `detour` times the first route. */
16605
+ function bestStreet(lines, open, e, o, min) {
16606
+ let best = -1;
16607
+ let bestSaving = 1e-9;
16608
+ for (const i of open) {
16609
+ const r = streetRide(lines[i], e);
16610
+ if (r.ride < min) continue;
16611
+ if (r.ramps + r.ride > o.detour * e.base) continue;
16612
+ const saving = e.base - (r.ramps + o.discount * r.ride);
16613
+ if (saving > bestSaving || saving === bestSaving && i < best) [best, bestSaving] = [i, saving];
16614
+ }
16615
+ return best;
16616
+ }
16304
16617
  /**
16305
- * This function gives the ridges of a density image (Lindeberg 1998). A ridge cell has a negative eigenvalue of the Hessian with the larger size.
16306
- * Its density is a maximum along the eigenvector of that eigenvalue. Its strength is the density times the size of that eigenvalue.
16307
- * The cells with `strength` times the largest strength or more join their neighbours with a direction within 30 degrees.
16308
- * Each group of cells gives the segment of its main axis. A segment of `minLength` or more is a ridge. The ridges come in the order of their total strength.
16618
+ * This function gives the highways on the gaps of the board (HWY-057). The streets are the free middle lines of the gaps.
16619
+ * Each edge takes the street that saves it the most cost, when a length on a street costs `discount` times its length.
16620
+ * A street with `minSize` edges or more becomes a highway. The edges of the other streets then choose again among the highways, two times.
16309
16621
  */
16310
- function densityRidges(img, strength, minLength) {
16311
- const { w, h, rho } = img;
16312
- const at = (x, y) => rho[Math.min(h - 1, Math.max(0, y)) * w + Math.min(w - 1, Math.max(0, x))];
16313
- const power = new Float64Array(w * h);
16314
- const across = new Float64Array(w * h * 2);
16315
- let top = 0;
16316
- for (let y = 1; y < h - 1; y++) for (let x = 1; x < w - 1; x++) {
16317
- const v = at(x, y);
16622
+ function gapPlan(input, o) {
16623
+ const finder = new WayFinder(input.graph.nodes);
16624
+ const lines = freeLines(input.graph, finder, o.source);
16625
+ const edges = streetEdges(input, finder);
16626
+ const min = 2 * finder.margin;
16627
+ const size = Math.max(2, Math.round(o.minSize));
16628
+ let open = new Set(lines.map((_, i) => i));
16629
+ let choice = /* @__PURE__ */ new Map();
16630
+ for (let round = 0; round < 3; round++) {
16631
+ choice = /* @__PURE__ */ new Map();
16632
+ const count = /* @__PURE__ */ new Map();
16633
+ for (const e of edges) {
16634
+ const i = bestStreet(lines, open, e, o, min);
16635
+ if (i < 0) continue;
16636
+ choice.set(e.edge, i);
16637
+ count.set(i, (count.get(i) ?? 0) + 1);
16638
+ }
16639
+ const kept = new Set([...open].filter((i) => (count.get(i) ?? 0) >= size));
16640
+ if (kept.size === open.size) break;
16641
+ open = kept;
16642
+ }
16643
+ const byLine = /* @__PURE__ */ new Map();
16644
+ for (const e of edges) {
16645
+ const i = choice.get(e.edge);
16646
+ if (i === void 0 || !open.has(i)) continue;
16647
+ const r = streetRide(lines[i], e);
16648
+ const list = byLine.get(i) ?? [];
16649
+ list.push({
16650
+ edge: e.edge,
16651
+ from: r.on,
16652
+ to: r.off,
16653
+ at: 0
16654
+ });
16655
+ byLine.set(i, list);
16656
+ }
16657
+ return snapPlan(input, [...byLine.keys()].sort((p, q) => p - q).map((i) => {
16658
+ const members = byLine.get(i);
16659
+ const line = lines[i];
16660
+ const vertical = line.a.x === line.b.x;
16661
+ const us = members.flatMap((m) => vertical ? [m.from.y, m.to.y] : [m.from.x, m.to.x]);
16662
+ const lo = Math.min(...us);
16663
+ const hi = Math.max(...us);
16664
+ const a = vertical ? {
16665
+ x: line.a.x,
16666
+ y: lo
16667
+ } : {
16668
+ x: lo,
16669
+ y: line.a.y
16670
+ };
16671
+ const b = vertical ? {
16672
+ x: line.a.x,
16673
+ y: hi
16674
+ } : {
16675
+ x: hi,
16676
+ y: line.a.y
16677
+ };
16678
+ return {
16679
+ key: `gap${i}`,
16680
+ a,
16681
+ b,
16682
+ members
16683
+ };
16684
+ }), {
16685
+ minSize: size,
16686
+ direct: true
16687
+ });
16688
+ }
16689
+ // src/highways/interchanges/index.ts
16690
+ /** The values of `InterchangeBase`. */
16691
+ const INTERCHANGE_BASES = Object.freeze(["corridors", "shared-ways"]);
16692
+ /** The default options of the strategy `interchanges`. */
16693
+ const INTERCHANGE_DEFAULTS = Object.freeze({
16694
+ from: "corridors",
16695
+ changeCost: 2,
16696
+ interchanges: 16,
16697
+ perEdge: 2,
16698
+ radius: 2,
16699
+ discount: .5,
16700
+ detour: 1.5
16701
+ });
16702
+ /** The largest number of candidate interchanges of the first pass. */
16703
+ const MAX_CANDIDATES = 64;
16704
+ /** The nearest points of the segments `a b` and `c d`, as the parameters `s` and `t` from 0 to 1, with their distance. */
16705
+ function nearestOfSegments(a, b, c, d) {
16706
+ const ux = b.x - a.x;
16707
+ const uy = b.y - a.y;
16708
+ const vx = d.x - c.x;
16709
+ const vy = d.y - c.y;
16710
+ const at = (s, t) => {
16711
+ const dx = a.x + s * ux - (c.x + t * vx);
16712
+ const dy = a.y + s * uy - (c.y + t * vy);
16713
+ return Math.sqrt(dx * dx + dy * dy);
16714
+ };
16715
+ const clamp = (v) => Math.min(1, Math.max(0, v));
16716
+ const uu = ux * ux + uy * uy;
16717
+ const vv = vx * vx + vy * vy;
16718
+ const project = (p, q, x, y, len) => len > 0 ? clamp(((p.x - q.x) * x + (p.y - q.y) * y) / len) : 0;
16719
+ const tries = [
16720
+ [0, project(a, c, vx, vy, vv)],
16721
+ [1, project(b, c, vx, vy, vv)],
16722
+ [project(c, a, ux, uy, uu), 0],
16723
+ [project(d, a, ux, uy, uu), 1]
16724
+ ];
16725
+ const den = ux * vy - uy * vx;
16726
+ if (Math.abs(den) > 1e-12) {
16727
+ const s = ((c.x - a.x) * vy - (c.y - a.y) * vx) / den;
16728
+ const t = ((c.x - a.x) * uy - (c.y - a.y) * ux) / den;
16729
+ if (s >= 0 && s <= 1 && t >= 0 && t <= 1) tries.push([s, t]);
16730
+ }
16731
+ let best = {
16732
+ s: 0,
16733
+ t: 0,
16734
+ d: Infinity
16735
+ };
16736
+ for (const [s, t] of tries) {
16737
+ const v = at(s, t);
16738
+ if (v < best.d - 1e-12) best = {
16739
+ s,
16740
+ t,
16741
+ d: v
16742
+ };
16743
+ }
16744
+ return best;
16745
+ }
16746
+ /** This function gives the candidate interchanges: the nearest places of each pair of spines that come within `radius`, with a link in clear view. */
16747
+ function interchangeCandidates(spines, radius, clear) {
16748
+ const out = [];
16749
+ for (let h = 0; h < spines.length; h++) for (let g = h + 1; g < spines.length; g++) {
16750
+ const p = spines[h];
16751
+ const q = spines[g];
16752
+ let best;
16753
+ for (let i = 0; i + 1 < p.length; i++) for (let j = 0; j + 1 < q.length; j++) {
16754
+ const n = nearestOfSegments(p[i], p[i + 1], q[j], q[j + 1]);
16755
+ if (n.d <= radius && (!best || n.d < best.gap - 1e-12)) best = {
16756
+ h,
16757
+ hAt: i + n.s,
16758
+ g,
16759
+ gAt: j + n.t,
16760
+ gap: n.d
16761
+ };
16762
+ }
16763
+ if (best && (best.gap <= 1e-9 || clear(pointAt(p, best.hAt), pointAt(q, best.gAt)))) out.push(best);
16764
+ }
16765
+ return out.sort((x, y) => x.gap - y.gap || x.h - y.h || x.g - y.g).slice(0, MAX_CANDIDATES);
16766
+ }
16767
+ /**
16768
+ * This function gives the highways of a network with interchanges (HWY-060). The highways come from a plan. Two highways that come near get an interchange.
16769
+ * Each edge takes its cheapest way: a ride on one highway, or on up to `perEdge` highways with a change at an interchange.
16770
+ * A change costs `changeCost` units. The interchanges that the most edges use stay, up to `interchanges`.
16771
+ */
16772
+ function interchangePlan(input, o) {
16773
+ const graph = input.graph;
16774
+ const base = basePlan(input, o.from);
16775
+ const spines = base.highways.map((h) => h.spine);
16776
+ if (spines.length === 0) return { highways: [] };
16777
+ const finder = new WayFinder(graph.nodes);
16778
+ const pad = finder.margin / 2;
16779
+ const unit = boardUnit(graph);
16780
+ const first = firstRoutes(input);
16781
+ const change = Math.max(0, o.changeCost) * unit;
16782
+ const perEdge = Math.min(3, Math.max(1, Math.round(o.perEdge)));
16783
+ const disc = Math.min(1, Math.max(.05, o.discount));
16784
+ const ride = (h, a, b) => lengthBetween(spines[h], Math.min(a, b), Math.max(a, b));
16785
+ const edges = edgePlugs(graph).map((p) => {
16786
+ const s = finder.out(p.ps);
16787
+ const t = finder.out(p.pt);
16788
+ const route = first.get(p.edge.id);
16789
+ const ends = spines.map((spine) => {
16790
+ const on = nearestPlace(spine, s);
16791
+ const off = nearestPlace(spine, t);
16792
+ return {
16793
+ on,
16794
+ off,
16795
+ rampIn: dist(s, pointAt(spine, on)),
16796
+ rampOut: dist(pointAt(spine, off), t)
16797
+ };
16798
+ });
16799
+ return {
16800
+ edge: p.edge.id,
16801
+ s,
16802
+ t,
16803
+ base: route ? polylineLength(route) : dist(s, t),
16804
+ ends
16805
+ };
16806
+ });
16807
+ const tripOf = (e, list) => {
16808
+ const found = {
16809
+ cost: Infinity,
16810
+ trip: void 0
16811
+ };
16812
+ const offer = (cost, length, trip) => {
16813
+ if (trip.legs.some((l) => ride(l.h, l.on, l.off) < finder.margin)) return;
16814
+ if (cost >= e.base || length > o.detour * e.base) return;
16815
+ if (cost < found.cost - 1e-9) [found.cost, found.trip] = [cost, trip];
16816
+ };
16817
+ spines.forEach((_, h) => {
16818
+ const x = e.ends[h];
16819
+ const r = ride(h, x.on, x.off);
16820
+ offer(x.rampIn + disc * r + x.rampOut, x.rampIn + r + x.rampOut, {
16821
+ legs: [{
16822
+ h,
16823
+ on: x.on,
16824
+ off: x.off
16825
+ }],
16826
+ used: []
16827
+ });
16828
+ });
16829
+ if (perEdge < 2) return found.trip;
16830
+ const sides = list.flatMap((c, i) => [{
16831
+ i,
16832
+ p: c.h,
16833
+ pAt: c.hAt,
16834
+ q: c.g,
16835
+ qAt: c.gAt,
16836
+ gap: c.gap
16837
+ }, {
16838
+ i,
16839
+ p: c.g,
16840
+ pAt: c.gAt,
16841
+ q: c.h,
16842
+ qAt: c.hAt,
16843
+ gap: c.gap
16844
+ }]);
16845
+ for (const a of sides) {
16846
+ const x = e.ends[a.p];
16847
+ const y = e.ends[a.q];
16848
+ const r1 = ride(a.p, x.on, a.pAt);
16849
+ const r2 = ride(a.q, a.qAt, y.off);
16850
+ offer(x.rampIn + disc * (r1 + r2) + a.gap + change + y.rampOut, x.rampIn + r1 + r2 + a.gap + y.rampOut, {
16851
+ legs: [{
16852
+ h: a.p,
16853
+ on: x.on,
16854
+ off: a.pAt
16855
+ }, {
16856
+ h: a.q,
16857
+ on: a.qAt,
16858
+ off: y.off
16859
+ }],
16860
+ used: [a.i]
16861
+ });
16862
+ if (perEdge < 3) continue;
16863
+ for (const b of sides) {
16864
+ if (b.p !== a.q || b.q === a.p || b.i === a.i) continue;
16865
+ const z = e.ends[b.q];
16866
+ const r3 = ride(a.q, a.qAt, b.pAt);
16867
+ const r4 = ride(b.q, b.qAt, z.off);
16868
+ offer(x.rampIn + disc * (r1 + r3 + r4) + a.gap + b.gap + 2 * change + z.rampOut, x.rampIn + r1 + r3 + r4 + a.gap + b.gap + z.rampOut, {
16869
+ legs: [
16870
+ {
16871
+ h: a.p,
16872
+ on: x.on,
16873
+ off: a.pAt
16874
+ },
16875
+ {
16876
+ h: a.q,
16877
+ on: a.qAt,
16878
+ off: b.pAt
16879
+ },
16880
+ {
16881
+ h: b.q,
16882
+ on: b.qAt,
16883
+ off: z.off
16884
+ }
16885
+ ],
16886
+ used: [a.i, b.i]
16887
+ });
16888
+ }
16889
+ }
16890
+ return found.trip;
16891
+ };
16892
+ const clear = (a, b) => finder.obstacles.clear(a, b, pad);
16893
+ const candidates = interchangeCandidates(spines, Math.max(0, o.radius) * unit, clear);
16894
+ const use = new Array(candidates.length).fill(0);
16895
+ for (const e of edges) for (const i of tripOf(e, candidates)?.used ?? []) use[i] = use[i] + 1;
16896
+ const kept = candidates.map((c, i) => ({
16897
+ c,
16898
+ i,
16899
+ n: use[i]
16900
+ })).filter((x) => x.n > 0).sort((x, y) => y.n - x.n || x.i - y.i).slice(0, Math.max(1, Math.round(o.interchanges))).sort((x, y) => x.i - y.i).map((x) => x.c);
16901
+ const riders = spines.map(() => []);
16902
+ for (const e of edges) {
16903
+ const trip = tripOf(e, kept);
16904
+ if (!trip) continue;
16905
+ trip.legs.forEach((l, seq) => riders[l.h].push({
16906
+ edge: e.edge,
16907
+ on: l.on,
16908
+ off: l.off,
16909
+ seq,
16910
+ points: [
16911
+ e.s,
16912
+ ...spinePart(spines[l.h], l.on, l.off),
16913
+ e.t
16914
+ ]
16915
+ }));
16916
+ }
16917
+ const highways = [];
16918
+ spines.forEach((spine, h) => {
16919
+ const list = riders[h];
16920
+ if (list.length < 2) return;
16921
+ const lanes = orderLanes(spine, list.map((r) => ({
16922
+ edge: r.edge,
16923
+ points: r.points
16924
+ })), "bundle");
16925
+ const byEdge = new Map(list.map((r) => [r.edge, r]));
16926
+ const members = lanes.map((edge) => {
16927
+ const r = byEdge.get(edge);
16928
+ return memberAt(spine, edge, r.on, r.off, r.seq);
16929
+ });
16930
+ highways.push({
16931
+ id: `h${h}`,
16932
+ key: base.highways[h].key,
16933
+ spine,
16934
+ lanes,
16935
+ members
16936
+ });
16937
+ });
16938
+ return tidyPlan({ highways });
16939
+ }
16940
+ // src/bench/measures/bundles.ts
16941
+ /**
16942
+ * This function gives the share of the ink that shared segments save (B1 of MEAS0): `inkSaved = 1 - ink(S) / Σ_e L(r_e)`.
16943
+ * `ink(S)` counts a shared segment one time, as `measureQuality` does. The scale is 0 to 1, and higher is better. The cost is `n log n`.
16944
+ */
16945
+ function inkSaved(routes) {
16946
+ let total = 0;
16947
+ for (const r of routes) total += polylineLength(r.points);
16948
+ if (total <= 0) return 0;
16949
+ let ink = 0;
16950
+ for (const [a, b] of drawnSegments(routes)) ink += dist(a, b);
16951
+ return Math.max(0, 1 - ink / total);
16952
+ }
16953
+ /**
16954
+ * This function measures the ambiguity of the routes (B4 of MEAS0), with one step of neighbours.
16955
+ * `N(v)` holds the nodes whose routes share a drawn segment with a route of `v`.
16956
+ * `amb = Σ_v count(w in N(v) : no edge v-w) / Σ_v count(N(v))`. The cost is quadratic, so a seeded sample serves above `exact` nodes.
16957
+ */
16958
+ function ambiguity(graph, routes, options = {}) {
16959
+ const n = graph.nodes.length;
16960
+ const index = new Map(graph.nodes.map((v, i) => [v.id, i]));
16961
+ const ends = /* @__PURE__ */ new Map();
16962
+ for (const e of graph.edges) {
16963
+ const s = index.get(e.source.node);
16964
+ const t = index.get(e.target.node);
16965
+ if (s !== void 0 && t !== void 0) ends.set(e.id, [s, t]);
16966
+ }
16967
+ const keyIds = /* @__PURE__ */ new Map();
16968
+ const nodesOf = [];
16969
+ const keysOf = /* @__PURE__ */ new Map();
16970
+ for (const r of routes) {
16971
+ const end = ends.get(r.edge);
16972
+ if (!end) continue;
16973
+ const own = [];
16974
+ for (let i = 1; i < r.points.length; i++) {
16975
+ const k = segmentKey(r.points[i - 1], r.points[i]);
16976
+ if (k === void 0) continue;
16977
+ let id = keyIds.get(k);
16978
+ if (id === void 0) {
16979
+ id = nodesOf.length;
16980
+ keyIds.set(k, id);
16981
+ nodesOf.push([]);
16982
+ }
16983
+ if (own.includes(id)) continue;
16984
+ own.push(id);
16985
+ nodesOf[id].push(end[0], end[1]);
16986
+ }
16987
+ keysOf.set(r.edge, own);
16988
+ }
16989
+ const mark = new Int32Array(n).fill(-1);
16990
+ nodesOf.forEach((list, k) => {
16991
+ let j = 0;
16992
+ for (const v of list) if (mark[v] !== k) {
16993
+ mark[v] = k;
16994
+ list[j++] = v;
16995
+ }
16996
+ list.length = j;
16997
+ });
16998
+ const incident = Array.from({ length: n }, () => []);
16999
+ const adjacent = Array.from({ length: n }, () => []);
17000
+ for (const e of graph.edges) {
17001
+ const end = ends.get(e.id);
17002
+ if (!end) continue;
17003
+ incident[end[0]].push(e.id);
17004
+ incident[end[1]].push(e.id);
17005
+ adjacent[end[0]].push(end[1]);
17006
+ adjacent[end[1]].push(end[0]);
17007
+ }
17008
+ const exact = n <= (options.exact ?? 1e3);
17009
+ const R = rng(options.seed ?? 1);
17010
+ const pick = exact ? Array.from({ length: n }, (_, i) => i) : Array.from({ length: options.sample ?? 2e3 }, () => Math.floor(R() * n));
17011
+ const keyStamp = new Int32Array(nodesOf.length).fill(-1);
17012
+ const nearStamp = new Int32Array(n).fill(-1);
17013
+ const linkStamp = new Int32Array(n).fill(-1);
17014
+ let all = 0;
17015
+ let wrong = 0;
17016
+ pick.forEach((v, step) => {
17017
+ for (const w of adjacent[v]) linkStamp[w] = step;
17018
+ nearStamp[v] = step;
17019
+ for (const e of incident[v]) for (const k of keysOf.get(e) ?? []) {
17020
+ if (keyStamp[k] === step) continue;
17021
+ keyStamp[k] = step;
17022
+ for (const w of nodesOf[k]) {
17023
+ if (nearStamp[w] === step) continue;
17024
+ nearStamp[w] = step;
17025
+ all++;
17026
+ if (linkStamp[w] !== step) wrong++;
17027
+ }
17028
+ }
17029
+ });
17030
+ return {
17031
+ value: all ? wrong / all : 0,
17032
+ estimated: !exact
17033
+ };
17034
+ }
17035
+ /** The drawn segments of each route, as keys, in the order of its points. A segment shorter than the grain of `segmentKey` has no key. */
17036
+ function routeKeys(routes) {
17037
+ const keys = /* @__PURE__ */ new Map();
17038
+ const users = /* @__PURE__ */ new Map();
17039
+ for (const r of routes) {
17040
+ const own = [];
17041
+ for (let i = 1; i < r.points.length; i++) {
17042
+ const k = segmentKey(r.points[i - 1], r.points[i]);
17043
+ if (k === void 0) continue;
17044
+ own.push(k);
17045
+ let set = users.get(k);
17046
+ if (!set) users.set(k, set = /* @__PURE__ */ new Set());
17047
+ set.add(r.edge);
17048
+ }
17049
+ keys.set(r.edge, own);
17050
+ }
17051
+ return {
17052
+ keys,
17053
+ users
17054
+ };
17055
+ }
17056
+ /**
17057
+ * This function gives the bundled share of a result (B2 of MEAS0): `count(e : e is in a run or a bundle) / m`.
17058
+ * An edge is also in a bundle when its route shares a drawn segment with the route of another edge. Thus a router without `runs` and `bundles` gets a value too.
17059
+ * The scale is 0 to 1. A higher value is better only in context. The cost is linear.
17060
+ */
17061
+ function bundledShare(result) {
17062
+ const routes = result.routes;
17063
+ if (routes.length === 0) return 0;
17064
+ const shared = /* @__PURE__ */ new Set();
17065
+ for (const run of result.runs ?? []) for (const e of run.edges) shared.add(e);
17066
+ for (const b of result.bundles ?? []) if (b.edges.length > 1) for (const e of b.edges) shared.add(e);
17067
+ const { keys, users } = routeKeys(routes);
17068
+ for (const [edge, own] of keys) if (own.some((k) => (users.get(k)?.size ?? 0) > 1)) shared.add(edge);
17069
+ let count = 0;
17070
+ for (const r of routes) if (shared.has(r.edge)) count++;
17071
+ return count / routes.length;
17072
+ }
17073
+ /**
17074
+ * This function gives the bundle stretch (B3 of MEAS0): `st(e) = L(r_e) / L(r*_e)` for each edge, with `r*_e` the base route of the edge.
17075
+ * The base routes are the routes of `corners` on the same input. An edge without a base route, or with a base route of 1 unit or less, does not count.
17076
+ * The cost is an extra run of the base router, and then linear.
17077
+ */
17078
+ function bundleStretch(routes, base) {
17079
+ const baseLength = /* @__PURE__ */ new Map();
17080
+ for (const r of base) baseLength.set(r.edge, polylineLength(r.points));
17081
+ const st = [];
17082
+ for (const r of routes) {
17083
+ const b = baseLength.get(r.edge);
17084
+ if (b === void 0 || b <= 1 || r.points.length < 2) continue;
17085
+ st.push(polylineLength(r.points) / b);
17086
+ }
17087
+ if (st.length === 0) return {
17088
+ median: 1,
17089
+ q95: 1,
17090
+ edges: 0
17091
+ };
17092
+ return {
17093
+ median: quantile(st, .5),
17094
+ q95: quantile(st, .95),
17095
+ edges: st.length
17096
+ };
17097
+ }
17098
+ /**
17099
+ * This function gives the merge angle of the routes (B6 of MEAS0): the largest turn of a lane where it joins or leaves a run, in degrees.
17100
+ * A run is a drawn segment that the routes of two edges or more share. A route joins or leaves a run at a vertex where the edges on its segment change.
17101
+ * One of the two segments at that vertex has two edges or more. The turn is the angle between the two segments.
17102
+ * Routes without a shared segment give 0. The scale is 0 to 180 degrees, and lower is better. The cost is linear.
17103
+ */
17104
+ function mergeAngle(routes) {
17105
+ const { users } = routeKeys(routes);
17106
+ const sig = /* @__PURE__ */ new Map();
17107
+ for (const [k, set] of users) sig.set(k, [...set].sort().join("\0"));
17108
+ let max = 0;
17109
+ for (const r of routes) {
17110
+ let prev;
17111
+ for (let i = 1; i < r.points.length; i++) {
17112
+ const a = r.points[i - 1];
17113
+ const b = r.points[i];
17114
+ const k = segmentKey(a, b);
17115
+ if (k === void 0) continue;
17116
+ if (prev && sig.get(prev.k) !== sig.get(k) && ((users.get(prev.k)?.size ?? 0) > 1 || (users.get(k)?.size ?? 0) > 1)) {
17117
+ const ux = prev.b.x - prev.a.x;
17118
+ const uy = prev.b.y - prev.a.y;
17119
+ const vx = b.x - a.x;
17120
+ const vy = b.y - a.y;
17121
+ const turn = Math.abs(Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy));
17122
+ max = Math.max(max, turn * 180 / Math.PI);
17123
+ }
17124
+ prev = {
17125
+ k,
17126
+ a,
17127
+ b
17128
+ };
17129
+ }
17130
+ }
17131
+ return max;
17132
+ }
17133
+ // src/highways/search/index.ts
17134
+ /** The default options of the strategy `search`. */
17135
+ const SEARCH_DEFAULTS = Object.freeze({
17136
+ from: "corridors",
17137
+ pool: Object.freeze(["quickbundles", "road-classes"]),
17138
+ evaluations: 16,
17139
+ temperature: .02,
17140
+ inkWeight: 1,
17141
+ stretchWeight: .5,
17142
+ ambiguityWeight: .5,
17143
+ angleWeight: .25,
17144
+ seed: 1
17145
+ });
17146
+ /** The values of `SearchMove`, in the order of the draw. */
17147
+ const SEARCH_MOVES = Object.freeze([
17148
+ "add",
17149
+ "drop",
17150
+ "edge",
17151
+ "point"
17152
+ ]);
17153
+ /** The number of moves of one sweep of the annealing. */
17154
+ const SWEEP = 4;
17155
+ /** This function gives a seeded random source with values from 0 to 1 (mulberry32). */
17156
+ function random(seed) {
17157
+ let a = Math.floor(seed) >>> 0;
17158
+ return () => {
17159
+ a = a + 1831565813 >>> 0;
17160
+ let t = a;
17161
+ t = Math.imul(t ^ t >>> 15, t | 1);
17162
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
17163
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
17164
+ };
17165
+ }
17166
+ /**
17167
+ * This function gives the score of the routes of a plan: `w1 B1 - w3 B3 - w4 B4 - w6 B6 / 180`. Higher is better.
17168
+ * B3 is the 0.95 quantile of the stretch against the first routes. The score reads only the graph and the routes of the plan.
17169
+ */
17170
+ function searchScore(graph, routes, first, o) {
17171
+ const ink = inkSaved(routes);
17172
+ const stretch = bundleStretch(routes, first).q95;
17173
+ const amb = o.ambiguityWeight > 0 ? ambiguity(graph, routes).value : 0;
17174
+ const angle = o.angleWeight > 0 ? mergeAngle(routes) / 180 : 0;
17175
+ return o.inkWeight * ink - o.stretchWeight * stretch - o.ambiguityWeight * amb - o.angleWeight * angle;
17176
+ }
17177
+ /**
17178
+ * The function `acceptance` gives the chance to keep a worse plan, for a loss `y` of 0 or more on the scale of the temperature.
17179
+ * It is `1 / (1 + y + y² / 2)`, near `exp(-y)` for a small loss.
17180
+ * It uses only exact arithmetic, so each platform keeps the same plans.
17181
+ */
17182
+ function acceptance(y) {
17183
+ return 1 / (1 + y + y * y / 2);
17184
+ }
17185
+ /**
17186
+ * This function gives the highways of a local search over the set of highways (HWY-059). The search starts at the plan of `from`.
17187
+ * Each move adds a highway of the pool, drops a highway, moves one edge to another highway, or moves one point of a spine.
17188
+ * The annealing keeps a worse plan with the chance `acceptance(-delta / T)`. The best plan that the search measures is the result.
17189
+ *
17190
+ * The score reads only this graph and its routes. Thus the search does not fit to the inputs of a screen.
17191
+ */
17192
+ function searchPlan(input, o) {
17193
+ const graph = input.graph;
17194
+ const start = tidyPlan(candidatePlan(input, o.from));
17195
+ const budget = Math.max(0, Math.floor(o.evaluations));
17196
+ if (budget === 0 || graph.edges.length === 0) return start;
17197
+ const pool = [];
17198
+ for (const source of o.pool) if (source !== o.from) pool.push(...candidatePlan(input, source).highways);
17199
+ const finder = new WayFinder(graph.nodes);
17200
+ const pad = finder.margin / 2;
17201
+ const plugs = new Map(edgePlugs(graph).map((p) => [p.edge.id, p]));
17202
+ const routes = firstRoutes(input);
17203
+ const first = follow(graph, { highways: [] }).routes;
17204
+ const scoreOf = (plan) => searchScore(graph, follow(graph, plan).routes, first, o);
17205
+ const next = random(o.seed);
17206
+ const pick = (list) => list[Math.floor(next() * list.length) % list.length];
17207
+ const tidy = (highways) => tidyPlan(orderAlongRoutes({ highways }, routes));
17208
+ /** This function gives the plan after one move, or `undefined` when the move does not apply. */
17209
+ const moved = (plan, move) => {
17210
+ const list = [...plan.highways];
17211
+ if (move === "add") {
17212
+ const known = new Set(list.map((h) => h.spine.map((p) => `${p.x},${p.y}`).join(";")));
17213
+ const fresh = pool.filter((h) => !known.has(h.spine.map((p) => `${p.x},${p.y}`).join(";")));
17214
+ if (fresh.length === 0) return void 0;
17215
+ return tidy([...list, pick(fresh)]);
17216
+ }
17217
+ if (list.length === 0) return void 0;
17218
+ const i = Math.floor(next() * list.length) % list.length;
17219
+ const h = list[i];
17220
+ if (move === "drop") return tidy(list.filter((_, j) => j !== i));
17221
+ if (move === "edge") {
17222
+ if (list.length < 2 || h.members.length < 3) return void 0;
17223
+ const m = pick(h.members);
17224
+ const plug = plugs.get(m.edge);
17225
+ if (!plug) return void 0;
17226
+ const others = list.map((g, j) => ({
17227
+ g,
17228
+ j
17229
+ })).filter((x) => x.j !== i && !x.g.members.some((y) => y.edge === m.edge));
17230
+ if (others.length === 0) return void 0;
17231
+ const s = plug.ps.point;
17232
+ const t = plug.pt.point;
17233
+ const to = pick(others);
17234
+ const on = nearestPlace(to.g.spine, s);
17235
+ const off = nearestPlace(to.g.spine, t);
17236
+ if (Math.abs(on - off) < 1e-6) return void 0;
17237
+ list[i] = {
17238
+ ...h,
17239
+ members: h.members.filter((y) => y.edge !== m.edge),
17240
+ lanes: h.lanes.filter((e) => e !== m.edge)
17241
+ };
17242
+ list[to.j] = {
17243
+ ...to.g,
17244
+ members: [...to.g.members, memberAt(to.g.spine, m.edge, on, off, m.seq)],
17245
+ lanes: [...to.g.lanes, m.edge]
17246
+ };
17247
+ return tidy(list);
17248
+ }
17249
+ const k = Math.floor(next() * h.spine.length) % h.spine.length;
17250
+ const step = 2 * finder.margin * (next() < .5 ? -1 : 1);
17251
+ const p = h.spine[k];
17252
+ const q = next() < .5 ? {
17253
+ x: p.x + step,
17254
+ y: p.y
17255
+ } : {
17256
+ x: p.x,
17257
+ y: p.y + step
17258
+ };
17259
+ const spine = h.spine.map((x, j) => j === k ? q : x);
17260
+ if (!finder.obstacles.clearPath(spine, pad)) return void 0;
17261
+ list[i] = {
17262
+ ...h,
17263
+ spine,
17264
+ members: h.members.map((m) => memberAt(spine, m.edge, m.on, m.off, m.seq))
17265
+ };
17266
+ return tidy(list);
17267
+ };
17268
+ let current = start;
17269
+ let currentScore = scoreOf(start);
17270
+ let best = start;
17271
+ let bestScore = currentScore;
17272
+ let temperature = Math.max(0, o.temperature);
17273
+ for (let n = 1, tries = 0; n < budget && tries < 4 * budget; tries++) {
17274
+ const plan = moved(current, pick(SEARCH_MOVES));
17275
+ if (!plan) continue;
17276
+ n++;
17277
+ const score = scoreOf(plan);
17278
+ const delta = score - currentScore;
17279
+ if (delta >= 0 || temperature > 0 && next() < acceptance(-delta / temperature)) [current, currentScore] = [plan, score];
17280
+ if (score > bestScore + 1e-12) [best, bestScore] = [plan, score];
17281
+ if (n % SWEEP === 0) temperature *= .95;
17282
+ }
17283
+ return best;
17284
+ }
17285
+ // src/highways/steiner/index.ts
17286
+ /** The values of `SteinerGroup`. */
17287
+ const STEINER_GROUPS = Object.freeze(["source", "key"]);
17288
+ /** The values of `SteinerMethod`. */
17289
+ const STEINER_METHODS = Object.freeze(["one-steiner", "spanning"]);
17290
+ /** The values of `SteinerMetric`. */
17291
+ const STEINER_METRICS = Object.freeze(["rectilinear", "octilinear"]);
17292
+ /** The default options of the strategy `steiner`. */
17293
+ const STEINER_DEFAULTS = Object.freeze({
17294
+ group: "source",
17295
+ method: "one-steiner",
17296
+ maxGroup: 12,
17297
+ metric: "rectilinear",
17298
+ detour: 1.5,
17299
+ minSize: 2
17300
+ });
17301
+ /** This function gives the distance of two points in the metric. */
17302
+ function metricDistance(a, b, metric) {
17303
+ const dx = Math.abs(a.x - b.x);
17304
+ const dy = Math.abs(a.y - b.y);
17305
+ return metric === "rectilinear" ? dx + dy : Math.max(dx, dy) + (Math.SQRT2 - 1) * Math.min(dx, dy);
17306
+ }
17307
+ /** This function gives the links of the spanning tree of the points (Prim), as pairs of indices, and the sum of their lengths. */
17308
+ function spanningTree(points, metric) {
17309
+ const n = points.length;
17310
+ if (n < 2) return {
17311
+ links: [],
17312
+ cost: 0
17313
+ };
17314
+ const inTree = new Uint8Array(n);
17315
+ const best = new Float64Array(n).fill(Infinity);
17316
+ const from = new Int32Array(n).fill(-1);
17317
+ best[0] = 0;
17318
+ const links = [];
17319
+ let cost = 0;
17320
+ for (let k = 0; k < n; k++) {
17321
+ let u = -1;
17322
+ for (let i = 0; i < n; i++) if (!inTree[i] && (u < 0 || best[i] < best[u])) u = i;
17323
+ inTree[u] = 1;
17324
+ if (from[u] >= 0) {
17325
+ links.push([from[u], u]);
17326
+ cost += best[u];
17327
+ }
17328
+ for (let i = 0; i < n; i++) {
17329
+ if (inTree[i]) continue;
17330
+ const d = metricDistance(points[u], points[i], metric);
17331
+ if (d < best[i]) {
17332
+ best[i] = d;
17333
+ from[i] = u;
17334
+ }
17335
+ }
17336
+ }
17337
+ return {
17338
+ links,
17339
+ cost
17340
+ };
17341
+ }
17342
+ /**
17343
+ * This function gives a Steiner tree of the terminals by the iterated 1-Steiner method (Kahng and Robins). The candidates are the points of the Hanan grid.
17344
+ * Each round adds the candidate that makes the spanning tree shortest, and drops each added point with two links or fewer.
17345
+ * The test `free` removes the candidates in the boxes. The result has the terminals first, then the added points.
17346
+ */
17347
+ function oneSteiner(terminals, metric, free) {
17348
+ const points = [...terminals];
17349
+ const xs = [...new Set(terminals.map((p) => p.x))].sort((a, b) => a - b);
17350
+ const ys = [...new Set(terminals.map((p) => p.y))].sort((a, b) => a - b);
17351
+ const taken = new Set(terminals.map((p) => `${p.x},${p.y}`));
17352
+ const candidates = [];
17353
+ for (const x of xs) for (const y of ys) if (!taken.has(`${x},${y}`) && free({
17354
+ x,
17355
+ y
17356
+ })) candidates.push({
17357
+ x,
17358
+ y
17359
+ });
17360
+ let tree = spanningTree(points, metric);
17361
+ for (let round = 0; round < terminals.length; round++) {
17362
+ let best = -1;
17363
+ let bestCost = tree.cost - 1e-9 * Math.max(1, tree.cost);
17364
+ candidates.forEach((c, i) => {
17365
+ const cost = spanningTree([...points, c], metric).cost;
17366
+ if (cost < bestCost) [best, bestCost] = [i, cost];
17367
+ });
17368
+ if (best < 0) break;
17369
+ points.push(candidates[best]);
17370
+ candidates.splice(best, 1);
17371
+ tree = spanningTree(points, metric);
17372
+ for (;;) {
17373
+ const degree = new Array(points.length).fill(0);
17374
+ for (const [a, b] of tree.links) {
17375
+ degree[a] = degree[a] + 1;
17376
+ degree[b] = degree[b] + 1;
17377
+ }
17378
+ let drop = -1;
17379
+ for (let i = terminals.length; i < points.length; i++) if (degree[i] <= 2) drop = i;
17380
+ if (drop < 0) break;
17381
+ points.splice(drop, 1);
17382
+ tree = spanningTree(points, metric);
17383
+ }
17384
+ }
17385
+ return {
17386
+ points,
17387
+ links: tree.links
17388
+ };
17389
+ }
17390
+ /**
17391
+ * This function gives the polyline of one link of the tree. A rectilinear link is an L in clear view, else a path through the corner graph.
17392
+ * An octilinear link is a straight piece and a diagonal piece. The same link gives the same points each time.
17393
+ */
17394
+ function linkPath(a, b, metric, finder) {
17395
+ const pad = finder.margin / 2;
17396
+ const dx = b.x - a.x;
17397
+ const dy = b.y - a.y;
17398
+ const tries = [];
17399
+ if (Math.abs(dx) < 1e-9 || Math.abs(dy) < 1e-9) tries.push([a, b]);
17400
+ else if (metric === "rectilinear") tries.push([
17401
+ a,
17402
+ {
17403
+ x: b.x,
17404
+ y: a.y
17405
+ },
17406
+ b
17407
+ ], [
17408
+ a,
17409
+ {
17410
+ x: a.x,
17411
+ y: b.y
17412
+ },
17413
+ b
17414
+ ]);
17415
+ else {
17416
+ const d = Math.min(Math.abs(dx), Math.abs(dy));
17417
+ const diag = {
17418
+ x: a.x + Math.sign(dx) * d,
17419
+ y: a.y + Math.sign(dy) * d
17420
+ };
17421
+ const late = {
17422
+ x: b.x - Math.sign(dx) * d,
17423
+ y: b.y - Math.sign(dy) * d
17424
+ };
17425
+ tries.push([
17426
+ a,
17427
+ diag,
17428
+ b
17429
+ ], [
17430
+ a,
17431
+ late,
17432
+ b
17433
+ ]);
17434
+ }
17435
+ for (const t of tries) if (finder.obstacles.clearPath(t, pad)) return t.filter((p, i) => i === 0 || p.x !== t[i - 1].x || p.y !== t[i - 1].y);
17436
+ const way = finder.corners.path(a, b);
17437
+ return way ? finder.corners.pull(way) : [a, b];
17438
+ }
17439
+ /**
17440
+ * This function gives the highways of obstacle-avoiding Steiner trees (HWY-036 with EDA-015). Each group of edges gets one tree over the points out of its plugs.
17441
+ * The links of the tree are rectilinear or octilinear, and they go around the boxes. Each edge takes its path in the tree, when that path is short enough.
17442
+ * The stretches of the tree that two edges or more share become highways.
17443
+ */
17444
+ function steinerPlan(input, o) {
17445
+ const finder = new WayFinder(input.graph.nodes);
17446
+ const pad = finder.margin / 2;
17447
+ const first = firstRoutes(input);
17448
+ const groups = /* @__PURE__ */ new Map();
17449
+ for (const p of edgePlugs(input.graph)) {
17450
+ const key = o.group === "key" ? byCollection(p.edge) ?? `\u0000${p.edge.source.node}` : p.edge.source.node;
17451
+ const s = finder.out(p.ps);
17452
+ const t = finder.out(p.pt);
17453
+ const route = first.get(p.edge.id);
17454
+ const list = groups.get(key) ?? [];
17455
+ list.push({
17456
+ edge: p.edge.id,
17457
+ s,
17458
+ t,
17459
+ base: route ? polylineLength(route) : metricDistance(s, t, "rectilinear")
17460
+ });
17461
+ groups.set(key, list);
17462
+ }
17463
+ const ways = [];
17464
+ for (const key of [...groups.keys()].sort()) {
17465
+ const list = groups.get(key);
17466
+ if (list.length < 2) continue;
17467
+ const index = /* @__PURE__ */ new Map();
17468
+ const terminals = [];
17469
+ const at = (p) => {
17470
+ const k = `${p.x},${p.y}`;
17471
+ let i = index.get(k);
17472
+ if (i === void 0) {
17473
+ index.set(k, i = terminals.length);
17474
+ terminals.push(p);
17475
+ }
17476
+ return i;
17477
+ };
17478
+ const ends = list.map((e) => [at(e.s), at(e.t)]);
17479
+ const tree = o.method === "one-steiner" && terminals.length <= o.maxGroup ? oneSteiner(terminals, o.metric, (p) => !finder.obstacles.inside(p, pad)) : {
17480
+ points: terminals,
17481
+ ...spanningTree(terminals, o.metric)
17482
+ };
17483
+ const n = tree.points.length;
17484
+ const next = Array.from({ length: n }, () => []);
17485
+ for (const [a, b] of tree.links) {
17486
+ const path = linkPath(tree.points[a], tree.points[b], o.metric, finder);
17487
+ next[a].push({
17488
+ to: b,
17489
+ path
17490
+ });
17491
+ next[b].push({
17492
+ to: a,
17493
+ path: [...path].reverse()
17494
+ });
17495
+ }
17496
+ list.forEach((e, k) => {
17497
+ const [s, t] = ends[k];
17498
+ const back = new Int32Array(n).fill(-2);
17499
+ const via = new Array(n);
17500
+ back[s] = -1;
17501
+ const queue = [s];
17502
+ while (queue.length > 0) {
17503
+ const u = queue.shift();
17504
+ if (u === t) break;
17505
+ for (const x of next[u]) {
17506
+ if (back[x.to] !== -2) continue;
17507
+ back[x.to] = u;
17508
+ via[x.to] = x.path;
17509
+ queue.push(x.to);
17510
+ }
17511
+ }
17512
+ if (back[t] === -2 || s === t) return;
17513
+ const pieces = [];
17514
+ for (let v = t; v !== s; v = back[v]) pieces.push(via[v]);
17515
+ const points = [];
17516
+ for (const piece of pieces.reverse()) for (const p of piece) if (points.length === 0 || p.x !== points[points.length - 1].x || p.y !== points[points.length - 1].y) points.push(p);
17517
+ if (polylineLength(points) > o.detour * e.base) return;
17518
+ ways.push({
17519
+ edge: e.edge,
17520
+ key,
17521
+ points
17522
+ });
17523
+ });
17524
+ }
17525
+ return sharedStretches(ways, { minSize: o.minSize });
17526
+ }
17527
+ // src/highways/streets/index.ts
17528
+ /** The default options of the strategy `streets`. */
17529
+ const STREET_DEFAULTS = Object.freeze({
17530
+ detour: 1.2,
17531
+ minGain: 2,
17532
+ maxStreets: 32,
17533
+ minSize: 2,
17534
+ source: "corners"
17535
+ });
17536
+ /**
17537
+ * This function gives orthogonal streets by a greedy set cover (HWY-058). A street covers each edge whose way along it is at most `detour` times its first route.
17538
+ * Each step takes the street with the largest gain: the length that its new edges ride, over the length of the street that they use.
17539
+ * The cover stops at `maxStreets` streets, or when the best gain is below `minGain`. Each edge rides one street at most.
17540
+ */
17541
+ function streetPlan(input, o) {
17542
+ const finder = new WayFinder(input.graph.nodes);
17543
+ const lines = freeLines(input.graph, finder, o.source);
17544
+ const edges = streetEdges(input, finder);
17545
+ const min = 2 * finder.margin;
17546
+ const size = Math.max(2, Math.round(o.minSize));
17547
+ const covers = lines.map((line) => {
17548
+ const out = [];
17549
+ for (const e of edges) {
17550
+ const r = streetRide(line, e);
17551
+ if (r.ride >= min && r.ramps + r.ride <= o.detour * e.base) out.push({
17552
+ edge: e.edge,
17553
+ ride: r
17554
+ });
17555
+ }
17556
+ return out;
17557
+ });
17558
+ const done = /* @__PURE__ */ new Set();
17559
+ const chosen = [];
17560
+ const used = /* @__PURE__ */ new Set();
17561
+ while (chosen.length < Math.max(0, Math.floor(o.maxStreets))) {
17562
+ let best = -1;
17563
+ let bestGain = -Infinity;
17564
+ let bestPart = [];
17565
+ covers.forEach((cover, i) => {
17566
+ if (used.has(i)) return;
17567
+ const part = cover.filter((c) => !done.has(c.edge));
17568
+ if (part.length < size) return;
17569
+ const vertical = lines[i].a.x === lines[i].b.x;
17570
+ const us = part.flatMap((c) => vertical ? [c.ride.on.y, c.ride.off.y] : [c.ride.on.x, c.ride.off.x]);
17571
+ const span = Math.max(...us) - Math.min(...us);
17572
+ if (span <= 0) return;
17573
+ let ride = 0;
17574
+ for (const c of part) ride += c.ride.ride;
17575
+ const gain = ride / span;
17576
+ if (gain > bestGain) [best, bestGain, bestPart] = [
17577
+ i,
17578
+ gain,
17579
+ part
17580
+ ];
17581
+ });
17582
+ if (best < 0 || bestGain < o.minGain) break;
17583
+ used.add(best);
17584
+ for (const c of bestPart) done.add(c.edge);
17585
+ const line = lines[best];
17586
+ const vertical = line.a.x === line.b.x;
17587
+ const us = bestPart.flatMap((c) => vertical ? [c.ride.on.y, c.ride.off.y] : [c.ride.on.x, c.ride.off.x]);
17588
+ const lo = Math.min(...us);
17589
+ const hi = Math.max(...us);
17590
+ const a = vertical ? {
17591
+ x: line.a.x,
17592
+ y: lo
17593
+ } : {
17594
+ x: lo,
17595
+ y: line.a.y
17596
+ };
17597
+ const b = vertical ? {
17598
+ x: line.a.x,
17599
+ y: hi
17600
+ } : {
17601
+ x: hi,
17602
+ y: line.a.y
17603
+ };
17604
+ if (dist(a, b) <= 0) continue;
17605
+ chosen.push({
17606
+ key: `street${chosen.length}`,
17607
+ a,
17608
+ b,
17609
+ members: bestPart.map((c) => ({
17610
+ edge: c.edge,
17611
+ from: c.ride.on,
17612
+ to: c.ride.off,
17613
+ at: 0
17614
+ }))
17615
+ });
17616
+ }
17617
+ return snapPlan(input, chosen, {
17618
+ minSize: size,
17619
+ direct: true
17620
+ });
17621
+ }
17622
+ // src/highways/ridges/index.ts
17623
+ /** The default options of the strategy `ridges`. */
17624
+ const RIDGE_DEFAULTS = Object.freeze({
17625
+ source: "lines",
17626
+ cell: 8,
17627
+ bandwidth: 16,
17628
+ strength: .15,
17629
+ minLength: 60,
17630
+ snap: 16,
17631
+ minSize: 2
17632
+ });
17633
+ /** The largest angle between an edge and a ridge that it snaps to, in degrees. */
17634
+ const MAX_ANGLE = 30;
17635
+ /**
17636
+ * This function gives the density of the polylines on a grid of cells, after a Gaussian blur of the width `bandwidth`.
17637
+ * Each polyline adds 1 to each cell that it goes through, one time for each polyline.
17638
+ */
17639
+ function lineDensity(lines, box, cellSize, bandwidth) {
17640
+ const cell = Math.max(cellSize, box.w / 1024, box.h / 1024, 1e-6);
17641
+ const pad = 3 * bandwidth;
17642
+ const x0 = box.x - pad;
17643
+ const y0 = box.y - pad;
17644
+ const w = Math.max(1, Math.ceil((box.w + 2 * pad) / cell));
17645
+ const h = Math.max(1, Math.ceil((box.h + 2 * pad) / cell));
17646
+ const raw = new Float64Array(w * h);
17647
+ const stamp = new Int32Array(w * h).fill(-1);
17648
+ lines.forEach((p, li) => {
17649
+ for (let i = 1; i < p.length; i++) {
17650
+ const a = p[i - 1];
17651
+ const b = p[i];
17652
+ const steps = Math.max(1, Math.ceil(2 * dist(a, b) / cell));
17653
+ for (let k = 0; k <= steps; k++) {
17654
+ const x = Math.floor((a.x + (b.x - a.x) * k / steps - x0) / cell);
17655
+ const y = Math.floor((a.y + (b.y - a.y) * k / steps - y0) / cell);
17656
+ if (x < 0 || y < 0 || x >= w || y >= h) continue;
17657
+ const c = y * w + x;
17658
+ if (stamp[c] === li) continue;
17659
+ stamp[c] = li;
17660
+ raw[c] = raw[c] + 1;
17661
+ }
17662
+ }
17663
+ });
17664
+ const sigma = Math.max(1e-6, bandwidth / cell);
17665
+ const r = Math.ceil(3 * sigma);
17666
+ const kernel = Array.from({ length: 2 * r + 1 }, (_, i) => Math.exp(-((i - r) ** 2) / (2 * sigma * sigma)));
17667
+ const sum = kernel.reduce((s, v) => s + v, 0);
17668
+ const tmp = new Float64Array(w * h);
17669
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
17670
+ let v = 0;
17671
+ for (let i = -r; i <= r; i++) {
17672
+ const xi = x + i;
17673
+ if (xi >= 0 && xi < w) v += raw[y * w + xi] * kernel[i + r];
17674
+ }
17675
+ tmp[y * w + x] = v / sum;
17676
+ }
17677
+ const rho = new Float64Array(w * h);
17678
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
17679
+ let v = 0;
17680
+ for (let i = -r; i <= r; i++) {
17681
+ const yi = y + i;
17682
+ if (yi >= 0 && yi < h) v += tmp[yi * w + x] * kernel[i + r];
17683
+ }
17684
+ rho[y * w + x] = v / sum;
17685
+ }
17686
+ return {
17687
+ w,
17688
+ h,
17689
+ cell,
17690
+ x0,
17691
+ y0,
17692
+ rho
17693
+ };
17694
+ }
17695
+ /**
17696
+ * This function gives the ridges of a density image (Lindeberg 1998). A ridge cell has a negative eigenvalue of the Hessian with the larger size.
17697
+ * Its density is a maximum along the eigenvector of that eigenvalue. Its strength is the density times the size of that eigenvalue.
17698
+ * The cells with `strength` times the largest strength or more join their neighbours with a direction within 30 degrees.
17699
+ * Each group of cells gives the segment of its main axis. A segment of `minLength` or more is a ridge. The ridges come in the order of their total strength.
17700
+ */
17701
+ function densityRidges(img, strength, minLength) {
17702
+ const { w, h, rho } = img;
17703
+ const at = (x, y) => rho[Math.min(h - 1, Math.max(0, y)) * w + Math.min(w - 1, Math.max(0, x))];
17704
+ const power = new Float64Array(w * h);
17705
+ const across = new Float64Array(w * h * 2);
17706
+ let top = 0;
17707
+ for (let y = 1; y < h - 1; y++) for (let x = 1; x < w - 1; x++) {
17708
+ const v = at(x, y);
16318
17709
  if (v <= 0) continue;
16319
17710
  const hxx = at(x + 1, y) - 2 * v + at(x - 1, y);
16320
17711
  const hyy = at(x, y + 1) - 2 * v + at(x, y - 1);
@@ -21318,18 +22709,343 @@ const PIPELINE_SCHEMA = {
21318
22709
  step: "levels",
21319
22710
  describe: "The level rule of a router that reads one, for example `sections`."
21320
22711
  },
21321
- levelOptions: {
21322
- type: "object",
21323
- describe: "The options of the level rule."
22712
+ levelOptions: {
22713
+ type: "object",
22714
+ describe: "The options of the level rule."
22715
+ },
22716
+ room: {
22717
+ type: "strategy",
22718
+ step: "room",
22719
+ describe: "The room strategy. With it, the gaps of the board open before the routes. Without it, the board stays."
22720
+ },
22721
+ roomOptions: {
22722
+ type: "object",
22723
+ describe: "The options of the room strategy."
22724
+ }
22725
+ };
22726
+ /** The declarations of the options of the highway strategy `steiner` (HWY-036 with EDA-015). */
22727
+ const STEINER_SCHEMA = {
22728
+ group: {
22729
+ type: "enum",
22730
+ values: ["source", "key"],
22731
+ default: "source",
22732
+ describe: "The groups of the trees: the edges from one source node, or the edges of one collection."
22733
+ },
22734
+ method: {
22735
+ type: "enum",
22736
+ values: ["one-steiner", "spanning"],
22737
+ default: "one-steiner",
22738
+ describe: "The method of the tree: the iterated 1-Steiner method, or the spanning tree of the terminals."
22739
+ },
22740
+ maxGroup: {
22741
+ type: "integer",
22742
+ min: 3,
22743
+ max: 32,
22744
+ step: 1,
22745
+ marks: [
22746
+ 8,
22747
+ 12,
22748
+ 16
22749
+ ],
22750
+ default: 12,
22751
+ describe: "A group with more terminals than this number gets the spanning tree alone."
22752
+ },
22753
+ metric: {
22754
+ type: "enum",
22755
+ values: ["rectilinear", "octilinear"],
22756
+ default: "rectilinear",
22757
+ describe: "The metric of the tree: two directions or four directions."
22758
+ },
22759
+ detour: {
22760
+ type: "number",
22761
+ min: 1,
22762
+ max: 3,
22763
+ step: .05,
22764
+ marks: [
22765
+ 1.25,
22766
+ 1.5,
22767
+ 2
22768
+ ],
22769
+ default: 1.5,
22770
+ describe: "An edge rides the tree only when its path in the tree is this many times its first route or less."
22771
+ },
22772
+ minSize: {
22773
+ type: "integer",
22774
+ min: 2,
22775
+ max: 16,
22776
+ step: 1,
22777
+ default: 2,
22778
+ describe: "A stretch of a tree with fewer edges than this number is not a highway."
22779
+ }
22780
+ };
22781
+ /** The declarations of the options of the highway strategy `gaps` (HWY-057). */
22782
+ const GAP_SCHEMA = {
22783
+ minSize: {
22784
+ type: "integer",
22785
+ min: 2,
22786
+ max: 16,
22787
+ step: 1,
22788
+ marks: [
22789
+ 2,
22790
+ 3,
22791
+ 6
22792
+ ],
22793
+ default: 3,
22794
+ describe: "A street with fewer edges than this number (the demand) is not a highway."
22795
+ },
22796
+ discount: {
22797
+ type: "number",
22798
+ min: .1,
22799
+ max: .8,
22800
+ step: .05,
22801
+ marks: [
22802
+ .3,
22803
+ .5,
22804
+ .7
22805
+ ],
22806
+ default: .5,
22807
+ describe: "The cost of a length on a street against a length off the streets."
22808
+ },
22809
+ detour: {
22810
+ type: "number",
22811
+ min: 1,
22812
+ max: 2,
22813
+ step: .05,
22814
+ marks: [
22815
+ 1.2,
22816
+ 1.3,
22817
+ 1.5
22818
+ ],
22819
+ default: 1.3,
22820
+ describe: "An edge rides a street only when its way along the street is this many times its first route or less."
22821
+ },
22822
+ source: {
22823
+ type: "enum",
22824
+ values: ["room", "corners"],
22825
+ default: "room",
22826
+ describe: "The source of the streets: the gaps of the room step, or the lines through the corners."
22827
+ }
22828
+ };
22829
+ /** The declarations of the options of the highway strategy `streets` (HWY-058). */
22830
+ const STREET_SCHEMA = {
22831
+ detour: {
22832
+ type: "number",
22833
+ min: 1,
22834
+ max: 1.5,
22835
+ step: .05,
22836
+ marks: [
22837
+ 1.1,
22838
+ 1.2,
22839
+ 1.3
22840
+ ],
22841
+ default: 1.2,
22842
+ describe: "A street covers an edge when the way of the edge along it is this many times its first route or less."
22843
+ },
22844
+ minGain: {
22845
+ type: "number",
22846
+ min: 1,
22847
+ max: 8,
22848
+ step: .25,
22849
+ marks: [
22850
+ 1.5,
22851
+ 2,
22852
+ 4
22853
+ ],
22854
+ default: 2,
22855
+ describe: "The cover stops when the best street gives less gain: the length that its edges ride over its length."
22856
+ },
22857
+ maxStreets: {
22858
+ type: "integer",
22859
+ min: 4,
22860
+ max: 64,
22861
+ step: 4,
22862
+ marks: [16, 32],
22863
+ default: 32,
22864
+ describe: "The largest number of streets."
22865
+ },
22866
+ minSize: {
22867
+ type: "integer",
22868
+ min: 2,
22869
+ max: 16,
22870
+ step: 1,
22871
+ default: 2,
22872
+ describe: "A street with fewer new edges than this number is not a highway."
22873
+ },
22874
+ source: {
22875
+ type: "enum",
22876
+ values: ["room", "corners"],
22877
+ default: "corners",
22878
+ describe: "The source of the streets: the gaps of the room step, or the lines through the corners."
22879
+ }
22880
+ };
22881
+ /** The declarations of the options of the highway strategy `search` (HWY-059). */
22882
+ const SEARCH_SCHEMA = {
22883
+ from: {
22884
+ type: "enum",
22885
+ values: [
22886
+ "corridors",
22887
+ "quickbundles",
22888
+ "wspd",
22889
+ "road-classes",
22890
+ "flow-field",
22891
+ "od-cells",
22892
+ "shared-ways"
22893
+ ],
22894
+ default: "corridors",
22895
+ describe: "The plan where the search starts."
22896
+ },
22897
+ pool: {
22898
+ type: "list",
22899
+ default: ["quickbundles", "road-classes"],
22900
+ describe: "The generators of the highways that the move `add` takes."
22901
+ },
22902
+ evaluations: {
22903
+ type: "integer",
22904
+ min: 0,
22905
+ max: 400,
22906
+ step: 4,
22907
+ marks: [
22908
+ 0,
22909
+ 16,
22910
+ 64
22911
+ ],
22912
+ default: 16,
22913
+ describe: "The number of plans that the search measures. The value 0 gives the start plan."
22914
+ },
22915
+ temperature: {
22916
+ type: "number",
22917
+ min: 0,
22918
+ max: .2,
22919
+ step: .005,
22920
+ marks: [
22921
+ 0,
22922
+ .02,
22923
+ .05
22924
+ ],
22925
+ default: .02,
22926
+ describe: "The first temperature of the annealing, on the scale of the score."
22927
+ },
22928
+ inkWeight: {
22929
+ type: "number",
22930
+ min: 0,
22931
+ max: 4,
22932
+ step: .25,
22933
+ marks: [1],
22934
+ default: 1,
22935
+ describe: "The weight of the ink that the routes save (B1)."
22936
+ },
22937
+ stretchWeight: {
22938
+ type: "number",
22939
+ min: 0,
22940
+ max: 4,
22941
+ step: .25,
22942
+ marks: [.5],
22943
+ default: .5,
22944
+ describe: "The weight of the 0.95 quantile of the stretch (B3)."
22945
+ },
22946
+ ambiguityWeight: {
22947
+ type: "number",
22948
+ min: 0,
22949
+ max: 4,
22950
+ step: .25,
22951
+ marks: [.5],
22952
+ default: .5,
22953
+ describe: "The weight of the ambiguity (B4)."
22954
+ },
22955
+ angleWeight: {
22956
+ type: "number",
22957
+ min: 0,
22958
+ max: 4,
22959
+ step: .25,
22960
+ marks: [.25],
22961
+ default: .25,
22962
+ describe: "The weight of the largest merge angle (B6), divided by 180 degrees."
22963
+ },
22964
+ seed: {
22965
+ type: "integer",
22966
+ min: 0,
22967
+ max: 1e3,
22968
+ step: 1,
22969
+ default: 1,
22970
+ describe: "The seed of the moves."
22971
+ }
22972
+ };
22973
+ /** The declarations of the options of the highway strategy `interchanges` (HWY-060). */
22974
+ const INTERCHANGE_SCHEMA = {
22975
+ from: {
22976
+ type: "enum",
22977
+ values: ["corridors", "shared-ways"],
22978
+ default: "corridors",
22979
+ describe: "The plan that gives the highways of the network."
22980
+ },
22981
+ changeCost: {
22982
+ type: "number",
22983
+ min: 0,
22984
+ max: 8,
22985
+ step: .5,
22986
+ marks: [
22987
+ 0,
22988
+ 2,
22989
+ 4
22990
+ ],
22991
+ default: 2,
22992
+ describe: "The cost of a change between two highways, in units of the board."
22993
+ },
22994
+ interchanges: {
22995
+ type: "integer",
22996
+ min: 1,
22997
+ max: 32,
22998
+ step: 1,
22999
+ marks: [4, 16],
23000
+ default: 16,
23001
+ describe: "The largest number of interchanges."
23002
+ },
23003
+ perEdge: {
23004
+ type: "integer",
23005
+ min: 1,
23006
+ max: 3,
23007
+ step: 1,
23008
+ default: 2,
23009
+ describe: "The largest number of highways on the way of one edge."
23010
+ },
23011
+ radius: {
23012
+ type: "number",
23013
+ min: .5,
23014
+ max: 8,
23015
+ step: .5,
23016
+ marks: [
23017
+ 1,
23018
+ 2,
23019
+ 4
23020
+ ],
23021
+ default: 2,
23022
+ describe: "Two highways get an interchange where they come this near, in units of the board."
21324
23023
  },
21325
- room: {
21326
- type: "strategy",
21327
- step: "room",
21328
- describe: "The room strategy. With it, the gaps of the board open before the routes. Without it, the board stays."
23024
+ discount: {
23025
+ type: "number",
23026
+ min: .1,
23027
+ max: 1,
23028
+ step: .05,
23029
+ marks: [
23030
+ .3,
23031
+ .5,
23032
+ .7
23033
+ ],
23034
+ default: .5,
23035
+ describe: "The cost of a length on a highway against a length off the highways."
21329
23036
  },
21330
- roomOptions: {
21331
- type: "object",
21332
- describe: "The options of the room strategy."
23037
+ detour: {
23038
+ type: "number",
23039
+ min: 1,
23040
+ max: 3,
23041
+ step: .05,
23042
+ marks: [
23043
+ 1.25,
23044
+ 1.5,
23045
+ 2
23046
+ ],
23047
+ default: 1.5,
23048
+ describe: "An edge rides the network only when its way is this many times its first route or less."
21333
23049
  }
21334
23050
  };
21335
23051
  // src/strategies/builtins.ts
@@ -22020,6 +23736,51 @@ const highwayStrategies = Object.freeze({
22020
23736
  maxNodes: 1e4,
22021
23737
  run: (input, o) => scaleSpacePlan(input, o)
22022
23738
  }),
23739
+ steiner: defineStrategy({
23740
+ step: "highway",
23741
+ name: "steiner",
23742
+ 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.",
23743
+ defaults: { ...STEINER_DEFAULTS },
23744
+ schema: STEINER_SCHEMA,
23745
+ maxNodes: 1e4,
23746
+ run: (input, o) => steinerPlan(input, o)
23747
+ }),
23748
+ gaps: defineStrategy({
23749
+ step: "highway",
23750
+ name: "gaps",
23751
+ describe: "Gap highways (HWY-057). The free middle lines of the gaps are streets, and a street that enough edges choose becomes a highway.",
23752
+ defaults: { ...GAP_DEFAULTS },
23753
+ schema: GAP_SCHEMA,
23754
+ maxNodes: 1e4,
23755
+ run: (input, o) => gapPlan(input, o)
23756
+ }),
23757
+ streets: defineStrategy({
23758
+ step: "highway",
23759
+ name: "streets",
23760
+ 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.",
23761
+ defaults: { ...STREET_DEFAULTS },
23762
+ schema: STREET_SCHEMA,
23763
+ maxNodes: 1e4,
23764
+ run: (input, o) => streetPlan(input, o)
23765
+ }),
23766
+ search: defineStrategy({
23767
+ step: "highway",
23768
+ name: "search",
23769
+ 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.",
23770
+ defaults: { ...SEARCH_DEFAULTS },
23771
+ schema: SEARCH_SCHEMA,
23772
+ maxNodes: 1e4,
23773
+ run: (input, o) => searchPlan(input, o)
23774
+ }),
23775
+ interchanges: defineStrategy({
23776
+ step: "highway",
23777
+ name: "interchanges",
23778
+ describe: "A network of highways with interchanges (HWY-060). Each edge rides up to a number of highways, with a cost for each change.",
23779
+ defaults: { ...INTERCHANGE_DEFAULTS },
23780
+ schema: INTERCHANGE_SCHEMA,
23781
+ maxNodes: 1e4,
23782
+ run: (input, o) => interchangePlan(input, o)
23783
+ }),
22023
23784
  "corridors+breakeven": gateStrategy(corridors, breakEvenGate),
22024
23785
  "corridors+stretch": gateStrategy(corridors, stretchGate),
22025
23786
  "corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
@@ -23394,199 +25155,6 @@ function formatSections(rows) {
23394
25155
  })
23395
25156
  ].join("\n");
23396
25157
  }
23397
- // src/bench/measures/bundles.ts
23398
- /**
23399
- * This function gives the share of the ink that shared segments save (B1 of MEAS0): `inkSaved = 1 - ink(S) / Σ_e L(r_e)`.
23400
- * `ink(S)` counts a shared segment one time, as `measureQuality` does. The scale is 0 to 1, and higher is better. The cost is `n log n`.
23401
- */
23402
- function inkSaved(routes) {
23403
- let total = 0;
23404
- for (const r of routes) total += polylineLength(r.points);
23405
- if (total <= 0) return 0;
23406
- let ink = 0;
23407
- for (const [a, b] of drawnSegments(routes)) ink += dist(a, b);
23408
- return Math.max(0, 1 - ink / total);
23409
- }
23410
- /**
23411
- * This function measures the ambiguity of the routes (B4 of MEAS0), with one step of neighbours.
23412
- * `N(v)` holds the nodes whose routes share a drawn segment with a route of `v`.
23413
- * `amb = Σ_v count(w in N(v) : no edge v-w) / Σ_v count(N(v))`. The cost is quadratic, so a seeded sample serves above `exact` nodes.
23414
- */
23415
- function ambiguity(graph, routes, options = {}) {
23416
- const n = graph.nodes.length;
23417
- const index = new Map(graph.nodes.map((v, i) => [v.id, i]));
23418
- const ends = /* @__PURE__ */ new Map();
23419
- for (const e of graph.edges) {
23420
- const s = index.get(e.source.node);
23421
- const t = index.get(e.target.node);
23422
- if (s !== void 0 && t !== void 0) ends.set(e.id, [s, t]);
23423
- }
23424
- const keyIds = /* @__PURE__ */ new Map();
23425
- const nodesOf = [];
23426
- const keysOf = /* @__PURE__ */ new Map();
23427
- for (const r of routes) {
23428
- const end = ends.get(r.edge);
23429
- if (!end) continue;
23430
- const own = [];
23431
- for (let i = 1; i < r.points.length; i++) {
23432
- const k = segmentKey(r.points[i - 1], r.points[i]);
23433
- if (k === void 0) continue;
23434
- let id = keyIds.get(k);
23435
- if (id === void 0) {
23436
- id = nodesOf.length;
23437
- keyIds.set(k, id);
23438
- nodesOf.push([]);
23439
- }
23440
- if (own.includes(id)) continue;
23441
- own.push(id);
23442
- nodesOf[id].push(end[0], end[1]);
23443
- }
23444
- keysOf.set(r.edge, own);
23445
- }
23446
- const mark = new Int32Array(n).fill(-1);
23447
- nodesOf.forEach((list, k) => {
23448
- let j = 0;
23449
- for (const v of list) if (mark[v] !== k) {
23450
- mark[v] = k;
23451
- list[j++] = v;
23452
- }
23453
- list.length = j;
23454
- });
23455
- const incident = Array.from({ length: n }, () => []);
23456
- const adjacent = Array.from({ length: n }, () => []);
23457
- for (const e of graph.edges) {
23458
- const end = ends.get(e.id);
23459
- if (!end) continue;
23460
- incident[end[0]].push(e.id);
23461
- incident[end[1]].push(e.id);
23462
- adjacent[end[0]].push(end[1]);
23463
- adjacent[end[1]].push(end[0]);
23464
- }
23465
- const exact = n <= (options.exact ?? 1e3);
23466
- const R = rng(options.seed ?? 1);
23467
- const pick = exact ? Array.from({ length: n }, (_, i) => i) : Array.from({ length: options.sample ?? 2e3 }, () => Math.floor(R() * n));
23468
- const keyStamp = new Int32Array(nodesOf.length).fill(-1);
23469
- const nearStamp = new Int32Array(n).fill(-1);
23470
- const linkStamp = new Int32Array(n).fill(-1);
23471
- let all = 0;
23472
- let wrong = 0;
23473
- pick.forEach((v, step) => {
23474
- for (const w of adjacent[v]) linkStamp[w] = step;
23475
- nearStamp[v] = step;
23476
- for (const e of incident[v]) for (const k of keysOf.get(e) ?? []) {
23477
- if (keyStamp[k] === step) continue;
23478
- keyStamp[k] = step;
23479
- for (const w of nodesOf[k]) {
23480
- if (nearStamp[w] === step) continue;
23481
- nearStamp[w] = step;
23482
- all++;
23483
- if (linkStamp[w] !== step) wrong++;
23484
- }
23485
- }
23486
- });
23487
- return {
23488
- value: all ? wrong / all : 0,
23489
- estimated: !exact
23490
- };
23491
- }
23492
- /** The drawn segments of each route, as keys, in the order of its points. A segment shorter than the grain of `segmentKey` has no key. */
23493
- function routeKeys(routes) {
23494
- const keys = /* @__PURE__ */ new Map();
23495
- const users = /* @__PURE__ */ new Map();
23496
- for (const r of routes) {
23497
- const own = [];
23498
- for (let i = 1; i < r.points.length; i++) {
23499
- const k = segmentKey(r.points[i - 1], r.points[i]);
23500
- if (k === void 0) continue;
23501
- own.push(k);
23502
- let set = users.get(k);
23503
- if (!set) users.set(k, set = /* @__PURE__ */ new Set());
23504
- set.add(r.edge);
23505
- }
23506
- keys.set(r.edge, own);
23507
- }
23508
- return {
23509
- keys,
23510
- users
23511
- };
23512
- }
23513
- /**
23514
- * This function gives the bundled share of a result (B2 of MEAS0): `count(e : e is in a run or a bundle) / m`.
23515
- * An edge is also in a bundle when its route shares a drawn segment with the route of another edge. Thus a router without `runs` and `bundles` gets a value too.
23516
- * The scale is 0 to 1. A higher value is better only in context. The cost is linear.
23517
- */
23518
- function bundledShare(result) {
23519
- const routes = result.routes;
23520
- if (routes.length === 0) return 0;
23521
- const shared = /* @__PURE__ */ new Set();
23522
- for (const run of result.runs ?? []) for (const e of run.edges) shared.add(e);
23523
- for (const b of result.bundles ?? []) if (b.edges.length > 1) for (const e of b.edges) shared.add(e);
23524
- const { keys, users } = routeKeys(routes);
23525
- for (const [edge, own] of keys) if (own.some((k) => (users.get(k)?.size ?? 0) > 1)) shared.add(edge);
23526
- let count = 0;
23527
- for (const r of routes) if (shared.has(r.edge)) count++;
23528
- return count / routes.length;
23529
- }
23530
- /**
23531
- * This function gives the bundle stretch (B3 of MEAS0): `st(e) = L(r_e) / L(r*_e)` for each edge, with `r*_e` the base route of the edge.
23532
- * The base routes are the routes of `corners` on the same input. An edge without a base route, or with a base route of 1 unit or less, does not count.
23533
- * The cost is an extra run of the base router, and then linear.
23534
- */
23535
- function bundleStretch(routes, base) {
23536
- const baseLength = /* @__PURE__ */ new Map();
23537
- for (const r of base) baseLength.set(r.edge, polylineLength(r.points));
23538
- const st = [];
23539
- for (const r of routes) {
23540
- const b = baseLength.get(r.edge);
23541
- if (b === void 0 || b <= 1 || r.points.length < 2) continue;
23542
- st.push(polylineLength(r.points) / b);
23543
- }
23544
- if (st.length === 0) return {
23545
- median: 1,
23546
- q95: 1,
23547
- edges: 0
23548
- };
23549
- return {
23550
- median: quantile(st, .5),
23551
- q95: quantile(st, .95),
23552
- edges: st.length
23553
- };
23554
- }
23555
- /**
23556
- * This function gives the merge angle of the routes (B6 of MEAS0): the largest turn of a lane where it joins or leaves a run, in degrees.
23557
- * A run is a drawn segment that the routes of two edges or more share. A route joins or leaves a run at a vertex where the edges on its segment change.
23558
- * One of the two segments at that vertex has two edges or more. The turn is the angle between the two segments.
23559
- * Routes without a shared segment give 0. The scale is 0 to 180 degrees, and lower is better. The cost is linear.
23560
- */
23561
- function mergeAngle(routes) {
23562
- const { users } = routeKeys(routes);
23563
- const sig = /* @__PURE__ */ new Map();
23564
- for (const [k, set] of users) sig.set(k, [...set].sort().join("\0"));
23565
- let max = 0;
23566
- for (const r of routes) {
23567
- let prev;
23568
- for (let i = 1; i < r.points.length; i++) {
23569
- const a = r.points[i - 1];
23570
- const b = r.points[i];
23571
- const k = segmentKey(a, b);
23572
- if (k === void 0) continue;
23573
- if (prev && sig.get(prev.k) !== sig.get(k) && ((users.get(prev.k)?.size ?? 0) > 1 || (users.get(k)?.size ?? 0) > 1)) {
23574
- const ux = prev.b.x - prev.a.x;
23575
- const uy = prev.b.y - prev.a.y;
23576
- const vx = b.x - a.x;
23577
- const vy = b.y - a.y;
23578
- const turn = Math.abs(Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy));
23579
- max = Math.max(max, turn * 180 / Math.PI);
23580
- }
23581
- prev = {
23582
- k,
23583
- a,
23584
- b
23585
- };
23586
- }
23587
- }
23588
- return max;
23589
- }
23590
25158
  // src/bench/measures/levels.ts
23591
25159
  /** This function gives the levels in the order of their depth, from the far view to the near view. */
23592
25160
  function byDepth(levels) {
@@ -25842,6 +27410,11 @@ function sideAt(p, w, h) {
25842
27410
  /** This function gives 0 for -0, because JSON writes -0 as 0. Then a file and the graph that it comes from hold the same numbers. */
25843
27411
  const num = round3;
25844
27412
  function portToFile(p, n) {
27413
+ if (p.along !== void 0) return {
27414
+ id: p.id,
27415
+ ...p.side ? { side: TO_FILE[p.side] } : {},
27416
+ along: p.along
27417
+ };
25845
27418
  const side = p.side ? TO_FILE[p.side] : p.at ? sideAt(p.at, n.box.w, n.box.h) : void 0;
25846
27419
  return {
25847
27420
  id: p.id,
@@ -25853,6 +27426,11 @@ function portToFile(p, n) {
25853
27426
  };
25854
27427
  }
25855
27428
  function portToModel(p) {
27429
+ if (p.along !== void 0) return {
27430
+ id: p.id,
27431
+ ...p.side ? { side: TO_MODEL[p.side] } : {},
27432
+ along: p.along
27433
+ };
25856
27434
  if (p.x !== void 0 && p.y !== void 0) return {
25857
27435
  id: p.id,
25858
27436
  at: {
@@ -25867,7 +27445,7 @@ function portToModel(p) {
25867
27445
  }
25868
27446
  /**
25869
27447
  * This function writes a graph as an input file of the form `layout-input/1`, with its hash.
25870
- * A fixed plug gets `x`, `y` and the side that holds it. `inputGraph` reads the file back.
27448
+ * A fixed plug gets `x`, `y` and the side that holds it. A plug with `along` keeps its `side` and `along`. `inputGraph` reads the file back.
25871
27449
  */
25872
27450
  function graphInput(graph, head) {
25873
27451
  const boxes = graph.nodes.map((n) => ({
@@ -28193,7 +29771,12 @@ const MOVE_NAMES = Object.freeze({
28193
29771
  territories: "territory",
28194
29772
  consensus: "cluster",
28195
29773
  "p-hub": "hubLink",
28196
- "scale-space": "cluster"
29774
+ "scale-space": "cluster",
29775
+ steiner: "tree",
29776
+ gaps: "gap",
29777
+ streets: "street",
29778
+ search: "searched",
29779
+ interchanges: "networkRoad"
28197
29780
  });
28198
29781
  /**
28199
29782
  * The highway strategies that change the plan of another strategy (`from`). Their own move is a change of that plan, so they count no move.
@@ -30379,6 +31962,6 @@ const sharedStretchStrategy = defineStrategy({
30379
31962
  name: "shared-stretch"
30380
31963
  }).route(graph)
30381
31964
  });
30382
- export { AMBIGUITY_GUARD_DEFAULTS, ARC_SOURCES, AREA_DEFAULTS, AREA_WIDTHS, BACKTRACK_MIN, BACKTRACK_SHARE, BREAK, BREAK_EVEN, BREAK_EVEN_DEFAULTS, BUILT_IN_STRATEGIES, BoundaryWalk, CANDIDATE_SOURCES, CAP_SWEEP, CASES, CHANNEL_REACH, CLEARANCE_CAP, COMPARE_ALL_STEPS, COMPARE_STEPS, CONFLUENT_DEFAULTS, CONSENSUS_DEFAULTS, CONSENSUS_LINKAGES, CORRIDOR_DEFAULTS, CROSSBAR_DEFAULTS, CUBU_DEFAULTS, CURVE_TYPES, CUT_DEFAULTS, CornerGraph, DEFAULT_RNG, DENSE_GROUPS, DENSITY_KERNELS, DIFFUSION_DEFAULTS, DIVIDED_DEFAULTS, EDGE_DISTANCES, EDGE_PATH_DEFAULTS, EXACT_CLASSES, EXACT_EDGES_PER_NODE, EXACT_V1, EXACT_V1_BOUNDS, EXACT_V1_BOUNDS_SIZES, EXACT_V1_SEEDS, EXACT_V1_SIZES, FDEB_DEFAULTS, FIXTURES, FIXTURE_LAYOUTS, FIXTURE_LOCALITY, FLOW_CLUSTER_DEFAULTS, FLOW_FIELD_DEFAULTS, FRECHET_DEFAULTS, FRONT_OBJECTIVES, GAP_BASELINES, GAP_BOOTSTRAP, GAP_CAPS, GAP_RULES, GAP_VIEWS, GROUP_SCOPES, GROUP_SPINES, GROWTH_DEFAULTS, GROWTH_SWEEP, Grid, HDBSCAN_DEFAULTS, HIERARCHY_DEFAULTS, HIERARCHY_GROUPS, HIGHWAYS_ENGINE, HIGHWAYS_FAMILIES, HIGHWAY_BASES, HIGHWAY_BASE_ROUTERS, HOUGH_DEFAULTS, HUB_ALLOCATIONS, HUB_ARC_DEFAULTS, HUB_CANDIDATES, HUB_CHAIN, HYSTERESIS_DEFAULTS, KDEEB_DEFAULTS, KD_DEFAULTS, KENDALL_EXACT, LANES_DEFAULTS, LANE_ORDERS, LANE_STRETCH_DEFAULTS, LANE_STRETCH_ORDERS, LANE_TRACKS, LEVEL_HIGHWAY_DEFAULTS, LINKAGES, LINK_COMMUNITY_DEFAULTS, MANY_GROUPS, MEASURES, MEASURE_LANE_WIDTH, MEET_DEFAULTS, MEMBER_FORMS, MESH_DEFAULTS, MINGLE_DEFAULTS, MIN_EFFECTS, MLSEB_DEFAULTS, MLS_FITS, MODULE_RULES, MOVE_NAMES, NESTED_DEFAULTS, OD_CELL_DEFAULTS, OUTSIDE_SLACK, Obstacles, PACKED_GRID, PICK, PLAN_CHANGERS, PORTFOLIO_B1, PORTFOLIO_BANDS, PORTFOLIO_BASE_MEMBER, PORTFOLIO_CAPS, PORTFOLIO_FIT, PORTFOLIO_HASHES, PORTFOLIO_KS_API, PORTFOLIO_RULES, PORTFOLIO_SETS, POWER_DEFAULTS, PROPOSED_RULES, P_HUB_DEFAULTS, QUICKBUNDLES_DEFAULTS, RECORD_EPS, RESISTANCE_DEFAULTS, RIDGE_DEFAULTS, ROAD_CLASS_DEFAULTS, ROOM_FAMILIES, ROUTED_STEPS, ROUTERS, SAME_HIGHWAY, SBEB_DEFAULTS, SCALE_SPACE_DEFAULTS, SCREEN_FAMILY_BY, SCREEN_RULES, SCREEN_SETS, SCREEN_STEPS, SCREEN_V1, SCREEN_V2, SCREEN_V2_HELD_OUT, SCREEN_WRAPS, SECTION_DEFAULTS, SELECT_DEFAULTS, SIDE_NORMAL, SKELETON_GROUPS, SKELETON_TARGETS, SLACK_SWEEP, SMALL_CLUSTERED, SOFT_KEY_DEFAULTS, SPACING_LIMIT, SPECTRAL_DEFAULTS, SPIRAL_DEFAULTS, STABLE_ID_DEFAULTS, STEPS, STRATEGIES, STRETCH_DEFAULTS, STRETCH_SWEEP, STUB_DEFAULTS, SUITE_COLUMNS, SoftGraph, SplitTree, TERRITORY_DEFAULTS, TERRITORY_POINTS, TINY_UNIFORM, TOLERANCE, TRACLUS_DEFAULTS, TRAFFIC_RULES, WEISZFELD_DEFAULTS, WSPD_DEFAULTS, WayFinder, ZIPPER_DEFAULTS, activationBase, activationReport, adjustedRandIndex, allSections, ambiguity, ambiguityGate, ambiguityPlan, arcCurve, areaPlan, asRouter, backtrackOf, balance, basePlan, benchBendRoom, benchCurveStrategies, benchCurves, benchHighways, benchLevelRoom, benchOne, benchRoom, benchRun, benchTracks, bendCount, bendingEnergy, bicliqueGroups, bigGroups, boardGaps, boardUnit, boardWins, bootstrapMedian, boundsFamilyName, boxExit, boxPush, boxUnit, boxesOverlap, breakEvenGate, breakEvenPlan, buildSections, bundleStretch, bundledShare, byCollection, byDataGroup, candidateArcs, candidatePlan, candidateScore, canonicalJson, capSettings, caseSeed, cellSteps, center, chainClusters, changeGraph, changedDimensions, channelOverflows, channelWidth, characteristicPoints, checkFamilies, checkLevels, checkOptions, checkPlan, checkProperties, checkSchema, checkSections, clampOptions, clothoidCurve, clusters1d, coAssociation, commonEdges, compareAnyStep, compareMeasure, compareSpec, compareStep, compatibility, compatibilityMatrix, confluentPlan, consensusGroups, consensusLabels, consensusPlan, controlChain, cornersRouter, corpusManifest, corridorPlan, countCrossings, countOverlaps, createFamilyRegistry, createRegistry, crossbarPlan, crossesItself, crossingAngle, crossingAngles, cubicAt, cubuPlan, curveFaults, curveJoints, curveOf, curveRuns, curveSpace, curveStrategies, curvesHash, cutGroups, cutShare, declaresFamily, defineStrategy, denseGroupsBoard, densityPlan, densityRidges, depthLevels, detourSpread, diffusionPlan, dimensionAt, dimensionUnit, dist, distinctPoints, divideHighway, dividedPlan, documentHash, dominates, drawnSegments, edgeBlocks, edgeEnds, edgeLengths, edgePathPlan, edgePaths, edgePlugs, edgeResistance, enumDimension, exactBoard, exactFamily, exactGenerator, exactSeed, exactStretch, exactStretches, exercised, facingEnds, familyCell, familyKind, familyOf, familySeed, farLevel, farLinks, fdebLines, fdebPlan, fft, findStrategy, finderOf, firstLines, fixedPortPoint, fixtureGroupOptions, fixtureParams, fixtureRngSeed, fixtureSpecOf, flowClusterPlan, flowClusters, flowDistance, flowFieldPlan, fnv1a32, follow, formatActivation, formatBendRoom, formatCapped, formatCurves, formatFaults, formatFronts, formatGap, formatHighways, formatInput, formatLevelRoom, formatLevels, formatMeasureTime, formatPaired, formatRoom, formatRules, formatScreen, formatSections, formatSummaries, formatTable, formatTracks, frechet, frechetBundles, frechetPlan, g1Defect, g2Defect, gapBaselines, gapCap, gapClosed, gapLaneCounts, gapLanes, gapNames, gapValues, gateStrategy, getStrategy, goesOutAndBack, graphInput, greedyBicliques, greedyCrossbars, groupEdges, groupRun, groupSettings, groupStability, groupStrategies, groupedPlan, growthPlan, growthSettings, guardBreaks, hasCusp, hdbscanClusters, hdbscanLabels, hdbscanPlan, hierarchyPlan, highwayChurn, highwayCurve, highwayCurves, highwayEdits, highwayIdKeep, highwayMoves, highwayRouter, highwayRouterStrategy, highwayRouters, highwayStrategies, highwaysRegistry, houghLines, houghPeaks, houghPlan, hubArcPlan, hubCandidates, hubChainBoard, hysteresisGate, hysteresisPlan, inflate, inflectionCount, inflectionCurve, inflectionSector, inkSaved, innerPoints, inputGraph, inputId, isRoutedStep, itemSizes, jaccard, jaccardIndex, jogPeaks, joinReports, jsonSafe, kMeans, kdGroups, kdeebLines, kdeebPlan, keepMembers, kendallTau, keyGroups, keyedSections, keysPlan, labelGroups, laneArea, laneCrossings, laneFidelity, laneOrder, laneRoutes, laneSeparation, laneSettings, laneStretches, lanesPlan, lanesThrough, leafGroups, lengthBetween, levelCut, levelGrouping, levelHighwayPlan, levelMeasures, levelPairs, levelRoom, levelStrategies, levelsOf, lineDensity, linePoint, linkAgreement, linkCommunities, linkCommunityGroups, linkCommunityPlan, linkDensity, listOptions, listStrategies, makeFixture, makeRoom, manifestHash, manyGroupsBoard, matchHighways, maxStretch, mdf, meanLine, measureBoard, measureByKey, measureByName, measureHighways, measureJogs, measureLevel, measureLevelFaults, measureLinks, measureName, measureQuality, measureRouteFaults, measureStability, meetMember, meetPlan, meetingPoints, memberAt, memberStrategy, mergeAngle, meshPlan, meshWaypoints, minEffectOf, minHash, mingleBundles, minglePlan, mixedSegments, mixesCollections, mlsPoint, mlsebLines, mlsebPlan, modularity, modulesOf, mulberry32, namedObjectives, nearestCentre, nearestOnBoundary, nearestOnLine, nearestSide, nearestSkeleton, nestedGridBoard, nestedGroups, nestedPlan, nestingFaults, nestingViolations, nextRow, nodeAmbiguity, nodeIndex, nodeLinks, numberDimension, numberOf, occupiedArea, odCellPlan, odGroups, offsetLine, offsetPolyline, openGaps, openLanes, optimaFile, optimumKey, optionDefaults, orderChanges, orderFragility, orderLanes, orderRows, orderStability, orderSuiteRows, orthogonality, outOfBoxes, outputGroups, outputHash, pHubPlan, pMedian, packedGridBoard, paramProblem, paretoFront, parseLines, partWithin, passSettings, pathPoints, peakCurvature, pickLevel, pickMoves, placePlug, planMoves, planOfResult, plugSpacing, plugStrategies, plugsRouter, pointAt, pointBoxDistance, pointDensity, pointGroups, pointOnSide, pointSegmentDistance, polylineLength, portfolio, portfolioB1Spec, portfolioBand, portfolioFacts, portfolioKey, portfolioMember, portfolioMembers, portfolioName, portfolioRouter, portfolioStrategy, positionStability, powerPlan, pullApart, quadtreeGroups, quantile, quickBundles, quickBundlesPlan, randomInt, rayToBox, recordLines, recordMoves, rectScattered, regionBounds, register, repairResult, repairStrategy, representative, resample, resistanceClusters, resistancePlan, resultForm, resultHash, ridgeLines, ridgePlan, rng, roadClassPlan, roomDemand, roomLinks, roomOrderFaults, roomStrategies, roomValues, round3, roundCorners, roundedCurve, routeCrossings, routeInStableOrder, routeMeasures, routePortfolio, routeSeparation, routeStability, routerStrategies, routersOf, ruleCounts, runBenchmark, runKey, runPipeline, runProfile, runSuite, sameData, sampleCurve, samplesOf, sbebLines, sbebPlan, scaleClusters, scaleSpaceGroups, scaleSpacePlan, screen, screenInputs, screenSplit, sectionsRouter, segmentBounds, segmentBoxDistance, segmentClusters, segmentDistance, segmentHitsBox, segmentHitsRect, segmentKey, segmentsCross, segmentsOf, selectBest, selectPlan, setManifest, sha256Hex, shapeSkeleton, sharedRouter, sharedStretchStrategy, sharedStretches, sharedWaysAll, sharedWaysPlan, shuffleEdges, sideAngle, sideToward, skeletonGroups, skeletonPath, slackSettings, smoothCurve, snapPlan, softDistance, softKeyClusters, softKeyPlan, spectralClusters, spectralLabels, spectralPlan, spinePart, spiralJoin, spiralPlan, spiralTree, splineCurve, splitPath, splitPoint, stableIdGate, stableIdPlan, stableOrder, stableOrderStrategy, stepRouterStrategy, stepRouters, stepSeed, straightCurve, straightRouter, strategyKey, stretchGate, stretchLaneOrders, stretchPlan, stretchSettings, stubPlan, suiteFronts, sumFaults, summarize, summarizeGap, summarizeRows, sweepGrid, sweepOf, sweepRoom, sweepSections, sweepValues, symmetricEigen, territoryPlan, toSvgPath, totalTurn, tracePlan, traclusPlan, treeCost, treeDepth, trunksRouter, turns, unwrappedName, utf8, waypointWay, weberPoint, weiszfeldPlan, wholeStrategies, withBends, withRepair, withStableOrder, wspdGroups, wspdPlan, zipLegs, zipperPlan };
31965
+ export { AMBIGUITY_GUARD_DEFAULTS, ARC_SOURCES, AREA_DEFAULTS, AREA_WIDTHS, ATTRACTION_DEFAULTS, ATTRACTION_SCHEMA, BACKTRACK_MIN, BACKTRACK_SHARE, BREAK, BREAK_EVEN, BREAK_EVEN_DEFAULTS, BUILT_IN_STRATEGIES, BoundaryWalk, CANDIDATE_SOURCES, CAP_SWEEP, CASES, CHANNEL_REACH, CLEARANCE_CAP, COMPARE_ALL_STEPS, COMPARE_STEPS, CONFLUENT_DEFAULTS, CONSENSUS_DEFAULTS, CONSENSUS_LINKAGES, CORRIDOR_DEFAULTS, CROSSBAR_DEFAULTS, CUBU_DEFAULTS, CURVE_TYPES, CUT_DEFAULTS, CornerGraph, DEFAULT_RNG, DENSE_GROUPS, DENSITY_KERNELS, DIFFUSION_DEFAULTS, DIVIDED_DEFAULTS, EDGE_DISTANCES, EDGE_PATH_DEFAULTS, EXACT_CLASSES, EXACT_EDGES_PER_NODE, EXACT_V1, EXACT_V1_BOUNDS, EXACT_V1_BOUNDS_SIZES, EXACT_V1_SEEDS, EXACT_V1_SIZES, FDEB_DEFAULTS, FIXTURES, FIXTURE_LAYOUTS, FIXTURE_LOCALITY, FLOW_CLUSTER_DEFAULTS, FLOW_FIELD_DEFAULTS, FRECHET_DEFAULTS, FRONT_OBJECTIVES, GAP_BASELINES, GAP_BOOTSTRAP, GAP_CAPS, GAP_DEFAULTS, GAP_RULES, GAP_VIEWS, GROUP_SCOPES, GROUP_SPINES, GROWTH_DEFAULTS, GROWTH_SWEEP, Grid, HDBSCAN_DEFAULTS, HIERARCHY_DEFAULTS, HIERARCHY_GROUPS, HIGHWAYS_ENGINE, HIGHWAYS_FAMILIES, HIGHWAY_BASES, HIGHWAY_BASE_ROUTERS, HOUGH_DEFAULTS, HUB_ALLOCATIONS, HUB_ARC_DEFAULTS, HUB_CANDIDATES, HUB_CHAIN, HYSTERESIS_DEFAULTS, INTERCHANGE_BASES, INTERCHANGE_DEFAULTS, KDEEB_DEFAULTS, KD_DEFAULTS, KENDALL_EXACT, LANES_DEFAULTS, LANE_ORDERS, LANE_STRETCH_DEFAULTS, LANE_STRETCH_ORDERS, LANE_TRACKS, LEVEL_HIGHWAY_DEFAULTS, LINKAGES, LINK_COMMUNITY_DEFAULTS, MANY_GROUPS, MEASURES, MEASURE_LANE_WIDTH, MEET_DEFAULTS, MEMBER_FORMS, MESH_DEFAULTS, MINGLE_DEFAULTS, MIN_EFFECTS, MLSEB_DEFAULTS, MLS_FITS, MODULE_RULES, MOVE_NAMES, NESTED_DEFAULTS, OD_CELL_DEFAULTS, OUTSIDE_SLACK, Obstacles, PACKED_GRID, PICK, PLAN_CHANGERS, PORTFOLIO_B1, PORTFOLIO_BANDS, PORTFOLIO_BASE_MEMBER, PORTFOLIO_CAPS, PORTFOLIO_FIT, PORTFOLIO_HASHES, PORTFOLIO_KS_API, PORTFOLIO_RULES, PORTFOLIO_SETS, POWER_DEFAULTS, PROPOSED_RULES, P_HUB_DEFAULTS, QUICKBUNDLES_DEFAULTS, RECORD_EPS, RESISTANCE_DEFAULTS, RIDGE_DEFAULTS, ROAD_CLASS_DEFAULTS, ROOM_FAMILIES, ROUTED_STEPS, ROUTERS, SAME_HIGHWAY, SBEB_DEFAULTS, SCALE_SPACE_DEFAULTS, SCREEN_FAMILY_BY, SCREEN_RULES, SCREEN_SETS, SCREEN_STEPS, SCREEN_V1, SCREEN_V2, SCREEN_V2_HELD_OUT, SCREEN_WRAPS, SEARCH_DEFAULTS, SEARCH_MOVES, SECTION_DEFAULTS, SELECT_DEFAULTS, SIDE_NORMAL, SKELETON_GROUPS, SKELETON_TARGETS, SLACK_SWEEP, SMALL_CLUSTERED, SOFT_KEY_DEFAULTS, SPACING_LIMIT, SPECTRAL_DEFAULTS, SPIRAL_DEFAULTS, STABLE_ID_DEFAULTS, STEINER_DEFAULTS, STEINER_GROUPS, STEINER_METHODS, STEINER_METRICS, STEPS, STRATEGIES, STREET_DEFAULTS, STREET_SOURCES, STRETCH_DEFAULTS, STRETCH_SWEEP, STUB_DEFAULTS, SUITE_COLUMNS, SoftGraph, SplitTree, TERRITORY_DEFAULTS, TERRITORY_POINTS, TINY_UNIFORM, TOLERANCE, TRACLUS_DEFAULTS, TRAFFIC_RULES, WEISZFELD_DEFAULTS, WSPD_DEFAULTS, WayFinder, ZIPPER_DEFAULTS, activationBase, activationReport, adjustedRandIndex, allSections, ambiguity, ambiguityGate, ambiguityPlan, arcCurve, areaPlan, asRouter, attractionRouter, attractionStrategy, backtrackOf, balance, basePlan, benchBendRoom, benchCurveStrategies, benchCurves, benchHighways, benchLevelRoom, benchOne, benchRoom, benchRun, benchTracks, bendCount, bendingEnergy, bicliqueGroups, bigGroups, boardGaps, boardUnit, boardWins, bootstrapMedian, boundsFamilyName, boxExit, boxPush, boxUnit, boxesOverlap, breakEvenGate, breakEvenPlan, buildSections, bundleStretch, bundledShare, byCollection, byDataGroup, candidateArcs, candidatePlan, candidateScore, canonicalJson, capSettings, caseSeed, cellSteps, center, chainClusters, changeGraph, changedDimensions, channelOverflows, channelWidth, characteristicPoints, checkFamilies, checkLevels, checkOptions, checkPlan, checkPort, checkProperties, checkSchema, checkSections, clampOptions, clothoidCurve, clusters1d, coAssociation, commonEdges, compareAnyStep, compareMeasure, compareSpec, compareStep, compatibility, compatibilityMatrix, confluentPlan, consensusGroups, consensusLabels, consensusPlan, controlChain, cornersRouter, corpusManifest, corridorPlan, countCrossings, countOverlaps, createFamilyRegistry, createRegistry, crossbarPlan, crossesItself, crossingAngle, crossingAngles, cubicAt, cubuPlan, curveFaults, curveJoints, curveOf, curveRuns, curveSpace, curveStrategies, curvesHash, cutGroups, cutShare, declaresFamily, defineStrategy, denseGroupsBoard, densityPlan, densityRidges, depthLevels, detourSpread, diffusionPlan, dimensionAt, dimensionUnit, dist, distinctPoints, divideHighway, dividedPlan, documentHash, dominates, drawnSegments, edgeBlocks, edgeEnds, edgeLengths, edgePathPlan, edgePaths, edgePlugs, edgeResistance, enumDimension, exactBoard, exactFamily, exactGenerator, exactSeed, exactStretch, exactStretches, exercised, facingEnds, familyCell, familyKind, familyOf, familySeed, farLevel, farLinks, fdebLines, fdebPlan, fft, findStrategy, finderOf, firstLines, fixedPortPoint, fixtureGroupOptions, fixtureParams, fixtureRngSeed, fixtureSpecOf, flowClusterPlan, flowClusters, flowDistance, flowFieldPlan, fnv1a32, follow, formatActivation, formatBendRoom, formatCapped, formatCurves, formatFaults, formatFronts, formatGap, formatHighways, formatInput, formatLevelRoom, formatLevels, formatMeasureTime, formatPaired, formatRoom, formatRules, formatScreen, formatSections, formatSummaries, formatTable, formatTracks, frechet, frechetBundles, frechetPlan, freeLines, g1Defect, g2Defect, gapBaselines, gapCap, gapClosed, gapLaneCounts, gapLanes, gapNames, gapPlan, gapValues, gateStrategy, getStrategy, goesOutAndBack, graphInput, greedyBicliques, greedyCrossbars, groupEdges, groupRun, groupSettings, groupStability, groupStrategies, groupedPlan, growthPlan, growthSettings, guardBreaks, hasCusp, hdbscanClusters, hdbscanLabels, hdbscanPlan, hierarchyPlan, highwayChurn, highwayCurve, highwayCurves, highwayEdits, highwayIdKeep, highwayMoves, highwayRouter, highwayRouterStrategy, highwayRouters, highwayStrategies, highwaysRegistry, houghLines, houghPeaks, houghPlan, hubArcPlan, hubCandidates, hubChainBoard, hysteresisGate, hysteresisPlan, inflate, inflectionCount, inflectionCurve, inflectionSector, inkSaved, innerPoints, inputGraph, inputId, interchangeCandidates, interchangePlan, isRoutedStep, itemSizes, jaccard, jaccardIndex, jogPeaks, joinReports, jsonSafe, kMeans, kdGroups, kdeebLines, kdeebPlan, keepMembers, kendallTau, keyGroups, keyedSections, keysPlan, labelGroups, laneArea, laneCrossings, laneFidelity, laneOrder, laneRoutes, laneSeparation, laneSettings, laneStretches, lanesPlan, lanesThrough, leafGroups, lengthBetween, levelCut, levelGrouping, levelHighwayPlan, levelMeasures, levelPairs, levelRoom, levelStrategies, levelsOf, lineDensity, linePoint, linkAgreement, linkCommunities, linkCommunityGroups, linkCommunityPlan, linkDensity, listOptions, listStrategies, makeFixture, makeRoom, manifestHash, manyGroupsBoard, matchHighways, maxStretch, mdf, meanLine, measureBoard, measureByKey, measureByName, measureHighways, measureJogs, measureLevel, measureLevelFaults, measureLinks, measureName, measureQuality, measureRouteFaults, measureStability, meetMember, meetPlan, meetingPoints, memberAt, memberStrategy, mergeAngle, meshPlan, meshWaypoints, minEffectOf, minHash, mingleBundles, minglePlan, mixedSegments, mixesCollections, mlsPoint, mlsebLines, mlsebPlan, modularity, modulesOf, mulberry32, namedObjectives, nearestCentre, nearestOnBoundary, nearestOnLine, nearestSide, nearestSkeleton, nestedGridBoard, nestedGroups, nestedPlan, nestingFaults, nestingViolations, nextRow, nodeAmbiguity, nodeIndex, nodeLinks, numberDimension, numberOf, occupiedArea, odCellPlan, odGroups, offsetLine, offsetPolyline, oneSteiner, openGaps, openLanes, optimaFile, optimumKey, optionDefaults, orderChanges, orderFragility, orderLanes, orderRows, orderStability, orderSuiteRows, orthogonality, outOfBoxes, outputGroups, outputHash, pHubPlan, pMedian, packedGridBoard, paramProblem, paretoFront, parseLines, partWithin, passSettings, pathPoints, peakCurvature, pickLevel, pickMoves, placePlug, planMoves, planOfResult, plugSpacing, plugStrategies, plugsRouter, pointAt, pointBoxDistance, pointDensity, pointGroups, pointOnSide, pointSegmentDistance, polylineLength, portOffset, portfolio, portfolioB1Spec, portfolioBand, portfolioFacts, portfolioKey, portfolioMember, portfolioMembers, portfolioName, portfolioRouter, portfolioStrategy, positionStability, powerPlan, pullApart, quadtreeGroups, quantile, quickBundles, quickBundlesPlan, randomInt, rayToBox, recordLines, recordMoves, rectScattered, regionBounds, register, repairResult, repairStrategy, representative, resample, resistanceClusters, resistancePlan, resolvePorts, resultForm, resultHash, ridgeLines, ridgePlan, rng, roadClassPlan, roomDemand, roomLinks, roomOrderFaults, roomStrategies, roomValues, round3, roundCorners, roundedCurve, routeCrossings, routeInStableOrder, routeMeasures, routePortfolio, routeSeparation, routeStability, routerStrategies, routersOf, ruleCounts, runBenchmark, runKey, runPipeline, runProfile, runSuite, sameData, sampleCurve, samplesOf, sbebLines, sbebPlan, scaleClusters, scaleSpaceGroups, scaleSpacePlan, screen, screenInputs, screenSplit, searchPlan, searchScore, sectionsRouter, segmentBounds, segmentBoxDistance, segmentClusters, segmentDistance, segmentHitsBox, segmentHitsRect, segmentKey, segmentsCross, segmentsOf, selectBest, selectPlan, setManifest, sha256Hex, shapeSkeleton, sharedRouter, sharedStretchStrategy, sharedStretches, sharedWaysAll, sharedWaysPlan, shuffleEdges, sideAngle, sideToward, skeletonGroups, skeletonPath, slackSettings, smoothCurve, snapPlan, softDistance, softKeyClusters, softKeyPlan, spanningTree, spectralClusters, spectralLabels, spectralPlan, spinePart, spiralJoin, spiralPlan, spiralTree, splineCurve, splitPath, splitPoint, stableIdGate, stableIdPlan, stableOrder, stableOrderStrategy, steinerPlan, stepRouterStrategy, stepRouters, stepSeed, straightCurve, straightRouter, strategyKey, streetEdges, streetPlan, streetPoint, streetRide, stretchGate, stretchLaneOrders, stretchPlan, stretchSettings, stubPlan, suiteFronts, sumFaults, summarize, summarizeGap, summarizeRows, sweepGrid, sweepOf, sweepRoom, sweepSections, sweepValues, symmetricEigen, territoryPlan, toSvgPath, totalTurn, tracePlan, traclusPlan, treeCost, treeDepth, trunksRouter, turns, unwrappedName, utf8, waypointWay, weberPoint, weiszfeldPlan, wholeStrategies, withBends, withRepair, withStableOrder, wspdGroups, wspdPlan, zipLegs, zipperPlan };
30383
31966
 
30384
31967
  //# sourceMappingURL=index.js.map