roadgraph-router-v2 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,6 +80,365 @@ var VarintReader = class {
80
80
  }
81
81
  };
82
82
 
83
+ // src/speed.ts
84
+ function parseMaxSpeedKph(value) {
85
+ if (!value) return null;
86
+ const text = value.trim().toLowerCase();
87
+ if (text === "walk") return 7;
88
+ const match = /^(\d+(?:\.\d+)?)\s*(mph|km\/h|kmh|kph|knots)?$/.exec(text);
89
+ if (!match) return null;
90
+ const amount = Number(match[1]);
91
+ if (!Number.isFinite(amount) || amount <= 0) return null;
92
+ if (match[2] === "mph") return amount * 1.609344;
93
+ if (match[2] === "knots") return amount * 1.852;
94
+ return amount;
95
+ }
96
+
97
+ // src/conditional.ts
98
+ var MAX_STORED_VALUES = 65536;
99
+ function answered(circumstances) {
100
+ const yes = /* @__PURE__ */ new Set();
101
+ for (const list of [circumstances?.conditions, circumstances?.userGroups, circumstances?.purposes]) {
102
+ for (const name of list ?? []) yes.add(name.trim().toLowerCase());
103
+ }
104
+ return yes;
105
+ }
106
+ var KNOWN_CIRCUMSTANCES = /* @__PURE__ */ new Set([
107
+ "wet",
108
+ "dry",
109
+ "snow",
110
+ "ice",
111
+ "fog",
112
+ "rain",
113
+ "disabled",
114
+ "resident",
115
+ "residents",
116
+ "customers",
117
+ "employees",
118
+ "emergency",
119
+ "permit",
120
+ "school",
121
+ "delivery",
122
+ "destination",
123
+ "agricultural",
124
+ "forestry",
125
+ "loading",
126
+ "unloading"
127
+ ]);
128
+ var WEEKDAYS = ["mo", "tu", "we", "th", "fr", "sa", "su"];
129
+ var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
130
+ function splitOutsideBrackets(text, separator) {
131
+ const parts = [];
132
+ let depth = 0;
133
+ let start = 0;
134
+ for (let index = 0; index < text.length; index += 1) {
135
+ const character = text[index];
136
+ if (character === "(") depth += 1;
137
+ else if (character === ")") depth = Math.max(0, depth - 1);
138
+ else if (character === separator && depth === 0) {
139
+ parts.push(text.slice(start, index));
140
+ start = index + 1;
141
+ }
142
+ }
143
+ parts.push(text.slice(start));
144
+ return parts.map((part) => part.trim()).filter(Boolean);
145
+ }
146
+ function parseClock(text) {
147
+ const match = /^(\d{1,2}):(\d{2})$/.exec(text.trim());
148
+ if (!match) return null;
149
+ const hours = Number(match[1]);
150
+ const minutes = Number(match[2]);
151
+ if (hours > 24 || minutes > 59) return null;
152
+ if (hours === 24 && minutes !== 0) return null;
153
+ return hours * 60 + minutes;
154
+ }
155
+ function parseIndexedRange(text, table) {
156
+ const [from, to] = text.split("-").map((part) => part.trim().toLowerCase().slice(0, 3));
157
+ const start = table.indexOf(from);
158
+ if (start < 0) return null;
159
+ if (to === void 0) return { from: start, to: start };
160
+ const end = table.indexOf(to);
161
+ return end < 0 ? null : { from: start, to: end };
162
+ }
163
+ function parseVehicleComparison(text) {
164
+ const match = /^(weight|height|width|length)\s*(<=|>=|<|>|=)\s*(\d+(?:\.\d+)?)$/.exec(text.trim().toLowerCase());
165
+ if (!match) return null;
166
+ return { property: match[1], operator: match[2], amount: Number(match[3]) };
167
+ }
168
+ function parseCondition(text) {
169
+ const condition = {};
170
+ const cleaned = text.trim().replace(/^\(|\)$/g, "").trim();
171
+ if (!cleaned) return { unreadable: text };
172
+ if (cleaned.toLowerCase() === "24/7") return { times: [{ from: 0, to: 1440 }] };
173
+ for (const token of cleaned.split(/\s+(?:and|AND)\s+|\s+/).filter(Boolean)) {
174
+ const lower = token.toLowerCase().replace(/,$/, "");
175
+ if (lower === "ph") {
176
+ condition.publicHoliday = true;
177
+ continue;
178
+ }
179
+ const comparison = parseVehicleComparison(lower);
180
+ if (comparison) {
181
+ (condition.vehicle ??= []).push(comparison);
182
+ continue;
183
+ }
184
+ const pieces = lower.split(",").filter(Boolean);
185
+ const spans = [];
186
+ const days = [];
187
+ const months = [];
188
+ let understood = true;
189
+ for (const piece of pieces) {
190
+ const [left, right] = piece.split("-");
191
+ const from = parseClock(left ?? "");
192
+ if (from !== null && right !== void 0) {
193
+ const to = parseClock(right);
194
+ if (to === null) {
195
+ understood = false;
196
+ break;
197
+ }
198
+ spans.push({ from, to });
199
+ continue;
200
+ }
201
+ const day = parseIndexedRange(piece, WEEKDAYS);
202
+ if (day) {
203
+ days.push(day);
204
+ continue;
205
+ }
206
+ const month = parseIndexedRange(piece, MONTHS);
207
+ if (month) {
208
+ months.push(month);
209
+ continue;
210
+ }
211
+ if (KNOWN_CIRCUMSTANCES.has(piece)) {
212
+ (condition.circumstances ??= []).push(piece);
213
+ continue;
214
+ }
215
+ understood = false;
216
+ break;
217
+ }
218
+ if (!understood) {
219
+ condition.unreadable = token;
220
+ continue;
221
+ }
222
+ if (spans.length) (condition.times ??= []).push(...spans);
223
+ if (days.length) (condition.days ??= []).push(...days);
224
+ if (months.length) (condition.months ??= []).push(...months);
225
+ }
226
+ return condition;
227
+ }
228
+ function parseConditional(value) {
229
+ if (!value || !value.includes("@")) return [];
230
+ const rules = [];
231
+ for (const part of splitOutsideBrackets(value, ";")) {
232
+ const at = part.indexOf("@");
233
+ if (at < 0) continue;
234
+ const left = part.slice(0, at).trim();
235
+ const right = part.slice(at + 1).trim();
236
+ if (!left || !right) continue;
237
+ const grouped = right.startsWith("(") && right.endsWith(")") ? right.slice(1, -1) : right;
238
+ const parts = splitOutsideBrackets(grouped, ";");
239
+ rules.push({ value: left, conditions: (parts.length ? parts : [grouped]).map(parseCondition) });
240
+ }
241
+ return rules;
242
+ }
243
+ var withinRange = (value, range) => range.from <= range.to ? value >= range.from && value <= range.to : value >= range.from || value <= range.to;
244
+ var withinSpan = (minutes, span) => span.from <= span.to ? minutes >= span.from && minutes < span.to : minutes >= span.from || minutes < span.to;
245
+ var YESTERDAY = (weekday) => (weekday + 6) % 7;
246
+ function dayBefore(when) {
247
+ const rolledOver = when.dayOfMonth === 1;
248
+ return {
249
+ weekday: YESTERDAY(when.weekday),
250
+ month: rolledOver ? when.month === 1 ? 12 : when.month - 1 : when.month
251
+ };
252
+ }
253
+ function selects(condition, day) {
254
+ if (condition.days?.length && !condition.days.some((range) => withinRange(day.weekday, range))) return false;
255
+ if (condition.months?.length && !condition.months.some((range) => withinRange(day.month - 1, range))) return false;
256
+ return true;
257
+ }
258
+ function dayAndTimeHold(condition, when) {
259
+ if (!condition.times?.length) return selects(condition, when);
260
+ return condition.times.some((span) => {
261
+ if (span.from <= span.to) return selects(condition, when) && withinSpan(when.minutes, span);
262
+ if (when.minutes >= span.from) return selects(condition, when);
263
+ if (when.minutes < span.to) return selects(condition, dayBefore(when));
264
+ return false;
265
+ });
266
+ }
267
+ function conditionHolds(condition, when, vehicle, circumstances) {
268
+ if (condition.unreadable !== void 0) return false;
269
+ if (condition.publicHoliday && !when.publicHoliday) return false;
270
+ if (condition.circumstances?.length) {
271
+ const yes = answered(circumstances);
272
+ if (!condition.circumstances.every((name) => yes.has(name))) return false;
273
+ }
274
+ if (!dayAndTimeHold(condition, when)) return false;
275
+ if (condition.vehicle?.length) {
276
+ if (!vehicle) return false;
277
+ for (const test of condition.vehicle) {
278
+ const measured = test.property === "weight" ? vehicle.weightTonnes : test.property === "height" ? vehicle.heightMeters : test.property === "width" ? vehicle.widthMeters : vehicle.lengthMeters;
279
+ const holds = test.operator === "<" ? measured < test.amount : test.operator === "<=" ? measured <= test.amount : test.operator === ">" ? measured > test.amount : test.operator === ">=" ? measured >= test.amount : measured === test.amount;
280
+ if (!holds) return false;
281
+ }
282
+ }
283
+ return true;
284
+ }
285
+ function conditionalValue(rules, when, vehicle, circumstances) {
286
+ let effective = null;
287
+ for (const rule of rules) {
288
+ if (rule.conditions.some((condition) => conditionHolds(condition, when, vehicle, circumstances))) effective = rule.value;
289
+ }
290
+ return effective;
291
+ }
292
+ function unreadableConditions(rules) {
293
+ return rules.reduce((count, rule) => count + rule.conditions.filter((condition) => condition.unreadable !== void 0).length, 0);
294
+ }
295
+ var WEEKDAY_NUMBER = { Mon: 0, Tue: 1, Wed: 2, Thu: 3, Fri: 4, Sat: 5, Sun: 6 };
296
+ function travelTimeFrom(date, publicHoliday = false, timeZone) {
297
+ if (!timeZone) {
298
+ return {
299
+ weekday: (date.getDay() + 6) % 7,
300
+ minutes: date.getHours() * 60 + date.getMinutes(),
301
+ month: date.getMonth() + 1,
302
+ dayOfMonth: date.getDate(),
303
+ publicHoliday
304
+ };
305
+ }
306
+ const parts = new Intl.DateTimeFormat("en-GB", {
307
+ timeZone,
308
+ weekday: "short",
309
+ year: "numeric",
310
+ month: "numeric",
311
+ day: "numeric",
312
+ hour: "numeric",
313
+ minute: "numeric",
314
+ hour12: false
315
+ }).formatToParts(date);
316
+ const field = (type) => parts.find((part) => part.type === type)?.value ?? "";
317
+ const hour = Number(field("hour")) % 24;
318
+ return {
319
+ weekday: WEEKDAY_NUMBER[field("weekday")] ?? 0,
320
+ minutes: hour * 60 + Number(field("minute")),
321
+ month: Number(field("month")),
322
+ dayOfMonth: Number(field("day")),
323
+ publicHoliday
324
+ };
325
+ }
326
+ var ConditionKind = { access: 0, maxspeed: 1 };
327
+ function packEdgeConditions(entries) {
328
+ const rows = entries.filter((entry) => parseConditional(entry.value).length > 0).sort((a, b) => a.edge - b.edge || a.kind - b.kind);
329
+ if (!rows.length) return void 0;
330
+ const index = /* @__PURE__ */ new Map();
331
+ const values = [];
332
+ const kept = [];
333
+ for (const row of rows) {
334
+ let at = index.get(row.value);
335
+ if (at === void 0) {
336
+ if (values.length >= MAX_STORED_VALUES) {
337
+ throw new RangeError(
338
+ `A tile holds more than ${MAX_STORED_VALUES} distinct conditional restrictions, which is more than the stored index can name. Compile the region in smaller tiles with --tile-size-km.`
339
+ );
340
+ }
341
+ at = values.push(row.value) - 1;
342
+ index.set(row.value, at);
343
+ }
344
+ kept.push({ edge: row.edge, kind: row.kind, rule: at });
345
+ }
346
+ if (!kept.length) return void 0;
347
+ return {
348
+ edges: Uint32Array.from(kept, (row) => row.edge),
349
+ kinds: Uint8Array.from(kept, (row) => row.kind),
350
+ rules: Uint16Array.from(kept, (row) => row.rule),
351
+ values
352
+ };
353
+ }
354
+ function parseEdgeConditions(table) {
355
+ if (!table?.edges.length) return void 0;
356
+ return { table, parsed: table.values.map(parseConditional) };
357
+ }
358
+ function firstRowFor(edges, edge) {
359
+ let low = 0;
360
+ let high = edges.length - 1;
361
+ let found = -1;
362
+ while (low <= high) {
363
+ const middle = low + high >> 1;
364
+ if (edges[middle] >= edge) {
365
+ if (edges[middle] === edge) found = middle;
366
+ high = middle - 1;
367
+ } else low = middle + 1;
368
+ }
369
+ return found;
370
+ }
371
+ var CLOSED = /* @__PURE__ */ new Set(["no", "private", "destination", "customers", "delivery", "permit", "agricultural", "forestry"]);
372
+ function conditionsOnEdge(conditions, edge, when, vehicle, circumstances) {
373
+ const { table, parsed } = conditions;
374
+ let closed = false;
375
+ let speedKph;
376
+ for (let row = firstRowFor(table.edges, edge); row >= 0 && row < table.edges.length && table.edges[row] === edge; row += 1) {
377
+ const value = conditionalValue(parsed[table.rules[row]], when, vehicle, circumstances);
378
+ if (value === null) continue;
379
+ if (table.kinds[row] === ConditionKind.access) {
380
+ if (CLOSED.has(value.trim().toLowerCase())) closed = true;
381
+ } else {
382
+ const parsedSpeed = parseMaxSpeedKph(value);
383
+ if (parsedSpeed !== null && parsedSpeed > 0) speedKph = speedKph === void 0 ? parsedSpeed : Math.min(speedKph, parsedSpeed);
384
+ }
385
+ }
386
+ return { closed, speedKph };
387
+ }
388
+ function accessAtTime(graph, conditions, when, vehicle, base, circumstances) {
389
+ const start = base ?? graph.edgeAccess;
390
+ if (!when || !conditions) return start;
391
+ const edges = graph.edgeTargets.length;
392
+ let mask;
393
+ const { table } = conditions;
394
+ for (let row = 0; row < table.edges.length; row += 1) {
395
+ const edge = table.edges[row];
396
+ if (edge >= edges || table.kinds[row] !== ConditionKind.access) continue;
397
+ if (mask && !edgeAllowed(mask, edge)) continue;
398
+ if (!conditionsOnEdge(conditions, edge, when, vehicle, circumstances).closed) continue;
399
+ mask ??= start ? Uint8Array.from(start) : accessMaskFor(edges, () => true);
400
+ mask[edge >> 3] &= ~(1 << (edge & 7));
401
+ }
402
+ return mask ?? start;
403
+ }
404
+ function countClosedAt(graph, conditions, when, vehicle) {
405
+ const before = graph.edgeAccess;
406
+ const after = accessAtTime(graph, conditions, when, vehicle);
407
+ if (after === before) return 0;
408
+ let closed = 0;
409
+ for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) {
410
+ if (edgeAllowed(before, edge) && !edgeAllowed(after, edge)) closed += 1;
411
+ }
412
+ return closed;
413
+ }
414
+ var parsedByTable = /* @__PURE__ */ new WeakMap();
415
+ function conditionsOf(graph) {
416
+ const table = graph.edgeConditions;
417
+ if (!table?.edges.length) return void 0;
418
+ const existing = parsedByTable.get(table);
419
+ if (existing) return existing;
420
+ const parsed = parseEdgeConditions(table);
421
+ parsedByTable.set(table, parsed);
422
+ return parsed;
423
+ }
424
+ function conditionalDurations(graph, conditions, when, vehicle, circumstances) {
425
+ if (!conditions) return void 0;
426
+ const distanceScale = graph.edgeDistanceScale ?? 1;
427
+ const durationScale = graph.edgeDurationScale ?? 1;
428
+ const slowed = /* @__PURE__ */ new Map();
429
+ const { table } = conditions;
430
+ for (let row = 0; row < table.edges.length; row += 1) {
431
+ const edge = table.edges[row];
432
+ if (table.kinds[row] !== ConditionKind.maxspeed || edge >= graph.edgeTargets.length || slowed.has(edge)) continue;
433
+ const { speedKph } = conditionsOnEdge(conditions, edge, when, vehicle, circumstances);
434
+ if (speedKph === void 0) continue;
435
+ const stored = graph.edgeDurationsSeconds[edge] * durationScale;
436
+ const limited = graph.edgeDistancesMeters[edge] * distanceScale / (speedKph / 3.6);
437
+ if (limited > stored) slowed.set(edge, limited);
438
+ }
439
+ return slowed.size ? slowed : void 0;
440
+ }
441
+
83
442
  // src/routing-profile.ts
84
443
  var ProfileId = {
85
444
  unspecified: 0,
@@ -164,18 +523,6 @@ function clampSpeed(value) {
164
523
  if (!Number.isFinite(value)) return MINIMUM_SPEED_KPH;
165
524
  return Math.min(MAXIMUM_SPEED_KPH, Math.max(MINIMUM_SPEED_KPH, value));
166
525
  }
167
- function parseMaxSpeedKph(value) {
168
- if (!value) return null;
169
- const text = value.trim().toLowerCase();
170
- if (text === "walk") return 7;
171
- const match = /^(\d+(?:\.\d+)?)\s*(mph|km\/h|kmh|kph|knots)?$/.exec(text);
172
- if (!match) return null;
173
- const amount = Number(match[1]);
174
- if (!Number.isFinite(amount) || amount <= 0) return null;
175
- if (match[2] === "mph") return amount * 1.609344;
176
- if (match[2] === "knots") return amount * 1.852;
177
- return amount;
178
- }
179
526
  var UNMEASURED = /* @__PURE__ */ new Set(["default", "below_default", "none", "unsigned", "no_sign", "no", "yes", "unknown"]);
180
527
  var FEET_METERS = 0.3048;
181
528
  var INCH_METERS = 0.0254;
@@ -226,6 +573,22 @@ function limitsFor(tags, mode, direction) {
226
573
  }
227
574
  return found ? limits : void 0;
228
575
  }
576
+ function conditionsFor(tags, accessKeys, speedKeys) {
577
+ const found = [];
578
+ let access;
579
+ for (const key of accessKeys) {
580
+ const value = tags[`${key}:conditional`];
581
+ if (value !== void 0) access = value;
582
+ }
583
+ if (access) found.push({ kind: ConditionKind.access, value: access });
584
+ let speed;
585
+ for (const key of speedKeys) {
586
+ const value = tags[`${key}:conditional`];
587
+ if (value !== void 0) speed = value;
588
+ }
589
+ if (speed) found.push({ kind: ConditionKind.maxspeed, value: speed });
590
+ return found.length ? found : void 0;
591
+ }
229
592
  function accessAllowed(tags, keys) {
230
593
  let allowed = true;
231
594
  for (const key of keys) {
@@ -268,9 +631,14 @@ function motorVehicleProfile(options) {
268
631
  `maxspeed:${mode}`,
269
632
  ...accessKeys,
270
633
  ...onewayKeys,
634
+ // The conditional forms of the same keys, which the router reads against
635
+ // a departure time rather than the compiler against nothing.
636
+ ...accessKeys.map((key) => `${key}:conditional`),
637
+ "maxspeed:conditional",
638
+ `maxspeed:${mode}:conditional`,
271
639
  ...options.physicalLimits ? LIMIT_KEYS.flatMap(([, key]) => [key, `${key}:${mode}`, `${key}:forward`, `${key}:backward`, ...key === "maxheight" || key === "maxwidth" ? [`${key}:physical`] : []]) : []
272
640
  ],
273
- relationTagKeys: ["type", "except", ...restrictionKeys],
641
+ relationTagKeys: ["type", "except", ...restrictionKeys, ...restrictionKeys.map((key) => `${key}:conditional`)],
274
642
  maxSpeedKph: ceiling,
275
643
  nodeTagKeys: BARRIER_TAG_KEYS,
276
644
  evaluateBarrier: (tags) => barrierAllows(tags, accessKeys, "motor"),
@@ -293,7 +661,8 @@ function motorVehicleProfile(options) {
293
661
  ...options.physicalLimits ? {
294
662
  forwardLimits: limitsFor(tags, mode, "forward"),
295
663
  backwardLimits: limitsFor(tags, mode, "backward")
296
- } : {}
664
+ } : {},
665
+ .../* @__PURE__ */ ((conditions) => conditions ? { conditions } : {})(conditionsFor(tags, accessKeys, ["maxspeed", `maxspeed:${mode}`]))
297
666
  };
298
667
  },
299
668
  evaluateTurnRestriction(tags) {
@@ -369,8 +738,16 @@ function activeTravelProfile(options) {
369
738
  return {
370
739
  name,
371
740
  id,
372
- wayTagKeys: ["highway", "junction", "maxspeed", ...accessKeys, "oneway", ...onewayKeys],
373
- relationTagKeys: ["type", "except", ...restrictionKeys],
741
+ wayTagKeys: [
742
+ "highway",
743
+ "junction",
744
+ "maxspeed",
745
+ ...accessKeys,
746
+ "oneway",
747
+ ...onewayKeys,
748
+ ...accessKeys.map((key) => `${key}:conditional`)
749
+ ],
750
+ relationTagKeys: ["type", "except", ...restrictionKeys, ...restrictionKeys.map((key) => `${key}:conditional`)],
374
751
  maxSpeedKph: ceilingKph,
375
752
  nodeTagKeys: BARRIER_TAG_KEYS,
376
753
  evaluateBarrier: (tags) => barrierAllows(tags, accessKeys, mode === "foot" ? "foot" : "bicycle"),
@@ -391,7 +768,8 @@ function activeTravelProfile(options) {
391
768
  const posted = parseMaxSpeedKph(tags.maxspeed);
392
769
  const wanted = Math.min(speeds[highway], posted !== null && posted > 0 ? posted : Infinity);
393
770
  const speed = Math.min(ceilingKph, Math.max(floorKph, wanted));
394
- return { forward, backward, forwardSpeedKph: speed, backwardSpeedKph: speed };
771
+ const conditions = conditionsFor(tags, accessKeys, []);
772
+ return { forward, backward, forwardSpeedKph: speed, backwardSpeedKph: speed, ...conditions ? { conditions } : {} };
395
773
  },
396
774
  evaluateTurnRestriction(tags) {
397
775
  if (tags.type !== "restriction") return null;
@@ -685,6 +1063,32 @@ function buildShapeTable(graph) {
685
1063
  function encodeSharedGraph(graph) {
686
1064
  return encodePackedGraph(graph, SPLIT_COMPACT_VERSION);
687
1065
  }
1066
+ function assertEdgeConditions(conditions, edges) {
1067
+ const rows = conditions.edges.length;
1068
+ if (conditions.kinds.length !== rows || conditions.rules.length !== rows) throw new Error("Edge condition columns must all be the same length.");
1069
+ for (let index = 0; index < rows; index += 1) {
1070
+ if (conditions.edges[index] >= edges) throw new Error("Edge condition references an edge outside the graph.");
1071
+ if (index && conditions.edges[index] < conditions.edges[index - 1]) throw new Error("Edge conditions must be sorted by edge.");
1072
+ if (conditions.rules[index] >= conditions.values.length) throw new Error("Edge condition names a value that is not in the table.");
1073
+ }
1074
+ if (conditions.values.some((value) => value.includes("\0"))) throw new Error("A conditional value cannot contain a null byte.");
1075
+ }
1076
+ function readEdgeConditions(buffer, at, edges) {
1077
+ const view = new DataView(buffer);
1078
+ const rows = view.getUint32(at, true);
1079
+ const textBytes = view.getUint32(at + 4, true);
1080
+ const conditionEdges = new Uint32Array(buffer.slice(at + 8, at + 8 + rows * 4));
1081
+ for (let index = 0; index < rows; index += 1) {
1082
+ if (conditionEdges[index] >= edges) throw new Error("Invalid routing graph: condition references an edge outside the graph.");
1083
+ if (index && conditionEdges[index] < conditionEdges[index - 1]) throw new Error("Invalid routing graph: conditions are not sorted.");
1084
+ }
1085
+ const values = new TextDecoder().decode(new Uint8Array(buffer, alignUp(at + 8 + rows * 7), textBytes)).split("\0");
1086
+ const rules = new Uint16Array(buffer.slice(at + 8 + rows * 4, at + 8 + rows * 6));
1087
+ for (let index = 0; index < rows; index += 1) {
1088
+ if (rules[index] >= values.length) throw new Error("Invalid routing graph: condition names a value that is not in the table.");
1089
+ }
1090
+ return { edges: conditionEdges, rules, kinds: new Uint8Array(buffer.slice(at + 8 + rows * 6, at + 8 + rows * 7)), values };
1091
+ }
688
1092
  function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
689
1093
  const nodes = graph.nodeLatitudes.length;
690
1094
  const edges = graph.edgeTargets.length;
@@ -741,7 +1145,14 @@ function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
741
1145
  const variableBytes = sections.reduce((sum, section) => sum + section.byteLength, 0);
742
1146
  const weightBytes = edges * (shared ? 2 : 4);
743
1147
  const restrictionsAt = alignUp(PACKED_HEADER_BYTES + (shared ? 0 : nodes * 8) + weightBytes + variableBytes);
744
- const buffer = new ArrayBuffer(restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0));
1148
+ const restrictionsEnd = restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0);
1149
+ const conditions = !shared && graph.edgeConditions?.edges.length ? graph.edgeConditions : void 0;
1150
+ if (conditions) assertEdgeConditions(conditions, edges);
1151
+ const conditionRows = conditions?.edges.length ?? 0;
1152
+ const conditionText = conditions ? new TextEncoder().encode(conditions.values.join("\0")) : void 0;
1153
+ const conditionsAt = alignUp(restrictionsEnd);
1154
+ const conditionTextAt = alignUp(conditionsAt + 8 + conditionRows * 7);
1155
+ const buffer = new ArrayBuffer(conditionRows ? conditionTextAt + (conditionText?.byteLength ?? 0) : restrictionsEnd);
745
1156
  const view = new DataView(buffer);
746
1157
  view.setUint32(0, COMPACT_MAGIC, true);
747
1158
  view.setUint16(4, version, true);
@@ -758,6 +1169,7 @@ function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
758
1169
  for (let index = 0; index < sections.length; index += 1) view.setUint32(40 + index * 4, sections[index].byteLength, true);
759
1170
  if (!shared) {
760
1171
  view.setUint8(60, graph.profileId ?? 0);
1172
+ view.setUint8(61, conditionRows ? 1 : 0);
761
1173
  view.setUint16(62, Math.round(graph.maxSpeedKph ?? 0), true);
762
1174
  }
763
1175
  let offset = PACKED_HEADER_BYTES;
@@ -790,6 +1202,14 @@ function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
790
1202
  offset += (runs + 1) * 4;
791
1203
  new Uint32Array(buffer, offset, runEdges).set(graph.turnRestrictionSequenceEdges);
792
1204
  }
1205
+ if (conditions && conditionRows && conditionText) {
1206
+ view.setUint32(conditionsAt, conditionRows, true);
1207
+ view.setUint32(conditionsAt + 4, conditionText.byteLength, true);
1208
+ new Uint32Array(buffer, conditionsAt + 8, conditionRows).set(conditions.edges);
1209
+ new Uint16Array(buffer, conditionsAt + 8 + conditionRows * 4, conditionRows).set(conditions.rules);
1210
+ new Uint8Array(buffer, conditionsAt + 8 + conditionRows * 6, conditionRows).set(conditions.kinds);
1211
+ new Uint8Array(buffer, conditionTextAt, conditionText.byteLength).set(conditionText);
1212
+ }
793
1213
  return buffer;
794
1214
  }
795
1215
  function decodePackedGraph(buffer, shared = false) {
@@ -806,7 +1226,17 @@ function decodePackedGraph(buffer, shared = false) {
806
1226
  const lengths = Array.from({ length: shared ? 6 : 5 }, (_, index) => view.getUint32(40 + index * 4, true));
807
1227
  const variableBytes = lengths.reduce((sum, length) => sum + length, 0);
808
1228
  const restrictionsAt = alignUp(PACKED_HEADER_BYTES + (shared ? 0 : nodes * 8) + edges * (shared ? 2 : 4) + variableBytes);
809
- const expected = restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0);
1229
+ const restrictionsEnd = restrictionsAt + pairs * 8 + (runs ? (runs + 1) * 4 + runEdges * 4 : 0);
1230
+ const hasConditions = !shared && view.getUint8(61) === 1;
1231
+ const conditionsAt = alignUp(restrictionsEnd);
1232
+ let expected = restrictionsEnd;
1233
+ if (hasConditions) {
1234
+ if (buffer.byteLength < conditionsAt + 8) throw new Error("Truncated compact routing graph");
1235
+ const rows = view.getUint32(conditionsAt, true);
1236
+ const textBytes = view.getUint32(conditionsAt + 4, true);
1237
+ if (rows > edges || !textBytes) throw new Error("Invalid compact routing graph: conditions do not fit the graph.");
1238
+ expected = alignUp(conditionsAt + 8 + rows * 7) + textBytes;
1239
+ }
810
1240
  if (!coordinateScale || !edgeDistanceScale || !edgeDurationScale || buffer.byteLength !== expected) throw new Error("Truncated compact routing graph");
811
1241
  if (shapeCount > edges) throw new Error("Invalid compact routing graph: more shapes than edges.");
812
1242
  const profileId = shared ? 0 : view.getUint8(60);
@@ -935,6 +1365,7 @@ function decodePackedGraph(buffer, shared = false) {
935
1365
  }
936
1366
  for (const edge of graph.turnRestrictionFromEdges ?? []) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
937
1367
  for (const edge of graph.turnRestrictionToEdges ?? []) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
1368
+ if (hasConditions) graph.edgeConditions = readEdgeConditions(buffer, conditionsAt, edges);
938
1369
  return graph;
939
1370
  }
940
1371
  var PROFILE_CHUNK_HEADER_BYTES = 64;
@@ -963,7 +1394,14 @@ function encodeProfileChunk(chunk) {
963
1394
  const variableAt = PROFILE_CHUNK_HEADER_BYTES + edges * 2 + (mask?.byteLength ?? 0) + (turns?.byteLength ?? 0);
964
1395
  const limitsAt = alignUp(variableAt);
965
1396
  const limitRows = limits?.edges.length ?? 0;
966
- const buffer = new ArrayBuffer(limitRows ? limitsAt + limitRows * 8 : variableAt);
1397
+ const limitsEnd = limitRows ? limitsAt + limitRows * 8 : variableAt;
1398
+ const conditions = chunk.edgeConditions?.edges.length ? chunk.edgeConditions : void 0;
1399
+ if (conditions) assertEdgeConditions(conditions, edges);
1400
+ const conditionRows = conditions?.edges.length ?? 0;
1401
+ const conditionText = conditions ? new TextEncoder().encode(conditions.values.join("\0")) : void 0;
1402
+ const conditionsAt = alignUp(limitsEnd);
1403
+ const conditionTextAt = alignUp(conditionsAt + conditionRows * 7);
1404
+ const buffer = new ArrayBuffer(conditionRows ? conditionTextAt + (conditionText?.byteLength ?? 0) : limitsEnd);
967
1405
  const view = new DataView(buffer);
968
1406
  view.setUint32(0, COMPACT_MAGIC, true);
969
1407
  view.setUint16(4, SPLIT_COMPACT_VERSION, true);
@@ -976,6 +1414,8 @@ function encodeProfileChunk(chunk) {
976
1414
  view.setUint32(20, limitRows, true);
977
1415
  view.setUint32(24, restrictions, true);
978
1416
  view.setUint32(28, turns?.byteLength ?? 0, true);
1417
+ view.setUint32(32, conditionRows, true);
1418
+ view.setUint32(36, conditionText?.byteLength ?? 0, true);
979
1419
  new Uint16Array(buffer, PROFILE_CHUNK_HEADER_BYTES, edges).set(chunk.edgeDurationsSeconds);
980
1420
  if (mask) new Uint8Array(buffer, PROFILE_CHUNK_HEADER_BYTES + edges * 2, mask.byteLength).set(mask);
981
1421
  if (turns) new Uint8Array(buffer, PROFILE_CHUNK_HEADER_BYTES + edges * 2 + (mask?.byteLength ?? 0), turns.byteLength).set(turns);
@@ -987,6 +1427,12 @@ function encodeProfileChunk(chunk) {
987
1427
  columns.set(limits.lengthDecimetres, limitRows * 2);
988
1428
  columns.set(limits.weightHalfTonnes, limitRows * 3);
989
1429
  }
1430
+ if (conditions && conditionRows && conditionText) {
1431
+ new Uint32Array(buffer, conditionsAt, conditionRows).set(conditions.edges);
1432
+ new Uint16Array(buffer, conditionsAt + conditionRows * 4, conditionRows).set(conditions.rules);
1433
+ new Uint8Array(buffer, conditionsAt + conditionRows * 6, conditionRows).set(conditions.kinds);
1434
+ new Uint8Array(buffer, conditionTextAt, conditionText.byteLength).set(conditionText);
1435
+ }
990
1436
  return buffer;
991
1437
  }
992
1438
  function decodeProfileChunk(buffer) {
@@ -1003,6 +1449,8 @@ function decodeProfileChunk(buffer) {
1003
1449
  const limitRows = view.getUint32(20, true);
1004
1450
  const restrictionCount = view.getUint32(24, true);
1005
1451
  const turnMaskBytes = view.getUint32(28, true);
1452
+ const conditionRows = view.getUint32(32, true);
1453
+ const conditionTextBytes = view.getUint32(36, true);
1006
1454
  if (!profileId || !getRoutingProfileById(profileId)) throw new Error(`Routing graph was compiled for an unsupported profile (id ${profileId}).`);
1007
1455
  if (!edgeDurationScale) throw new Error("Invalid routing profile chunk: zero duration scale.");
1008
1456
  if (maxSpeedKph > MAX_PLAUSIBLE_SPEED_KPH) throw new Error("Invalid routing profile chunk: implausible profile speed ceiling.");
@@ -1010,8 +1458,12 @@ function decodeProfileChunk(buffer) {
1010
1458
  if (turnMaskBytes && turnMaskBytes !== restrictionCount + 7 >> 3) throw new Error("Invalid routing profile chunk: restriction mask does not cover every restriction.");
1011
1459
  const variableAt = PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes + turnMaskBytes;
1012
1460
  const limitsAt = alignUp(variableAt);
1013
- const expected = limitRows ? limitsAt + limitRows * 8 : variableAt;
1461
+ const limitsEnd = limitRows ? limitsAt + limitRows * 8 : variableAt;
1462
+ const conditionsAt = alignUp(limitsEnd);
1463
+ const conditionTextAt = alignUp(conditionsAt + conditionRows * 7);
1464
+ const expected = conditionRows ? conditionTextAt + conditionTextBytes : limitsEnd;
1014
1465
  if (limitRows > edges) throw new Error("Invalid routing profile chunk: more limits than edges.");
1466
+ if (conditionRows && !conditionTextBytes) throw new Error("Invalid routing profile chunk: conditions with no values.");
1015
1467
  if (buffer.byteLength !== expected) throw new Error("Truncated routing profile chunk");
1016
1468
  let edgeLimits;
1017
1469
  if (limitRows) {
@@ -1029,6 +1481,25 @@ function decodeProfileChunk(buffer) {
1029
1481
  weightHalfTonnes: columns.slice(limitRows * 3)
1030
1482
  };
1031
1483
  }
1484
+ let edgeConditions;
1485
+ if (conditionRows) {
1486
+ const conditionEdges = new Uint32Array(buffer.slice(conditionsAt, conditionsAt + conditionRows * 4));
1487
+ for (let index = 0; index < conditionRows; index += 1) {
1488
+ if (conditionEdges[index] >= edges) throw new Error("Invalid routing profile chunk: condition references an edge outside the graph.");
1489
+ if (index && conditionEdges[index] < conditionEdges[index - 1]) throw new Error("Invalid routing profile chunk: conditions are not sorted.");
1490
+ }
1491
+ const values = new TextDecoder().decode(new Uint8Array(buffer, conditionTextAt, conditionTextBytes)).split("\0");
1492
+ const rules = new Uint16Array(buffer.slice(conditionsAt + conditionRows * 4, conditionsAt + conditionRows * 6));
1493
+ for (let index = 0; index < conditionRows; index += 1) {
1494
+ if (rules[index] >= values.length) throw new Error("Invalid routing profile chunk: condition names a value that is not in the table.");
1495
+ }
1496
+ edgeConditions = {
1497
+ edges: conditionEdges,
1498
+ rules,
1499
+ kinds: new Uint8Array(buffer.slice(conditionsAt + conditionRows * 6, conditionsAt + conditionRows * 7)),
1500
+ values
1501
+ };
1502
+ }
1032
1503
  return {
1033
1504
  profileId,
1034
1505
  maxSpeedKph,
@@ -1037,7 +1508,8 @@ function decodeProfileChunk(buffer) {
1037
1508
  ...maskBytes ? { edgeAccess: new Uint8Array(buffer.slice(PROFILE_CHUNK_HEADER_BYTES + edges * 2, PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes)) } : {},
1038
1509
  ...restrictionCount ? { restrictionCount } : {},
1039
1510
  ...turnMaskBytes ? { restrictionMask: new Uint8Array(buffer.slice(PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes, variableAt)) } : {},
1040
- ...edgeLimits ? { edgeLimits } : {}
1511
+ ...edgeLimits ? { edgeLimits } : {},
1512
+ ...edgeConditions ? { edgeConditions } : {}
1041
1513
  };
1042
1514
  }
1043
1515
  function decodeSharedGraph(buffer) {
@@ -1061,6 +1533,7 @@ function combineProfiledGraph(shared, profile) {
1061
1533
  maxSpeedKph: profile.maxSpeedKph,
1062
1534
  ...profile.edgeAccess ? { edgeAccess: profile.edgeAccess } : {},
1063
1535
  ...profile.edgeLimits ? { edgeLimits: profile.edgeLimits } : {},
1536
+ ...profile.edgeConditions ? { edgeConditions: profile.edgeConditions } : {},
1064
1537
  ...profile.restrictionMask ? { restrictionMask: profile.restrictionMask } : {}
1065
1538
  };
1066
1539
  }
@@ -1153,303 +1626,144 @@ function componentSizes(labels) {
1153
1626
  return sizes;
1154
1627
  }
1155
1628
 
1156
- // src/limits.ts
1157
- var DECIMETRE_CEILING = 255;
1158
- var HALF_TONNE_CEILING = 255;
1159
- function toDecimetres(metres) {
1160
- if (metres === void 0 || !(metres > 0)) return 0;
1161
- const value = Math.floor(metres * 10);
1162
- return value > DECIMETRE_CEILING ? 0 : value;
1163
- }
1164
- function toHalfTonnes(tonnes) {
1165
- if (tonnes === void 0 || !(tonnes > 0)) return 0;
1166
- const value = Math.floor(tonnes * 2);
1167
- return value > HALF_TONNE_CEILING ? 0 : value;
1168
- }
1169
- var fromDecimetres = (value) => value / 10;
1170
- var fromHalfTonnes = (value) => value / 2;
1171
- function packEdgeLimits(entries) {
1172
- const rows = entries.map(({ edge, limits }) => ({
1173
- edge,
1174
- height: toDecimetres(limits.heightMeters),
1175
- width: toDecimetres(limits.widthMeters),
1176
- length: toDecimetres(limits.lengthMeters),
1177
- weight: toHalfTonnes(limits.weightTonnes)
1178
- })).filter((row) => row.height || row.width || row.length || row.weight).sort((a, b) => a.edge - b.edge);
1179
- if (!rows.length) return void 0;
1180
- return {
1181
- edges: Uint32Array.from(rows, (row) => row.edge),
1182
- heightDecimetres: Uint8Array.from(rows, (row) => row.height),
1183
- widthDecimetres: Uint8Array.from(rows, (row) => row.width),
1184
- lengthDecimetres: Uint8Array.from(rows, (row) => row.length),
1185
- weightHalfTonnes: Uint8Array.from(rows, (row) => row.weight)
1186
- };
1187
- }
1188
- function blocks(limits, index, vehicle) {
1189
- const height = limits.heightDecimetres[index];
1190
- if (height && vehicle.heightMeters > fromDecimetres(height)) return true;
1191
- const width = limits.widthDecimetres[index];
1192
- if (width && vehicle.widthMeters > fromDecimetres(width)) return true;
1193
- const length = limits.lengthDecimetres[index];
1194
- if (length && vehicle.lengthMeters > fromDecimetres(length)) return true;
1195
- const weight = limits.weightHalfTonnes[index];
1196
- return Boolean(weight) && vehicle.weightTonnes > fromHalfTonnes(weight);
1629
+ // src/edge-index.ts
1630
+ var MAX_CELLS_PER_EDGE = 4096;
1631
+ function cellKey(latitudeCell, longitudeCell) {
1632
+ return `${latitudeCell}:${longitudeCell}`;
1197
1633
  }
1198
- function accessForVehicle(graph, vehicle) {
1199
- const limits = graph.edgeLimits;
1200
- if (!vehicle || !limits?.edges.length) return graph.edgeAccess;
1201
- const edges = graph.edgeTargets.length;
1202
- const mask = graph.edgeAccess ? Uint8Array.from(graph.edgeAccess) : accessMaskFor(edges, () => true);
1203
- for (let index = 0; index < limits.edges.length; index += 1) {
1204
- const edge = limits.edges[index];
1205
- if (edge < edges && blocks(limits, index, vehicle)) mask[edge >> 3] &= ~(1 << (edge & 7));
1634
+ function edgeSource(graph, edge) {
1635
+ let low = 0;
1636
+ let high = graph.nodeLatitudes.length - 1;
1637
+ while (low < high) {
1638
+ const middle = low + high + 1 >> 1;
1639
+ if (graph.edgeOffsets[middle] <= edge) low = middle;
1640
+ else high = middle - 1;
1206
1641
  }
1207
- return mask;
1208
- }
1209
- function countBlocked(graph, vehicle) {
1210
- const mask = accessForVehicle(graph, vehicle);
1211
- if (!mask) return 0;
1212
- let blocked = 0;
1213
- for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) if (!edgeAllowed(mask, edge)) blocked += 1;
1214
- return blocked;
1642
+ return low;
1215
1643
  }
1216
-
1217
- // src/types.ts
1218
- var RouterError = class extends Error {
1219
- constructor(code, message) {
1220
- super(message);
1221
- this.code = code;
1222
- this.name = "RouterError";
1644
+ function edgePolyline(graph, edge, source = edgeSource(graph, edge)) {
1645
+ const scale = graph.coordinateScale ?? 1;
1646
+ const target = graph.edgeTargets[edge];
1647
+ const points = [{ lat: graph.nodeLatitudes[source] / scale, lng: graph.nodeLongitudes[source] / scale }];
1648
+ if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
1649
+ for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
1650
+ points.push({ lat: graph.edgeShapeLatitudes[point] / scale, lng: graph.edgeShapeLongitudes[point] / scale });
1651
+ }
1223
1652
  }
1224
- code;
1225
- };
1226
-
1227
- // src/spatial-index.ts
1228
- function cellKey(latitude, longitude, cellSizeDegrees) {
1229
- return `${Math.floor(latitude / cellSizeDegrees)}:${Math.floor(longitude / cellSizeDegrees)}`;
1653
+ points.push({ lat: graph.nodeLatitudes[target] / scale, lng: graph.nodeLongitudes[target] / scale });
1654
+ return points;
1230
1655
  }
1231
- function coordinateFor(graph, nodeId) {
1656
+ function createEdgeSpatialIndex(graph, cellSizeDegrees = 0.02) {
1657
+ if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Edge index cell size must be positive.");
1232
1658
  const scale = graph.coordinateScale ?? 1;
1233
- return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
1234
- }
1235
- function createSpatialIndex(graph, cellSizeDegrees = 0.02) {
1236
- if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Spatial index cell size must be positive.");
1237
1659
  const buckets = /* @__PURE__ */ new Map();
1238
- for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) {
1239
- const coordinate = coordinateFor(graph, nodeId);
1240
- const key = cellKey(coordinate.lat, coordinate.lng, cellSizeDegrees);
1241
- const bucket = buckets.get(key);
1242
- if (bucket) bucket.push(nodeId);
1243
- else buckets.set(key, [nodeId]);
1660
+ const oversized = [];
1661
+ const shapeOffsets = graph.edgeShapeOffsets;
1662
+ const shapeLatitudes = graph.edgeShapeLatitudes;
1663
+ const shapeLongitudes = graph.edgeShapeLongitudes;
1664
+ for (let source = 0; source < graph.nodeLatitudes.length; source += 1) {
1665
+ const sourceLat = graph.nodeLatitudes[source] / scale;
1666
+ const sourceLng = graph.nodeLongitudes[source] / scale;
1667
+ for (let edge = graph.edgeOffsets[source]; edge < graph.edgeOffsets[source + 1]; edge += 1) {
1668
+ const target = graph.edgeTargets[edge];
1669
+ let minLat = Math.min(sourceLat, graph.nodeLatitudes[target] / scale);
1670
+ let maxLat = Math.max(sourceLat, graph.nodeLatitudes[target] / scale);
1671
+ let minLng = Math.min(sourceLng, graph.nodeLongitudes[target] / scale);
1672
+ let maxLng = Math.max(sourceLng, graph.nodeLongitudes[target] / scale);
1673
+ if (shapeOffsets && shapeLatitudes && shapeLongitudes) {
1674
+ for (let point = shapeOffsets[edge]; point < shapeOffsets[edge + 1]; point += 1) {
1675
+ const lat = shapeLatitudes[point] / scale;
1676
+ const lng = shapeLongitudes[point] / scale;
1677
+ if (lat < minLat) minLat = lat;
1678
+ else if (lat > maxLat) maxLat = lat;
1679
+ if (lng < minLng) minLng = lng;
1680
+ else if (lng > maxLng) maxLng = lng;
1681
+ }
1682
+ }
1683
+ const minLatCell = Math.floor(minLat / cellSizeDegrees);
1684
+ const maxLatCell = Math.floor(maxLat / cellSizeDegrees);
1685
+ const minLngCell = Math.floor(minLng / cellSizeDegrees);
1686
+ const maxLngCell = Math.floor(maxLng / cellSizeDegrees);
1687
+ if (!Number.isFinite(minLatCell) || !Number.isFinite(minLngCell) || (maxLatCell - minLatCell + 1) * (maxLngCell - minLngCell + 1) > MAX_CELLS_PER_EDGE) {
1688
+ oversized.push(edge);
1689
+ continue;
1690
+ }
1691
+ for (let latitudeCell = minLatCell; latitudeCell <= maxLatCell; latitudeCell += 1) {
1692
+ for (let longitudeCell = minLngCell; longitudeCell <= maxLngCell; longitudeCell += 1) {
1693
+ const key = cellKey(latitudeCell, longitudeCell);
1694
+ const bucket = buckets.get(key);
1695
+ if (bucket) bucket.push(edge);
1696
+ else buckets.set(key, [edge]);
1697
+ }
1698
+ }
1699
+ }
1244
1700
  }
1245
- return {
1246
- cellSizeDegrees,
1247
- cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)]))
1248
- };
1249
- }
1250
- function withSpatialIndex(graph, cellSizeDegrees) {
1251
- return { ...graph, spatialIndex: createSpatialIndex(graph, cellSizeDegrees) };
1701
+ return { cellSizeDegrees, cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)])), oversized: Uint32Array.from(oversized) };
1252
1702
  }
1253
- function nearbyNodeIds(index, coordinate, maxDistanceMeters) {
1703
+ var indexes = /* @__PURE__ */ new WeakMap();
1704
+ function edgeSpatialIndexFor(graph, cellSizeDegrees) {
1705
+ let index = indexes.get(graph);
1706
+ if (!index) {
1707
+ index = createEdgeSpatialIndex(graph, cellSizeDegrees);
1708
+ indexes.set(graph, index);
1709
+ }
1710
+ return index;
1711
+ }
1712
+ function nearbyEdgeIds(index, coordinate, maxDistanceMeters) {
1254
1713
  const latitudeDelta = maxDistanceMeters / 110574;
1255
1714
  const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
1256
1715
  const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
1257
1716
  const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
1258
1717
  const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
1259
1718
  const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
1260
- const ids = [];
1719
+ const ids = new Set(index.oversized);
1261
1720
  for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
1262
1721
  for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
1263
- const bucket = index.cells.get(`${latitudeCell}:${longitudeCell}`);
1264
- if (bucket) ids.push(...bucket);
1722
+ const bucket = index.cells.get(cellKey(latitudeCell, longitudeCell));
1723
+ if (bucket) for (const id of bucket) ids.add(id);
1265
1724
  }
1266
1725
  }
1267
1726
  return ids;
1268
1727
  }
1269
1728
 
1270
- // src/turn-restrictions.ts
1271
- function buildTurnRestrictionIndex(graph) {
1272
- const fromEdges = graph.turnRestrictionFromEdges;
1273
- const toEdges = graph.turnRestrictionToEdges;
1274
- if (!fromEdges || !toEdges || !fromEdges.length) return null;
1275
- if (fromEdges.length !== toEdges.length) throw new Error("Turn restriction arrays must have the same length.");
1276
- const index = /* @__PURE__ */ new Map();
1277
- for (let pair = 0; pair < fromEdges.length; pair += 1) {
1278
- if (!edgeAllowed(graph.restrictionMask, pair)) continue;
1279
- const banned = index.get(fromEdges[pair]);
1280
- if (banned) banned.add(toEdges[pair]);
1281
- else index.set(fromEdges[pair], /* @__PURE__ */ new Set([toEdges[pair]]));
1282
- }
1283
- return index.size ? index : null;
1284
- }
1285
- function isTurnBanned(index, fromEdge, toEdge) {
1286
- return index.get(fromEdge)?.has(toEdge) ?? false;
1287
- }
1288
- function packTurnRestrictions(pairs) {
1289
- const unique = /* @__PURE__ */ new Set();
1290
- const sorted = [];
1291
- for (const [fromEdge, toEdge] of pairs) {
1292
- const key = `${fromEdge}:${toEdge}`;
1293
- if (unique.has(key)) continue;
1294
- unique.add(key);
1295
- sorted.push([fromEdge, toEdge]);
1296
- }
1297
- sorted.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
1298
- return {
1299
- turnRestrictionFromEdges: Uint32Array.from(sorted, (pair) => pair[0]),
1300
- turnRestrictionToEdges: Uint32Array.from(sorted, (pair) => pair[1])
1301
- };
1302
- }
1303
- function buildTurnSequenceIndex(graph) {
1304
- const offsets = graph.turnRestrictionSequenceOffsets;
1305
- const edges = graph.turnRestrictionSequenceEdges;
1306
- if (!offsets || !edges || offsets.length < 2) return null;
1307
- const sequences = [];
1308
- const startingAt = /* @__PURE__ */ new Map();
1309
- const pairs = graph.turnRestrictionFromEdges?.length ?? 0;
1310
- for (let index = 0; index + 1 < offsets.length; index += 1) {
1311
- if (!edgeAllowed(graph.restrictionMask, pairs + index)) continue;
1312
- const sequence = Array.from(edges.subarray(offsets[index], offsets[index + 1]));
1313
- if (sequence.length < 3) continue;
1314
- const id = sequences.length;
1315
- sequences.push(sequence);
1316
- const bucket = startingAt.get(sequence[0]);
1317
- if (bucket) bucket.push(id);
1318
- else startingAt.set(sequence[0], [id]);
1319
- }
1320
- if (!sequences.length) return null;
1321
- const stride = sequences.reduce((longest, sequence) => Math.max(longest, sequence.length), 0) + 1;
1322
- return { sequences, startingAt, stride };
1323
- }
1324
- function packTurnSequences(sequences) {
1325
- const kept = sequences.filter((sequence) => sequence.length >= 3);
1326
- const unique = new Map(kept.map((sequence) => [sequence.join(","), sequence]));
1327
- const ordered = [...unique.values()].sort((a, b) => a[0] - b[0] || a.length - b.length || a[1] - b[1]);
1328
- const turnRestrictionSequenceOffsets = new Uint32Array(ordered.length + 1);
1329
- for (let index = 0; index < ordered.length; index += 1) turnRestrictionSequenceOffsets[index + 1] = turnRestrictionSequenceOffsets[index] + ordered[index].length;
1330
- const turnRestrictionSequenceEdges = new Uint32Array(turnRestrictionSequenceOffsets[ordered.length]);
1331
- let at = 0;
1332
- for (const sequence of ordered) for (const edge of sequence) turnRestrictionSequenceEdges[at++] = edge;
1333
- return { turnRestrictionSequenceOffsets, turnRestrictionSequenceEdges };
1334
- }
1335
- var guardedCache = /* @__PURE__ */ new WeakMap();
1336
- function guardedJunctionsFor(graph, restrictions, sequences) {
1337
- const held = guardedCache.get(graph);
1338
- if (held) return held;
1339
- const nodes = graph.nodeLatitudes.length;
1340
- const edges = graph.edgeTargets.length;
1341
- const guarded = new Uint8Array(nodes);
1342
- if (restrictions) for (const edge of restrictions.keys()) guarded[graph.edgeTargets[edge]] = 1;
1343
- if (sequences) {
1344
- for (const sequence of sequences.sequences) {
1345
- for (const edge of sequence) guarded[graph.edgeTargets[edge]] = 1;
1346
- }
1347
- }
1348
- const stateOfEdge = new Int32Array(edges).fill(-1);
1349
- let count = 0;
1350
- for (let edge = 0; edge < edges; edge += 1) if (guarded[graph.edgeTargets[edge]]) stateOfEdge[edge] = count++;
1351
- const edgeOfState = new Int32Array(count);
1352
- for (let edge = 0; edge < edges; edge += 1) if (stateOfEdge[edge] >= 0) edgeOfState[stateOfEdge[edge]] = edge;
1353
- const index = { guarded, stateOfEdge, edgeOfState, count };
1354
- guardedCache.set(graph, index);
1355
- return index;
1356
- }
1357
-
1358
- // src/edge-index.ts
1359
- var MAX_CELLS_PER_EDGE = 4096;
1360
- function cellKey2(latitudeCell, longitudeCell) {
1361
- return `${latitudeCell}:${longitudeCell}`;
1362
- }
1363
- function edgeSource(graph, edge) {
1364
- let low = 0;
1365
- let high = graph.nodeLatitudes.length - 1;
1366
- while (low < high) {
1367
- const middle = low + high + 1 >> 1;
1368
- if (graph.edgeOffsets[middle] <= edge) low = middle;
1369
- else high = middle - 1;
1370
- }
1371
- return low;
1729
+ // src/spatial-index.ts
1730
+ function cellKey2(latitude, longitude, cellSizeDegrees) {
1731
+ return `${Math.floor(latitude / cellSizeDegrees)}:${Math.floor(longitude / cellSizeDegrees)}`;
1372
1732
  }
1373
- function edgePolyline(graph, edge, source = edgeSource(graph, edge)) {
1733
+ function coordinateFor(graph, nodeId) {
1374
1734
  const scale = graph.coordinateScale ?? 1;
1375
- const target = graph.edgeTargets[edge];
1376
- const points = [{ lat: graph.nodeLatitudes[source] / scale, lng: graph.nodeLongitudes[source] / scale }];
1377
- if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
1378
- for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
1379
- points.push({ lat: graph.edgeShapeLatitudes[point] / scale, lng: graph.edgeShapeLongitudes[point] / scale });
1380
- }
1381
- }
1382
- points.push({ lat: graph.nodeLatitudes[target] / scale, lng: graph.nodeLongitudes[target] / scale });
1383
- return points;
1735
+ return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
1384
1736
  }
1385
- function createEdgeSpatialIndex(graph, cellSizeDegrees = 0.02) {
1386
- if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Edge index cell size must be positive.");
1387
- const scale = graph.coordinateScale ?? 1;
1737
+ function createSpatialIndex(graph, cellSizeDegrees = 0.02) {
1738
+ if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Spatial index cell size must be positive.");
1388
1739
  const buckets = /* @__PURE__ */ new Map();
1389
- const oversized = [];
1390
- const shapeOffsets = graph.edgeShapeOffsets;
1391
- const shapeLatitudes = graph.edgeShapeLatitudes;
1392
- const shapeLongitudes = graph.edgeShapeLongitudes;
1393
- for (let source = 0; source < graph.nodeLatitudes.length; source += 1) {
1394
- const sourceLat = graph.nodeLatitudes[source] / scale;
1395
- const sourceLng = graph.nodeLongitudes[source] / scale;
1396
- for (let edge = graph.edgeOffsets[source]; edge < graph.edgeOffsets[source + 1]; edge += 1) {
1397
- const target = graph.edgeTargets[edge];
1398
- let minLat = Math.min(sourceLat, graph.nodeLatitudes[target] / scale);
1399
- let maxLat = Math.max(sourceLat, graph.nodeLatitudes[target] / scale);
1400
- let minLng = Math.min(sourceLng, graph.nodeLongitudes[target] / scale);
1401
- let maxLng = Math.max(sourceLng, graph.nodeLongitudes[target] / scale);
1402
- if (shapeOffsets && shapeLatitudes && shapeLongitudes) {
1403
- for (let point = shapeOffsets[edge]; point < shapeOffsets[edge + 1]; point += 1) {
1404
- const lat = shapeLatitudes[point] / scale;
1405
- const lng = shapeLongitudes[point] / scale;
1406
- if (lat < minLat) minLat = lat;
1407
- else if (lat > maxLat) maxLat = lat;
1408
- if (lng < minLng) minLng = lng;
1409
- else if (lng > maxLng) maxLng = lng;
1410
- }
1411
- }
1412
- const minLatCell = Math.floor(minLat / cellSizeDegrees);
1413
- const maxLatCell = Math.floor(maxLat / cellSizeDegrees);
1414
- const minLngCell = Math.floor(minLng / cellSizeDegrees);
1415
- const maxLngCell = Math.floor(maxLng / cellSizeDegrees);
1416
- if (!Number.isFinite(minLatCell) || !Number.isFinite(minLngCell) || (maxLatCell - minLatCell + 1) * (maxLngCell - minLngCell + 1) > MAX_CELLS_PER_EDGE) {
1417
- oversized.push(edge);
1418
- continue;
1419
- }
1420
- for (let latitudeCell = minLatCell; latitudeCell <= maxLatCell; latitudeCell += 1) {
1421
- for (let longitudeCell = minLngCell; longitudeCell <= maxLngCell; longitudeCell += 1) {
1422
- const key = cellKey2(latitudeCell, longitudeCell);
1423
- const bucket = buckets.get(key);
1424
- if (bucket) bucket.push(edge);
1425
- else buckets.set(key, [edge]);
1426
- }
1427
- }
1428
- }
1740
+ for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) {
1741
+ const coordinate = coordinateFor(graph, nodeId);
1742
+ const key = cellKey2(coordinate.lat, coordinate.lng, cellSizeDegrees);
1743
+ const bucket = buckets.get(key);
1744
+ if (bucket) bucket.push(nodeId);
1745
+ else buckets.set(key, [nodeId]);
1429
1746
  }
1430
- return { cellSizeDegrees, cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)])), oversized: Uint32Array.from(oversized) };
1747
+ return {
1748
+ cellSizeDegrees,
1749
+ cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)]))
1750
+ };
1431
1751
  }
1432
- var indexes = /* @__PURE__ */ new WeakMap();
1433
- function edgeSpatialIndexFor(graph, cellSizeDegrees) {
1434
- let index = indexes.get(graph);
1435
- if (!index) {
1436
- index = createEdgeSpatialIndex(graph, cellSizeDegrees);
1437
- indexes.set(graph, index);
1438
- }
1439
- return index;
1752
+ function withSpatialIndex(graph, cellSizeDegrees) {
1753
+ return { ...graph, spatialIndex: createSpatialIndex(graph, cellSizeDegrees) };
1440
1754
  }
1441
- function nearbyEdgeIds(index, coordinate, maxDistanceMeters) {
1755
+ function nearbyNodeIds(index, coordinate, maxDistanceMeters) {
1442
1756
  const latitudeDelta = maxDistanceMeters / 110574;
1443
1757
  const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
1444
1758
  const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
1445
1759
  const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
1446
1760
  const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
1447
1761
  const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
1448
- const ids = new Set(index.oversized);
1762
+ const ids = [];
1449
1763
  for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
1450
1764
  for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
1451
- const bucket = index.cells.get(cellKey2(latitudeCell, longitudeCell));
1452
- if (bucket) for (const id of bucket) ids.add(id);
1765
+ const bucket = index.cells.get(`${latitudeCell}:${longitudeCell}`);
1766
+ if (bucket) ids.push(...bucket);
1453
1767
  }
1454
1768
  }
1455
1769
  return ids;
@@ -1461,6 +1775,8 @@ var METERS_PER_DEGREE_LATITUDE = 110574;
1461
1775
  var METERS_PER_DEGREE_LONGITUDE = 111320;
1462
1776
  var NODE_SNAP_TOLERANCE_METERS = 1;
1463
1777
  var TIE_TOLERANCE_METERS = 0.5;
1778
+ var DEFAULT_SNAP_RADIUS_METERS = 500;
1779
+ var FALLBACK_SNAP_RADIUS_METERS = 1e3;
1464
1780
  function haversineMeters(a, b) {
1465
1781
  const radians = Math.PI / 180;
1466
1782
  const dLat = (b.lat - a.lat) * radians;
@@ -1499,7 +1815,7 @@ function projectOntoPolyline(points, query) {
1499
1815
  }
1500
1816
  return { ...best, totalMeters: total };
1501
1817
  }
1502
- function snapToGraph(graph, coordinate, maxDistanceMeters = 500, access = graph.edgeAccess, accept) {
1818
+ function snapToGraph(graph, coordinate, maxDistanceMeters = DEFAULT_SNAP_RADIUS_METERS, access = graph.edgeAccess, accept, everyNearbyRoad = false) {
1503
1819
  const candidates = graph.edgeTargets.length ? nearbyEdgeIds(edgeSpatialIndexFor(graph), coordinate, maxDistanceMeters) : /* @__PURE__ */ new Set();
1504
1820
  let best = null;
1505
1821
  const projections = [];
@@ -1529,9 +1845,9 @@ function snapToGraph(graph, coordinate, maxDistanceMeters = 500, access = graph.
1529
1845
  }
1530
1846
  const startMeters = best.fraction * totalLengthOf(best);
1531
1847
  const endMeters = (1 - best.fraction) * totalLengthOf(best);
1532
- if (startMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.source, point: best.polyline[0], distanceMeters: best.distanceMeters };
1533
- if (endMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.target, point: best.polyline[best.polyline.length - 1], distanceMeters: best.distanceMeters };
1534
- const sameRoad = projections.filter((projection) => projection.distanceMeters <= best.distanceMeters + TIE_TOLERANCE_METERS);
1848
+ if (!everyNearbyRoad && startMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.source, point: best.polyline[0], distanceMeters: best.distanceMeters };
1849
+ if (!everyNearbyRoad && endMeters <= NODE_SNAP_TOLERANCE_METERS) return { kind: "node", node: best.target, point: best.polyline[best.polyline.length - 1], distanceMeters: best.distanceMeters };
1850
+ const sameRoad = everyNearbyRoad ? projections.sort((left, right) => left.distanceMeters - right.distanceMeters) : projections.filter((projection) => projection.distanceMeters <= best.distanceMeters + TIE_TOLERANCE_METERS);
1535
1851
  return { kind: "edge", projections: sameRoad, point: best.point, distanceMeters: best.distanceMeters };
1536
1852
  }
1537
1853
  function totalLengthOf(projection) {
@@ -1542,7 +1858,7 @@ function totalLengthOf(projection) {
1542
1858
  function nearestNodeWithin(graph, coordinate, maxDistanceMeters, accept) {
1543
1859
  let closest = null;
1544
1860
  let closestDistance = maxDistanceMeters;
1545
- const check = (nodeId) => {
1861
+ const check2 = (nodeId) => {
1546
1862
  if (accept && !accept(nodeId)) return;
1547
1863
  const distance = haversineMeters(coordinate, coordinateFor2(graph, nodeId));
1548
1864
  if (distance <= closestDistance) {
@@ -1550,8 +1866,8 @@ function nearestNodeWithin(graph, coordinate, maxDistanceMeters, accept) {
1550
1866
  closestDistance = distance;
1551
1867
  }
1552
1868
  };
1553
- if (graph.spatialIndex) for (const nodeId of nearbyNodeIds(graph.spatialIndex, coordinate, maxDistanceMeters)) check(nodeId);
1554
- else for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) check(nodeId);
1869
+ if (graph.spatialIndex) for (const nodeId of nearbyNodeIds(graph.spatialIndex, coordinate, maxDistanceMeters)) check2(nodeId);
1870
+ else for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) check2(nodeId);
1555
1871
  return closest;
1556
1872
  }
1557
1873
  function geometryAfter(projection) {
@@ -1564,6 +1880,230 @@ function geometryBetween(from, to) {
1564
1880
  return [from.point, ...from.polyline.slice(from.vertexIndex + 1, to.vertexIndex + 1), to.point];
1565
1881
  }
1566
1882
 
1883
+ // src/avoid.ts
1884
+ function check(area, index) {
1885
+ const say = (what) => {
1886
+ throw new RangeError(`avoid[${index}]: ${what}`);
1887
+ };
1888
+ if (!Number.isFinite(area.lat) || area.lat < -90 || area.lat > 90) say(`lat must be a latitude, not ${area.lat}.`);
1889
+ if (!Number.isFinite(area.lng) || area.lng < -180 || area.lng > 180) say(`lng must be a longitude, not ${area.lng}.`);
1890
+ if (!Number.isFinite(area.radiusMeters) || area.radiusMeters <= 0) say(`radiusMeters must be a positive distance, not ${area.radiusMeters}.`);
1891
+ }
1892
+ var METRES_PER_DEGREE_LATITUDE2 = 111320;
1893
+ function touchesArea(graph, edge, area, centre = { lat: area.lat, lng: area.lng }) {
1894
+ const polyline = edgePolyline(graph, edge, edgeSource(graph, edge));
1895
+ const longitudeScale = METRES_PER_DEGREE_LATITUDE2 * Math.cos(centre.lat * Math.PI / 180);
1896
+ const apart = (from, to) => {
1897
+ const delta = from - to;
1898
+ return delta > 180 ? delta - 360 : delta < -180 ? delta + 360 : delta;
1899
+ };
1900
+ const x = (point) => apart(point.lng, centre.lng) * longitudeScale;
1901
+ const y = (point) => (point.lat - centre.lat) * METRES_PER_DEGREE_LATITUDE2;
1902
+ for (let index = 0; index < polyline.length; index += 1) {
1903
+ if (haversineMeters(centre, polyline[index]) <= area.radiusMeters) return true;
1904
+ if (index === 0) continue;
1905
+ const ax = x(polyline[index - 1]);
1906
+ const ay = y(polyline[index - 1]);
1907
+ const bx = x(polyline[index]);
1908
+ const by = y(polyline[index]);
1909
+ const dx = bx - ax;
1910
+ const dy = by - ay;
1911
+ const lengthSquared = dx * dx + dy * dy;
1912
+ if (lengthSquared === 0) continue;
1913
+ const along = Math.max(0, Math.min(1, -(ax * dx + ay * dy) / lengthSquared));
1914
+ if (Math.hypot(ax + along * dx, ay + along * dy) <= area.radiusMeters) return true;
1915
+ }
1916
+ return false;
1917
+ }
1918
+ function accessAvoiding(graph, areas, base) {
1919
+ const start = base ?? graph.edgeAccess;
1920
+ if (!areas?.length) return start;
1921
+ areas.forEach(check);
1922
+ const edges = graph.edgeTargets.length;
1923
+ let mask;
1924
+ const index = edgeSpatialIndexFor(graph);
1925
+ for (const area of areas) {
1926
+ const centre = { lat: area.lat, lng: area.lng };
1927
+ for (const edge of nearbyEdgeIds(index, centre, area.radiusMeters)) {
1928
+ if (edge >= edges) continue;
1929
+ if (mask && !edgeAllowed(mask, edge)) continue;
1930
+ if (!touchesArea(graph, edge, area, centre)) continue;
1931
+ mask ??= start ? Uint8Array.from(start) : accessMaskFor(edges, () => true);
1932
+ mask[edge >> 3] &= ~(1 << (edge & 7));
1933
+ }
1934
+ }
1935
+ return mask ?? start;
1936
+ }
1937
+ function countAvoided(graph, areas) {
1938
+ const before = graph.edgeAccess;
1939
+ const after = accessAvoiding(graph, areas);
1940
+ if (after === before) return 0;
1941
+ let closed = 0;
1942
+ for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) {
1943
+ if (edgeAllowed(before, edge) && !edgeAllowed(after, edge)) closed += 1;
1944
+ }
1945
+ return closed;
1946
+ }
1947
+
1948
+ // src/limits.ts
1949
+ var DECIMETRE_CEILING = 255;
1950
+ var HALF_TONNE_CEILING = 255;
1951
+ function toDecimetres(metres) {
1952
+ if (metres === void 0 || !(metres > 0)) return 0;
1953
+ const value = Math.floor(metres * 10);
1954
+ return value > DECIMETRE_CEILING ? 0 : value;
1955
+ }
1956
+ function toHalfTonnes(tonnes) {
1957
+ if (tonnes === void 0 || !(tonnes > 0)) return 0;
1958
+ const value = Math.floor(tonnes * 2);
1959
+ return value > HALF_TONNE_CEILING ? 0 : value;
1960
+ }
1961
+ var fromDecimetres = (value) => value / 10;
1962
+ var fromHalfTonnes = (value) => value / 2;
1963
+ function packEdgeLimits(entries) {
1964
+ const rows = entries.map(({ edge, limits }) => ({
1965
+ edge,
1966
+ height: toDecimetres(limits.heightMeters),
1967
+ width: toDecimetres(limits.widthMeters),
1968
+ length: toDecimetres(limits.lengthMeters),
1969
+ weight: toHalfTonnes(limits.weightTonnes)
1970
+ })).filter((row) => row.height || row.width || row.length || row.weight).sort((a, b) => a.edge - b.edge);
1971
+ if (!rows.length) return void 0;
1972
+ return {
1973
+ edges: Uint32Array.from(rows, (row) => row.edge),
1974
+ heightDecimetres: Uint8Array.from(rows, (row) => row.height),
1975
+ widthDecimetres: Uint8Array.from(rows, (row) => row.width),
1976
+ lengthDecimetres: Uint8Array.from(rows, (row) => row.length),
1977
+ weightHalfTonnes: Uint8Array.from(rows, (row) => row.weight)
1978
+ };
1979
+ }
1980
+ function blocks(limits, index, vehicle) {
1981
+ const height = limits.heightDecimetres[index];
1982
+ if (height && vehicle.heightMeters > fromDecimetres(height)) return true;
1983
+ const width = limits.widthDecimetres[index];
1984
+ if (width && vehicle.widthMeters > fromDecimetres(width)) return true;
1985
+ const length = limits.lengthDecimetres[index];
1986
+ if (length && vehicle.lengthMeters > fromDecimetres(length)) return true;
1987
+ const weight = limits.weightHalfTonnes[index];
1988
+ return Boolean(weight) && vehicle.weightTonnes > fromHalfTonnes(weight);
1989
+ }
1990
+ function accessForVehicle(graph, vehicle) {
1991
+ const limits = graph.edgeLimits;
1992
+ if (!vehicle || !limits?.edges.length) return graph.edgeAccess;
1993
+ const edges = graph.edgeTargets.length;
1994
+ const mask = graph.edgeAccess ? Uint8Array.from(graph.edgeAccess) : accessMaskFor(edges, () => true);
1995
+ for (let index = 0; index < limits.edges.length; index += 1) {
1996
+ const edge = limits.edges[index];
1997
+ if (edge < edges && blocks(limits, index, vehicle)) mask[edge >> 3] &= ~(1 << (edge & 7));
1998
+ }
1999
+ return mask;
2000
+ }
2001
+ function countBlocked(graph, vehicle) {
2002
+ const mask = accessForVehicle(graph, vehicle);
2003
+ if (!mask) return 0;
2004
+ let blocked = 0;
2005
+ for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) if (!edgeAllowed(mask, edge)) blocked += 1;
2006
+ return blocked;
2007
+ }
2008
+
2009
+ // src/types.ts
2010
+ var RouterError = class extends Error {
2011
+ constructor(code, message) {
2012
+ super(message);
2013
+ this.code = code;
2014
+ this.name = "RouterError";
2015
+ }
2016
+ code;
2017
+ };
2018
+
2019
+ // src/turn-restrictions.ts
2020
+ function buildTurnRestrictionIndex(graph) {
2021
+ const fromEdges = graph.turnRestrictionFromEdges;
2022
+ const toEdges = graph.turnRestrictionToEdges;
2023
+ if (!fromEdges || !toEdges || !fromEdges.length) return null;
2024
+ if (fromEdges.length !== toEdges.length) throw new Error("Turn restriction arrays must have the same length.");
2025
+ const index = /* @__PURE__ */ new Map();
2026
+ for (let pair = 0; pair < fromEdges.length; pair += 1) {
2027
+ if (!edgeAllowed(graph.restrictionMask, pair)) continue;
2028
+ const banned = index.get(fromEdges[pair]);
2029
+ if (banned) banned.add(toEdges[pair]);
2030
+ else index.set(fromEdges[pair], /* @__PURE__ */ new Set([toEdges[pair]]));
2031
+ }
2032
+ return index.size ? index : null;
2033
+ }
2034
+ function isTurnBanned(index, fromEdge, toEdge) {
2035
+ return index.get(fromEdge)?.has(toEdge) ?? false;
2036
+ }
2037
+ function packTurnRestrictions(pairs) {
2038
+ const unique = /* @__PURE__ */ new Set();
2039
+ const sorted = [];
2040
+ for (const [fromEdge, toEdge] of pairs) {
2041
+ const key = `${fromEdge}:${toEdge}`;
2042
+ if (unique.has(key)) continue;
2043
+ unique.add(key);
2044
+ sorted.push([fromEdge, toEdge]);
2045
+ }
2046
+ sorted.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
2047
+ return {
2048
+ turnRestrictionFromEdges: Uint32Array.from(sorted, (pair) => pair[0]),
2049
+ turnRestrictionToEdges: Uint32Array.from(sorted, (pair) => pair[1])
2050
+ };
2051
+ }
2052
+ function buildTurnSequenceIndex(graph) {
2053
+ const offsets = graph.turnRestrictionSequenceOffsets;
2054
+ const edges = graph.turnRestrictionSequenceEdges;
2055
+ if (!offsets || !edges || offsets.length < 2) return null;
2056
+ const sequences = [];
2057
+ const startingAt = /* @__PURE__ */ new Map();
2058
+ const pairs = graph.turnRestrictionFromEdges?.length ?? 0;
2059
+ for (let index = 0; index + 1 < offsets.length; index += 1) {
2060
+ if (!edgeAllowed(graph.restrictionMask, pairs + index)) continue;
2061
+ const sequence = Array.from(edges.subarray(offsets[index], offsets[index + 1]));
2062
+ if (sequence.length < 3) continue;
2063
+ const id = sequences.length;
2064
+ sequences.push(sequence);
2065
+ const bucket = startingAt.get(sequence[0]);
2066
+ if (bucket) bucket.push(id);
2067
+ else startingAt.set(sequence[0], [id]);
2068
+ }
2069
+ if (!sequences.length) return null;
2070
+ const stride = sequences.reduce((longest, sequence) => Math.max(longest, sequence.length), 0) + 1;
2071
+ return { sequences, startingAt, stride };
2072
+ }
2073
+ function packTurnSequences(sequences) {
2074
+ const kept = sequences.filter((sequence) => sequence.length >= 3);
2075
+ const unique = new Map(kept.map((sequence) => [sequence.join(","), sequence]));
2076
+ const ordered = [...unique.values()].sort((a, b) => a[0] - b[0] || a.length - b.length || a[1] - b[1]);
2077
+ const turnRestrictionSequenceOffsets = new Uint32Array(ordered.length + 1);
2078
+ for (let index = 0; index < ordered.length; index += 1) turnRestrictionSequenceOffsets[index + 1] = turnRestrictionSequenceOffsets[index] + ordered[index].length;
2079
+ const turnRestrictionSequenceEdges = new Uint32Array(turnRestrictionSequenceOffsets[ordered.length]);
2080
+ let at = 0;
2081
+ for (const sequence of ordered) for (const edge of sequence) turnRestrictionSequenceEdges[at++] = edge;
2082
+ return { turnRestrictionSequenceOffsets, turnRestrictionSequenceEdges };
2083
+ }
2084
+ var guardedCache = /* @__PURE__ */ new WeakMap();
2085
+ function guardedJunctionsFor(graph, restrictions, sequences) {
2086
+ const held = guardedCache.get(graph);
2087
+ if (held) return held;
2088
+ const nodes = graph.nodeLatitudes.length;
2089
+ const edges = graph.edgeTargets.length;
2090
+ const guarded = new Uint8Array(nodes);
2091
+ if (restrictions) for (const edge of restrictions.keys()) guarded[graph.edgeTargets[edge]] = 1;
2092
+ if (sequences) {
2093
+ for (const sequence of sequences.sequences) {
2094
+ for (const edge of sequence) guarded[graph.edgeTargets[edge]] = 1;
2095
+ }
2096
+ }
2097
+ const stateOfEdge = new Int32Array(edges).fill(-1);
2098
+ let count = 0;
2099
+ for (let edge = 0; edge < edges; edge += 1) if (guarded[graph.edgeTargets[edge]]) stateOfEdge[edge] = count++;
2100
+ const edgeOfState = new Int32Array(count);
2101
+ for (let edge = 0; edge < edges; edge += 1) if (stateOfEdge[edge] >= 0) edgeOfState[stateOfEdge[edge]] = edge;
2102
+ const index = { guarded, stateOfEdge, edgeOfState, count };
2103
+ guardedCache.set(graph, index);
2104
+ return index;
2105
+ }
2106
+
1567
2107
  // src/priority-queue.ts
1568
2108
  var MinPriorityQueue = class {
1569
2109
  values = [];
@@ -1608,6 +2148,7 @@ var MinPriorityQueue = class {
1608
2148
  };
1609
2149
 
1610
2150
  // src/router.ts
2151
+ var ROADS_TO_TRY = 6;
1611
2152
  function assertGraph(graph) {
1612
2153
  const nodes = graph.nodeLatitudes.length;
1613
2154
  const edges = graph.edgeTargets.length;
@@ -1632,7 +2173,7 @@ function findNearestNode(graph, coordinate, maxDistanceMeters = 500) {
1632
2173
  }
1633
2174
  return closest;
1634
2175
  }
1635
- function searchContext(graph, destinationPoint, preference) {
2176
+ function searchContext(graph, destinationPoint, preference, slowed) {
1636
2177
  const distanceScale = graph.edgeDistanceScale ?? 1;
1637
2178
  const durationScale = graph.edgeDurationScale ?? 1;
1638
2179
  const { maxSpeedMps, longitudeMetresPerDegree } = searchBoundsFor(graph);
@@ -1643,7 +2184,11 @@ function searchContext(graph, destinationPoint, preference) {
1643
2184
  const destinationLongitude = destinationPoint.lng;
1644
2185
  const toCost = preference === "fastest" ? 1 / maxSpeedMps : 1;
1645
2186
  return {
1646
- costOf: (edge) => preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale,
2187
+ // An overridden cost only ever exceeds the graph's own — a conditional
2188
+ // limit slows an edge and a penalty makes it dearer — so the heuristic
2189
+ // built from the graph's fastest edge stays a lower bound and the search
2190
+ // still returns the best route under the costs it was given.
2191
+ costOf: (edge) => slowed?.get(edge) ?? (preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale),
1647
2192
  // Called once per relaxation, millions of times on a long route, so it
1648
2193
  // reads the coordinate arrays directly and avoids a haversine's four
1649
2194
  // trigonometric calls for a number that only has to be a lower bound.
@@ -1656,8 +2201,8 @@ function searchContext(graph, destinationPoint, preference) {
1656
2201
  ) * toCost
1657
2202
  };
1658
2203
  }
1659
- function searchByNode(graph, seeds, finishers, destinationPoint, preference, costBound, access) {
1660
- const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference);
2204
+ function searchByNode(graph, seeds, finishers, destinationPoint, preference, costBound, access, slowed) {
2205
+ const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference, slowed);
1661
2206
  const nodeCount = graph.nodeLatitudes.length;
1662
2207
  const costs = new Float64Array(nodeCount).fill(Infinity);
1663
2208
  const previousNode = new Int32Array(nodeCount).fill(-1);
@@ -1705,8 +2250,8 @@ function searchByNode(graph, seeds, finishers, destinationPoint, preference, cos
1705
2250
  edges.reverse();
1706
2251
  return { edges, finisher: bestFinisher, seed: seedAt.get(node) };
1707
2252
  }
1708
- function searchWithRestrictions(graph, seeds, finishers, destinationPoint, preference, restrictions, sequenceIndex, costBound, access) {
1709
- const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference);
2253
+ function searchWithRestrictions(graph, seeds, finishers, destinationPoint, preference, restrictions, sequenceIndex, costBound, access, slowed) {
2254
+ const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference, slowed);
1710
2255
  const targets = graph.edgeTargets;
1711
2256
  const { guarded, stateOfEdge, edgeOfState, count: guardedStates } = guardedJunctionsFor(graph, restrictions, sequenceIndex);
1712
2257
  const nodes = graph.nodeLatitudes.length;
@@ -1875,8 +2420,24 @@ function finishersFor(snap, costOf) {
1875
2420
  function projectionOf(snap, edge) {
1876
2421
  return snap.kind === "edge" ? snap.projections.find((projection) => projection.edge === edge) : void 0;
1877
2422
  }
1878
- function snapSummary(snap) {
1879
- return snap.kind === "node" ? { node: snap.node, edge: -1, point: snap.point, distanceMeters: snap.distanceMeters } : { node: -1, edge: snap.projections[0].edge, point: snap.point, distanceMeters: snap.distanceMeters };
2423
+ function snapSummary(snap, travelledMeters = 0) {
2424
+ return snap.kind === "node" ? { node: snap.node, edge: -1, point: snap.point, distanceMeters: snap.distanceMeters, travelledMeters: 0 } : { node: -1, edge: snap.projections[0].edge, point: snap.point, distanceMeters: snap.distanceMeters, travelledMeters };
2425
+ }
2426
+ function withPenalties(graph, preference, slowed, penalties) {
2427
+ if (!penalties?.size) return slowed;
2428
+ const distanceScale = graph.edgeDistanceScale ?? 1;
2429
+ const durationScale = graph.edgeDurationScale ?? 1;
2430
+ const costs = new Map(slowed);
2431
+ for (const [edge, multiplier] of penalties) {
2432
+ const base = costs.get(edge) ?? (preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale);
2433
+ costs.set(edge, base * multiplier);
2434
+ }
2435
+ return costs;
2436
+ }
2437
+ function departureTime(options) {
2438
+ const departAt = options.departAt;
2439
+ if (!departAt) return void 0;
2440
+ return departAt instanceof Date ? travelTimeFrom(departAt, options.publicHoliday, options.timeZone) : { ...departAt, publicHoliday: departAt.publicHoliday ?? options.publicHoliday };
1880
2441
  }
1881
2442
  function nodeOfSnap(snap) {
1882
2443
  return snap.kind === "node" ? snap.node : snap.projections[0].source;
@@ -1901,24 +2462,67 @@ function reconnect(graph, from, to, preference, options, access) {
1901
2462
  }
1902
2463
  function route(graph, from, to, preference = "fastest", options = {}) {
1903
2464
  assertGraph(graph);
1904
- const access = accessForVehicle(graph, options.vehicle);
2465
+ const vehicleAccess = accessForVehicle(graph, options.vehicle);
2466
+ const timed = accessAtTime(graph, conditionsOf(graph), departureTime(options), options.vehicle, vehicleAccess, options.circumstances);
2467
+ const access = accessAvoiding(graph, options.avoid, timed);
1905
2468
  try {
1906
2469
  return routeBetween(graph, from, to, preference, options, access);
1907
2470
  } catch (error) {
1908
2471
  if (!(error instanceof RouterError) || error.code !== "NO_ROUTE") throw error;
1909
- return reconnect(graph, from, to, preference, options, access) ?? (() => {
2472
+ const mended = reconnect(graph, from, to, preference, options, access);
2473
+ if (mended) return mended;
2474
+ if (options.finishOnANearbyRoad === false) throw error;
2475
+ return onAnotherRoad(graph, from, to, preference, options, access) ?? (() => {
1910
2476
  throw error;
1911
2477
  })();
1912
2478
  }
1913
2479
  }
1914
- function routeBetween(graph, from, to, preference, options, access, acceptOrigin, acceptDestination) {
1915
- const originSnap = snapToGraph(graph, from, void 0, access, acceptOrigin);
2480
+ function roadsNear(graph, point, access) {
2481
+ const nearby = snapToGraph(graph, point, FALLBACK_SNAP_RADIUS_METERS, access, void 0, true);
2482
+ if (!nearby || nearby.kind === "node") return nearby ? [nearby] : [];
2483
+ const byRoad = /* @__PURE__ */ new Map();
2484
+ for (const projection of nearby.projections) {
2485
+ const key = `${Math.min(projection.source, projection.target)}:${Math.max(projection.source, projection.target)}`;
2486
+ const held = byRoad.get(key);
2487
+ if (held) held.push(projection);
2488
+ else byRoad.set(key, [projection]);
2489
+ }
2490
+ return [...byRoad.values()].map((projections) => ({
2491
+ kind: "edge",
2492
+ projections,
2493
+ point: projections[0].point,
2494
+ distanceMeters: projections[0].distanceMeters
2495
+ }));
2496
+ }
2497
+ function onAnotherRoad(graph, from, to, preference, options, access) {
2498
+ const attempt = (chosen) => {
2499
+ try {
2500
+ return routeBetween(graph, from, to, preference, options, access, void 0, void 0, chosen);
2501
+ } catch {
2502
+ return null;
2503
+ }
2504
+ };
2505
+ for (const destination of roadsNear(graph, to, access).slice(1, ROADS_TO_TRY + 1)) {
2506
+ const found = attempt({ destination });
2507
+ if (found) return found;
2508
+ }
2509
+ for (const origin of roadsNear(graph, from, access).slice(1, ROADS_TO_TRY + 1)) {
2510
+ const found = attempt({ origin });
2511
+ if (found) return found;
2512
+ }
2513
+ return null;
2514
+ }
2515
+ function routeBetween(graph, from, to, preference, options, access, acceptOrigin, acceptDestination, chosen) {
2516
+ const originSnap = chosen?.origin ?? snapToGraph(graph, from, void 0, access, acceptOrigin);
1916
2517
  if (!originSnap) throw new RouterError("ORIGIN_NOT_ON_GRAPH", "No routable road is close to the origin.");
1917
- const destinationSnap = snapToGraph(graph, to, void 0, access, acceptDestination);
2518
+ const destinationSnap = chosen?.destination ?? snapToGraph(graph, to, void 0, access, acceptDestination);
1918
2519
  if (!destinationSnap) throw new RouterError("DESTINATION_NOT_ON_GRAPH", "No routable road is close to the destination.");
1919
2520
  const distanceScale = graph.edgeDistanceScale ?? 1;
1920
2521
  const durationScale = graph.edgeDurationScale ?? 1;
1921
- const costOf = (edge) => preference === "fastest" ? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale;
2522
+ const when = departureTime(options);
2523
+ const conditionallySlowed = preference === "fastest" && when ? conditionalDurations(graph, conditionsOf(graph), when, options.vehicle, options.circumstances) : void 0;
2524
+ const slowed = withPenalties(graph, preference, conditionallySlowed, options.costPenalties);
2525
+ const costOf = (edge) => preference === "fastest" ? conditionallySlowed?.get(edge) ?? graph.edgeDurationsSeconds[edge] * durationScale : graph.edgeDistancesMeters[edge] * distanceScale;
1922
2526
  const seeds = seedsFor(originSnap, costOf);
1923
2527
  const finisherList = finishersFor(destinationSnap, costOf);
1924
2528
  const finishers = /* @__PURE__ */ new Map();
@@ -1940,7 +2544,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
1940
2544
  const costBound = options.costBound ?? Infinity;
1941
2545
  const restrictions = buildTurnRestrictionIndex(graph);
1942
2546
  const sequenceIndex = buildTurnSequenceIndex(graph);
1943
- const outcome = sameNode ? { edges: [], finisher: finisherList[0], seed: seeds[0] } : restrictions || sequenceIndex ? searchWithRestrictions(graph, seeds, finishers, destinationSnap.point, preference, restrictions, sequenceIndex, costBound, access) : searchByNode(graph, seeds, finishers, destinationSnap.point, preference, costBound, access);
2547
+ const outcome = sameNode ? { edges: [], finisher: finisherList[0], seed: seeds[0] } : restrictions || sequenceIndex ? searchWithRestrictions(graph, seeds, finishers, destinationSnap.point, preference, restrictions, sequenceIndex, costBound, access, slowed) : searchByNode(graph, seeds, finishers, destinationSnap.point, preference, costBound, access, slowed);
1944
2548
  if (direct && direct.cost >= costBound) direct = null;
1945
2549
  if (!outcome && !direct) throw new RouterError("NO_ROUTE", `No ${profileOf(graph).name} route connects the selected points.`);
1946
2550
  const searchCost = outcome ? outcome.seed.cost + outcome.edges.reduce((sum, edge) => sum + costOf(edge), 0) + outcome.finisher.cost : Infinity;
@@ -1954,11 +2558,14 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
1954
2558
  let edgeIds = [];
1955
2559
  let distanceMeters = 0;
1956
2560
  let durationSeconds = 0;
2561
+ let travelledAtOrigin = 0;
2562
+ let travelledAtDestination = 0;
1957
2563
  if (direct && direct.cost <= searchCost) {
1958
2564
  for (const point of geometryBetween(direct.origin, direct.destination)) push(point);
1959
2565
  const span = partial(direct.origin, direct.destination.fraction - direct.origin.fraction);
1960
2566
  distanceMeters = span.distance;
1961
2567
  durationSeconds = span.duration;
2568
+ travelledAtOrigin = span.distance;
1962
2569
  } else {
1963
2570
  const { edges, finisher, seed } = outcome;
1964
2571
  edgeIds = edges;
@@ -1969,6 +2576,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
1969
2576
  const span = partial(entry, 1 - entry.fraction);
1970
2577
  distanceMeters += span.distance;
1971
2578
  durationSeconds += span.duration;
2579
+ travelledAtOrigin = span.distance;
1972
2580
  nodeIds.push(entry.target);
1973
2581
  push(coordinateFor2(graph, entry.target));
1974
2582
  } else {
@@ -1977,7 +2585,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
1977
2585
  const scale = graph.coordinateScale ?? 1;
1978
2586
  for (const edge of edges) {
1979
2587
  distanceMeters += graph.edgeDistancesMeters[edge] * distanceScale;
1980
- durationSeconds += graph.edgeDurationsSeconds[edge] * durationScale;
2588
+ durationSeconds += conditionallySlowed?.get(edge) ?? graph.edgeDurationsSeconds[edge] * durationScale;
1981
2589
  if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
1982
2590
  for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
1983
2591
  coordinates.push([graph.edgeShapeLongitudes[point] / scale, graph.edgeShapeLatitudes[point] / scale]);
@@ -1992,6 +2600,7 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
1992
2600
  const span = partial(exit, exit.fraction);
1993
2601
  distanceMeters += span.distance;
1994
2602
  durationSeconds += span.duration;
2603
+ travelledAtDestination = span.distance;
1995
2604
  }
1996
2605
  }
1997
2606
  return {
@@ -1999,8 +2608,8 @@ function routeBetween(graph, from, to, preference, options, access, acceptOrigin
1999
2608
  edgeIds,
2000
2609
  distanceMeters,
2001
2610
  durationSeconds,
2002
- originSnap: snapSummary(originSnap),
2003
- destinationSnap: snapSummary(destinationSnap),
2611
+ originSnap: snapSummary(originSnap, travelledAtOrigin),
2612
+ destinationSnap: snapSummary(destinationSnap, travelledAtDestination),
2004
2613
  geometry: { type: "LineString", coordinates }
2005
2614
  };
2006
2615
  }
@@ -2013,8 +2622,22 @@ export {
2013
2622
  unzigzag,
2014
2623
  VarintWriter,
2015
2624
  VarintReader,
2016
- ProfileId,
2017
2625
  parseMaxSpeedKph,
2626
+ MAX_STORED_VALUES,
2627
+ parseConditional,
2628
+ conditionHolds,
2629
+ conditionalValue,
2630
+ unreadableConditions,
2631
+ travelTimeFrom,
2632
+ ConditionKind,
2633
+ packEdgeConditions,
2634
+ parseEdgeConditions,
2635
+ conditionsOnEdge,
2636
+ accessAtTime,
2637
+ countClosedAt,
2638
+ conditionsOf,
2639
+ conditionalDurations,
2640
+ ProfileId,
2018
2641
  parseLengthMeters,
2019
2642
  parseWeightTonnes,
2020
2643
  carRoutingProfile,
@@ -2037,6 +2660,17 @@ export {
2037
2660
  straightLineAtMost,
2038
2661
  componentsFor,
2039
2662
  componentSizes,
2663
+ edgeSource,
2664
+ edgePolyline,
2665
+ createEdgeSpatialIndex,
2666
+ edgeSpatialIndexFor,
2667
+ nearbyEdgeIds,
2668
+ createSpatialIndex,
2669
+ withSpatialIndex,
2670
+ snapToGraph,
2671
+ touchesArea,
2672
+ accessAvoiding,
2673
+ countAvoided,
2040
2674
  toDecimetres,
2041
2675
  toHalfTonnes,
2042
2676
  fromDecimetres,
@@ -2045,20 +2679,12 @@ export {
2045
2679
  accessForVehicle,
2046
2680
  countBlocked,
2047
2681
  RouterError,
2048
- createSpatialIndex,
2049
- withSpatialIndex,
2050
2682
  buildTurnRestrictionIndex,
2051
2683
  isTurnBanned,
2052
2684
  packTurnRestrictions,
2053
2685
  buildTurnSequenceIndex,
2054
2686
  packTurnSequences,
2055
2687
  guardedJunctionsFor,
2056
- edgeSource,
2057
- edgePolyline,
2058
- createEdgeSpatialIndex,
2059
- edgeSpatialIndexFor,
2060
- nearbyEdgeIds,
2061
- snapToGraph,
2062
2688
  findNearestNode,
2063
2689
  route
2064
2690
  };