roadgraph-router-v2 0.1.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.
@@ -0,0 +1,2690 @@
1
+ // src/access.ts
2
+ function accessMaskFor(edges, allowed) {
3
+ const mask = new Uint8Array(edges + 7 >> 3);
4
+ for (let edge = 0; edge < edges; edge += 1) if (allowed(edge)) mask[edge >> 3] |= 1 << (edge & 7);
5
+ return mask;
6
+ }
7
+ function edgeAllowed(mask, edge) {
8
+ return mask === void 0 || (mask[edge >> 3] & 1 << (edge & 7)) !== 0;
9
+ }
10
+ function allowsEverything(mask, edges) {
11
+ for (let edge = 0; edge < edges; edge += 1) if (!edgeAllowed(mask, edge)) return false;
12
+ return true;
13
+ }
14
+
15
+ // src/varint.ts
16
+ function zigzag(value) {
17
+ return (value << 1 ^ value >> 31) >>> 0;
18
+ }
19
+ function unzigzag(value) {
20
+ return value >>> 1 ^ -(value & 1);
21
+ }
22
+ var VarintWriter = class {
23
+ bytes;
24
+ length = 0;
25
+ constructor(capacity = 1024) {
26
+ this.bytes = new Uint8Array(capacity);
27
+ }
28
+ reserve(extra) {
29
+ if (this.length + extra <= this.bytes.length) return;
30
+ let capacity = this.bytes.length * 2;
31
+ while (capacity < this.length + extra) capacity *= 2;
32
+ const grown = new Uint8Array(capacity);
33
+ grown.set(this.bytes.subarray(0, this.length));
34
+ this.bytes = grown;
35
+ }
36
+ /** Appends an unsigned integer. Values must be below 2^32. */
37
+ push(value) {
38
+ this.reserve(5);
39
+ let remaining = value >>> 0;
40
+ while (remaining > 127) {
41
+ this.bytes[this.length++] = remaining & 127 | 128;
42
+ remaining >>>= 7;
43
+ }
44
+ this.bytes[this.length++] = remaining;
45
+ }
46
+ /** Appends a signed integer through zigzag, so small magnitudes stay short. */
47
+ pushSigned(value) {
48
+ this.push(zigzag(value));
49
+ }
50
+ get byteLength() {
51
+ return this.length;
52
+ }
53
+ toUint8Array() {
54
+ return this.bytes.subarray(0, this.length);
55
+ }
56
+ };
57
+ var VarintReader = class {
58
+ constructor(bytes, start = 0) {
59
+ this.bytes = bytes;
60
+ this.at = start;
61
+ }
62
+ bytes;
63
+ at;
64
+ read() {
65
+ let value = 0;
66
+ let shift = 0;
67
+ for (; ; ) {
68
+ const byte = this.bytes[this.at++];
69
+ value += shift < 28 ? (byte & 127) << shift : (byte & 127) * 2 ** shift;
70
+ if ((byte & 128) === 0) return value >>> 0;
71
+ shift += 7;
72
+ if (shift > 35) throw new Error("Malformed varint in routing graph.");
73
+ }
74
+ }
75
+ readSigned() {
76
+ return unzigzag(this.read());
77
+ }
78
+ get offset() {
79
+ return this.at;
80
+ }
81
+ };
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
+
442
+ // src/routing-profile.ts
443
+ var ProfileId = {
444
+ unspecified: 0,
445
+ car: 1,
446
+ truck: 2,
447
+ motorcycle: 3,
448
+ bicycle: 4,
449
+ pedestrian: 5
450
+ };
451
+ var MINIMUM_SPEED_KPH = 5;
452
+ var MAXIMUM_SPEED_KPH = 150;
453
+ var BLOCKED_ACCESS = /* @__PURE__ */ new Set(["no", "private", "agricultural", "forestry", "military", "emergency", "use_sidepath"]);
454
+ var MOTOR_VEHICLE_SPEEDS = {
455
+ motorway: 110,
456
+ motorway_link: 50,
457
+ trunk: 90,
458
+ trunk_link: 50,
459
+ primary: 70,
460
+ primary_link: 45,
461
+ secondary: 60,
462
+ secondary_link: 40,
463
+ tertiary: 50,
464
+ tertiary_link: 35,
465
+ unclassified: 35,
466
+ residential: 30,
467
+ living_street: 10,
468
+ service: 20,
469
+ road: 25
470
+ };
471
+ var TURN_RESTRICTION_KINDS = {
472
+ no_left_turn: "no",
473
+ no_right_turn: "no",
474
+ no_straight_on: "no",
475
+ no_u_turn: "no",
476
+ no_entry: "no",
477
+ no_exit: "no",
478
+ only_left_turn: "only",
479
+ only_right_turn: "only",
480
+ only_straight_on: "only",
481
+ only_u_turn: "only"
482
+ };
483
+ var BARRIER_DEFAULTS = {
484
+ bollard: { motor: false, bicycle: true, foot: true },
485
+ block: { motor: false, bicycle: true, foot: true },
486
+ jersey_barrier: { motor: false, bicycle: true, foot: true },
487
+ planter: { motor: false, bicycle: true, foot: true },
488
+ cycle_barrier: { motor: false, bicycle: false, foot: true },
489
+ stile: { motor: false, bicycle: false, foot: true },
490
+ turnstile: { motor: false, bicycle: false, foot: true },
491
+ kissing_gate: { motor: false, bicycle: false, foot: true },
492
+ fence: { motor: false, bicycle: false, foot: false },
493
+ wall: { motor: false, bicycle: false, foot: false },
494
+ hedge: { motor: false, bicycle: false, foot: false },
495
+ ditch: { motor: false, bicycle: false, foot: false },
496
+ // A gate or a boom is usually open, or opens for you; `access` and `locked`
497
+ // say when it is not.
498
+ gate: { motor: true, bicycle: true, foot: true },
499
+ lift_gate: { motor: true, bicycle: true, foot: true },
500
+ swing_gate: { motor: true, bicycle: true, foot: true },
501
+ bus_trap: { motor: false, bicycle: true, foot: true },
502
+ sally_port: { motor: true, bicycle: true, foot: true },
503
+ toll_booth: { motor: true, bicycle: true, foot: true },
504
+ border_control: { motor: true, bicycle: true, foot: true },
505
+ entrance: { motor: true, bicycle: true, foot: true }
506
+ };
507
+ var BARRIER_TAG_KEYS = ["barrier", "locked", "access", "foot", "bicycle", "vehicle", "motor_vehicle", "motorcar", "motorcycle", "hgv"];
508
+ function barrierAllows(tags, accessKeys, kind) {
509
+ const barrier = tags.barrier?.trim().toLowerCase();
510
+ if (!barrier || barrier === "no") return true;
511
+ let decided;
512
+ for (const key of accessKeys) {
513
+ const value = tags[key];
514
+ if (value !== void 0) decided = !BLOCKED_ACCESS.has(value);
515
+ }
516
+ if (decided !== void 0) return decided;
517
+ if (tags.locked?.trim().toLowerCase() === "yes") return false;
518
+ const defaults = BARRIER_DEFAULTS[barrier];
519
+ return defaults ? defaults[kind] : true;
520
+ }
521
+ var IMPLICITLY_ONEWAY = /* @__PURE__ */ new Set(["motorway", "motorway_link"]);
522
+ function clampSpeed(value) {
523
+ if (!Number.isFinite(value)) return MINIMUM_SPEED_KPH;
524
+ return Math.min(MAXIMUM_SPEED_KPH, Math.max(MINIMUM_SPEED_KPH, value));
525
+ }
526
+ var UNMEASURED = /* @__PURE__ */ new Set(["default", "below_default", "none", "unsigned", "no_sign", "no", "yes", "unknown"]);
527
+ var FEET_METERS = 0.3048;
528
+ var INCH_METERS = 0.0254;
529
+ function parseLengthMeters(value) {
530
+ if (!value) return null;
531
+ const text = value.trim().toLowerCase();
532
+ if (UNMEASURED.has(text)) return null;
533
+ const imperial = /^(\d+(?:\.\d+)?)\s*(?:'|ft|feet)\s*(?:(\d+(?:\.\d+)?)\s*(?:"|in|inch(?:es)?)?)?$/.exec(text);
534
+ if (imperial) {
535
+ const metres = Number(imperial[1]) * FEET_METERS + Number(imperial[2] ?? 0) * INCH_METERS;
536
+ return metres > 0 ? metres : null;
537
+ }
538
+ const metric = /^(\d+(?:\.\d+)?)\s*(m|metres?|meters?|cm)?$/.exec(text);
539
+ if (!metric) return null;
540
+ const amount = Number(metric[1]);
541
+ if (!Number.isFinite(amount) || amount <= 0) return null;
542
+ return metric[2] === "cm" ? amount / 100 : amount;
543
+ }
544
+ function parseWeightTonnes(value) {
545
+ if (!value) return null;
546
+ const text = value.trim().toLowerCase();
547
+ if (UNMEASURED.has(text)) return null;
548
+ const match = /^(\d+(?:\.\d+)?)\s*(t|tonnes?|tons?|kg|lbs?|pounds?)?$/.exec(text);
549
+ if (!match) return null;
550
+ const amount = Number(match[1]);
551
+ if (!Number.isFinite(amount) || amount <= 0) return null;
552
+ if (match[2] === "kg") return amount / 1e3;
553
+ if (match[2] === "lb" || match[2] === "lbs" || match[2] === "pound" || match[2] === "pounds") return amount * 45359237e-11;
554
+ return amount;
555
+ }
556
+ var LIMIT_KEYS = [
557
+ ["heightMeters", "maxheight", parseLengthMeters],
558
+ ["widthMeters", "maxwidth", parseLengthMeters],
559
+ ["lengthMeters", "maxlength", parseLengthMeters],
560
+ ["weightTonnes", "maxweight", parseWeightTonnes]
561
+ ];
562
+ function limitsFor(tags, mode, direction) {
563
+ const limits = {};
564
+ let found = false;
565
+ for (const [field, key, parse] of LIMIT_KEYS) {
566
+ const value = parse(tags[`${key}:${direction}`]) ?? parse(tags[`${key}:${mode}`]) ?? parse(tags[key]);
567
+ const physical = key === "maxheight" || key === "maxwidth" ? parse(tags[`${key}:physical`]) : null;
568
+ const resolved = value !== null && physical !== null ? Math.min(value, physical) : value ?? physical;
569
+ if (resolved !== null && resolved !== void 0) {
570
+ limits[field] = resolved;
571
+ found = true;
572
+ }
573
+ }
574
+ return found ? limits : void 0;
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
+ }
592
+ function accessAllowed(tags, keys) {
593
+ let allowed = true;
594
+ for (const key of keys) {
595
+ const value = tags[key];
596
+ if (value === void 0) continue;
597
+ allowed = !BLOCKED_ACCESS.has(value);
598
+ }
599
+ return allowed;
600
+ }
601
+ function onewayDirections(tags, highway, keys) {
602
+ let oneway;
603
+ for (const key of keys) if (tags[key] !== void 0) oneway = tags[key];
604
+ const value = oneway?.trim().toLowerCase();
605
+ if (value === "yes" || value === "true" || value === "1") return { forward: true, backward: false };
606
+ if (value === "-1" || value === "reverse") return { forward: false, backward: true };
607
+ if (value !== void 0 && value !== "") return { forward: true, backward: true };
608
+ const junction = tags.junction?.trim().toLowerCase();
609
+ if (junction === "roundabout" || junction === "circular") return { forward: true, backward: false };
610
+ if (highway && IMPLICITLY_ONEWAY.has(highway)) return { forward: true, backward: false };
611
+ return { forward: true, backward: true };
612
+ }
613
+ function motorVehicleProfile(options) {
614
+ const { name, id, mode } = options;
615
+ const speeds = options.speeds ?? MOTOR_VEHICLE_SPEEDS;
616
+ const ceiling = options.speedCeilingKph ?? MAXIMUM_SPEED_KPH;
617
+ const limit = (value) => Math.min(ceiling, value);
618
+ const accessKeys = ["access", "vehicle", "motor_vehicle", mode];
619
+ const onewayKeys = ["oneway", "oneway:motor_vehicle", `oneway:${mode}`];
620
+ const restrictionKeys = ["restriction", "restriction:vehicle", "restriction:motor_vehicle", `restriction:${mode}`];
621
+ const exceptModes = /* @__PURE__ */ new Set([mode, "motor_vehicle", "vehicle"]);
622
+ return {
623
+ name,
624
+ id,
625
+ wayTagKeys: [
626
+ "highway",
627
+ "junction",
628
+ "maxspeed",
629
+ "maxspeed:forward",
630
+ "maxspeed:backward",
631
+ `maxspeed:${mode}`,
632
+ ...accessKeys,
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`,
639
+ ...options.physicalLimits ? LIMIT_KEYS.flatMap(([, key]) => [key, `${key}:${mode}`, `${key}:forward`, `${key}:backward`, ...key === "maxheight" || key === "maxwidth" ? [`${key}:physical`] : []]) : []
640
+ ],
641
+ relationTagKeys: ["type", "except", ...restrictionKeys, ...restrictionKeys.map((key) => `${key}:conditional`)],
642
+ maxSpeedKph: ceiling,
643
+ nodeTagKeys: BARRIER_TAG_KEYS,
644
+ evaluateBarrier: (tags) => barrierAllows(tags, accessKeys, "motor"),
645
+ ...options.physicalLimits ? { usesPhysicalLimits: true } : {},
646
+ evaluateWay(tags) {
647
+ const highway = tags.highway;
648
+ if (!highway || !(highway in speeds)) return null;
649
+ if (!accessAllowed(tags, accessKeys)) return null;
650
+ const { forward, backward } = onewayDirections(tags, highway, onewayKeys);
651
+ if (!forward && !backward) return null;
652
+ const base = parseMaxSpeedKph(tags[`maxspeed:${mode}`]) ?? parseMaxSpeedKph(tags.maxspeed) ?? speeds[highway];
653
+ return {
654
+ forward,
655
+ backward,
656
+ // A vehicle class with a lower legal ceiling does not reach the posted
657
+ // limit on a road signed above it, so the ceiling caps the speed
658
+ // rather than only bounding the A* heuristic.
659
+ forwardSpeedKph: clampSpeed(limit(parseMaxSpeedKph(tags["maxspeed:forward"]) ?? base)),
660
+ backwardSpeedKph: clampSpeed(limit(parseMaxSpeedKph(tags["maxspeed:backward"]) ?? base)),
661
+ ...options.physicalLimits ? {
662
+ forwardLimits: limitsFor(tags, mode, "forward"),
663
+ backwardLimits: limitsFor(tags, mode, "backward")
664
+ } : {},
665
+ .../* @__PURE__ */ ((conditions) => conditions ? { conditions } : {})(conditionsFor(tags, accessKeys, ["maxspeed", `maxspeed:${mode}`]))
666
+ };
667
+ },
668
+ evaluateTurnRestriction(tags) {
669
+ if (tags.type !== "restriction") return null;
670
+ const except = tags.except?.split(";").map((value2) => value2.trim().toLowerCase()) ?? [];
671
+ if (except.some((value2) => exceptModes.has(value2))) return null;
672
+ let value;
673
+ for (const key of restrictionKeys) if (tags[key] !== void 0) value = tags[key];
674
+ if (!value) return null;
675
+ const kind = TURN_RESTRICTION_KINDS[value.trim().toLowerCase()];
676
+ return kind ? { kind, value } : null;
677
+ }
678
+ };
679
+ }
680
+ var carRoutingProfile = motorVehicleProfile({ name: "car", id: ProfileId.car, mode: "motorcar" });
681
+ var motorcycleRoutingProfile = motorVehicleProfile({ name: "motorcycle", id: ProfileId.motorcycle, mode: "motorcycle" });
682
+ var truckRoutingProfile = motorVehicleProfile({
683
+ name: "truck",
684
+ id: ProfileId.truck,
685
+ mode: "hgv",
686
+ // Heavy vehicles are held below the posted limit nearly everywhere; 90 is
687
+ // at or above the cap in the regimes this library has to serve at once.
688
+ speedCeilingKph: 90,
689
+ physicalLimits: true
690
+ });
691
+ var WALKING_SPEEDS = {
692
+ footway: 5,
693
+ // A shared path is often tagged as a cycleway; where walking is genuinely
694
+ // forbidden, `foot=no` says so and the access ladder catches it.
695
+ cycleway: 5,
696
+ path: 5,
697
+ pedestrian: 5,
698
+ living_street: 5,
699
+ residential: 5,
700
+ service: 5,
701
+ unclassified: 5,
702
+ road: 5,
703
+ tertiary: 5,
704
+ tertiary_link: 5,
705
+ secondary: 5,
706
+ secondary_link: 5,
707
+ primary: 5,
708
+ primary_link: 5,
709
+ track: 4,
710
+ bridleway: 4,
711
+ corridor: 5,
712
+ steps: 2
713
+ };
714
+ var CYCLING_SPEEDS = {
715
+ cycleway: 18,
716
+ residential: 15,
717
+ living_street: 10,
718
+ service: 12,
719
+ unclassified: 15,
720
+ road: 14,
721
+ tertiary: 16,
722
+ tertiary_link: 14,
723
+ secondary: 16,
724
+ secondary_link: 14,
725
+ primary: 15,
726
+ primary_link: 13,
727
+ path: 11,
728
+ footway: 7,
729
+ pedestrian: 7,
730
+ track: 9,
731
+ bridleway: 8
732
+ };
733
+ function activeTravelProfile(options) {
734
+ const { name, id, mode, speeds, ceilingKph, accessKeys, ignoresOneway, floorKph } = options;
735
+ const onewayKeys = [`oneway:${mode}`];
736
+ const restrictionKeys = ["restriction", `restriction:${mode}`];
737
+ const exceptModes = /* @__PURE__ */ new Set([mode]);
738
+ return {
739
+ name,
740
+ id,
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`)],
751
+ maxSpeedKph: ceilingKph,
752
+ nodeTagKeys: BARRIER_TAG_KEYS,
753
+ evaluateBarrier: (tags) => barrierAllows(tags, accessKeys, mode === "foot" ? "foot" : "bicycle"),
754
+ evaluateWay(tags) {
755
+ const highway = tags.highway;
756
+ if (!highway || !(highway in speeds)) return null;
757
+ if (!accessAllowed(tags, accessKeys)) return null;
758
+ const own = tags[`oneway:${mode}`]?.trim().toLowerCase();
759
+ let forward = true;
760
+ let backward = true;
761
+ if (own !== void 0 && own !== "") {
762
+ forward = own !== "-1" && own !== "reverse";
763
+ backward = own === "no" || own === "false" || own === "0" || own === "-1" || own === "reverse";
764
+ } else if (!ignoresOneway) {
765
+ ({ forward, backward } = onewayDirections(tags, highway, ["oneway"]));
766
+ }
767
+ if (!forward && !backward) return null;
768
+ const posted = parseMaxSpeedKph(tags.maxspeed);
769
+ const wanted = Math.min(speeds[highway], posted !== null && posted > 0 ? posted : Infinity);
770
+ const speed = Math.min(ceilingKph, Math.max(floorKph, wanted));
771
+ const conditions = conditionsFor(tags, accessKeys, []);
772
+ return { forward, backward, forwardSpeedKph: speed, backwardSpeedKph: speed, ...conditions ? { conditions } : {} };
773
+ },
774
+ evaluateTurnRestriction(tags) {
775
+ if (tags.type !== "restriction") return null;
776
+ const except = tags.except?.split(";").map((value2) => value2.trim().toLowerCase()) ?? [];
777
+ if (except.some((value2) => exceptModes.has(value2))) return null;
778
+ let value;
779
+ for (const key of restrictionKeys) if (tags[key] !== void 0) value = tags[key];
780
+ if (!value) return null;
781
+ const kind = TURN_RESTRICTION_KINDS[value.trim().toLowerCase()];
782
+ return kind ? { kind, value } : null;
783
+ }
784
+ };
785
+ }
786
+ var bicycleRoutingProfile = activeTravelProfile({
787
+ name: "bicycle",
788
+ id: ProfileId.bicycle,
789
+ mode: "bicycle",
790
+ speeds: CYCLING_SPEEDS,
791
+ ceilingKph: 30,
792
+ accessKeys: ["access", "vehicle", "bicycle"],
793
+ ignoresOneway: false,
794
+ floorKph: 4
795
+ });
796
+ var pedestrianRoutingProfile = activeTravelProfile({
797
+ name: "pedestrian",
798
+ id: ProfileId.pedestrian,
799
+ mode: "foot",
800
+ speeds: WALKING_SPEEDS,
801
+ ceilingKph: 7,
802
+ accessKeys: ["access", "foot"],
803
+ ignoresOneway: true,
804
+ floorKph: 1
805
+ });
806
+ var routingProfiles = {
807
+ car: carRoutingProfile,
808
+ motorcycle: motorcycleRoutingProfile,
809
+ truck: truckRoutingProfile,
810
+ bicycle: bicycleRoutingProfile,
811
+ pedestrian: pedestrianRoutingProfile
812
+ };
813
+ function getRoutingProfile(name) {
814
+ const profile = routingProfiles[name];
815
+ if (!profile) throw new Error(`Unknown routing profile "${name}". Available: ${Object.keys(routingProfiles).join(", ")}.`);
816
+ return profile;
817
+ }
818
+ function getRoutingProfileById(id) {
819
+ if (id === ProfileId.unspecified) return carRoutingProfile;
820
+ return Object.values(routingProfiles).find((profile) => profile.id === id) ?? null;
821
+ }
822
+ function profileOf(graph) {
823
+ return getRoutingProfileById(graph.profileId ?? ProfileId.unspecified) ?? carRoutingProfile;
824
+ }
825
+ function maxSpeedMpsFor(graph) {
826
+ return (graph.maxSpeedKph ?? profileOf(graph).maxSpeedKph) * 1e3 / 3600;
827
+ }
828
+
829
+ // src/codec.ts
830
+ var MAGIC = 1162236500;
831
+ var VERSION = 1;
832
+ var HEADER_BYTES = 16;
833
+ var COMPACT_MAGIC = 844252741;
834
+ var COMPACT_VERSION = 2;
835
+ var SHAPED_COMPACT_VERSION = 3;
836
+ var RESTRICTED_COMPACT_VERSION = 4;
837
+ var SEQUENCED_COMPACT_VERSION = 5;
838
+ var COMPACT_HEADER_BYTES = 32;
839
+ var SHAPED_COMPACT_HEADER_BYTES = 40;
840
+ var SUPPORTED_COMPACT_VERSIONS = /* @__PURE__ */ new Set([COMPACT_VERSION, SHAPED_COMPACT_VERSION, RESTRICTED_COMPACT_VERSION, SEQUENCED_COMPACT_VERSION, 6, 7]);
841
+ function encodeGraph(graph) {
842
+ const nodes = graph.nodeLatitudes.length, edges = graph.edgeTargets.length;
843
+ if (graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) throw new Error("Invalid road graph");
844
+ const buffer = new ArrayBuffer(HEADER_BYTES + nodes * 16 + (nodes + 1) * 4 + edges * 12);
845
+ const view = new DataView(buffer);
846
+ view.setUint32(0, MAGIC, true);
847
+ view.setUint16(4, VERSION, true);
848
+ view.setUint32(8, nodes, true);
849
+ view.setUint32(12, edges, true);
850
+ const coordinateScale = graph.coordinateScale ?? 1;
851
+ const distanceScale = graph.edgeDistanceScale ?? 1;
852
+ const durationScale = graph.edgeDurationScale ?? 1;
853
+ let offset = HEADER_BYTES;
854
+ new Float64Array(buffer, offset, nodes).set(Float64Array.from(graph.nodeLatitudes, (value) => value / coordinateScale));
855
+ offset += nodes * 8;
856
+ new Float64Array(buffer, offset, nodes).set(Float64Array.from(graph.nodeLongitudes, (value) => value / coordinateScale));
857
+ offset += nodes * 8;
858
+ new Uint32Array(buffer, offset, nodes + 1).set(graph.edgeOffsets);
859
+ offset += (nodes + 1) * 4;
860
+ new Uint32Array(buffer, offset, edges).set(graph.edgeTargets);
861
+ offset += edges * 4;
862
+ new Float32Array(buffer, offset, edges).set(Float32Array.from(graph.edgeDistancesMeters, (value) => value * distanceScale));
863
+ offset += edges * 4;
864
+ new Float32Array(buffer, offset, edges).set(Float32Array.from(graph.edgeDurationsSeconds, (value) => value * durationScale));
865
+ return buffer;
866
+ }
867
+ function decodeGraph(buffer) {
868
+ if (buffer.byteLength < HEADER_BYTES) throw new Error("Invalid routing graph header");
869
+ const view = new DataView(buffer), magic = view.getUint32(0, true), version = view.getUint16(4, true);
870
+ if (magic === COMPACT_MAGIC && SUPPORTED_COMPACT_VERSIONS.has(version)) return decodeCompactGraph(buffer);
871
+ if (magic !== MAGIC || version !== VERSION) throw new Error("Unsupported routing graph format");
872
+ const nodes = view.getUint32(8, true), edges = view.getUint32(12, true), expected = HEADER_BYTES + nodes * 16 + (nodes + 1) * 4 + edges * 12;
873
+ if (buffer.byteLength !== expected) throw new Error("Truncated routing graph");
874
+ let offset = HEADER_BYTES;
875
+ const nodeLatitudes = new Float64Array(buffer.slice(offset, offset += nodes * 8));
876
+ const nodeLongitudes = new Float64Array(buffer.slice(offset, offset += nodes * 8));
877
+ const edgeOffsets = new Uint32Array(buffer.slice(offset, offset += (nodes + 1) * 4));
878
+ const edgeTargets = new Uint32Array(buffer.slice(offset, offset += edges * 4));
879
+ const edgeDistancesMeters = new Float32Array(buffer.slice(offset, offset += edges * 4));
880
+ const edgeDurationsSeconds = new Float32Array(buffer.slice(offset, offset += edges * 4));
881
+ assertGeographic(nodeLatitudes, nodeLongitudes, 1);
882
+ for (const target of edgeTargets) if (target >= nodes) throw new Error("Invalid routing graph: edge target outside the graph.");
883
+ assertOffsets(edgeOffsets, nodes, edges, "edge offsets");
884
+ return { nodeLatitudes, nodeLongitudes, edgeOffsets, edgeTargets, edgeDistancesMeters, edgeDurationsSeconds };
885
+ }
886
+ function encodeCompactGraph(graph, options = {}) {
887
+ return (options.layout ?? "packed") === "packed" ? encodePackedGraph(graph) : encodeLegacyCompactGraph(graph);
888
+ }
889
+ function encodeLegacyCompactGraph(graph) {
890
+ if (graph.profileId) throw new Error("The legacy EFR2 layout cannot store a routing profile. Use the packed layout.");
891
+ if (!(graph.nodeLatitudes instanceof Int32Array) || !(graph.nodeLongitudes instanceof Int32Array) || !(graph.edgeDistancesMeters instanceof Uint16Array) || !(graph.edgeDurationsSeconds instanceof Uint16Array)) throw new Error("Compact graphs require Int32 coordinates and Uint16 edge weights.");
892
+ const coordinateScale = graph.coordinateScale ?? 0, edgeDistanceScale = graph.edgeDistanceScale ?? 1, edgeDurationScale = graph.edgeDurationScale ?? 1;
893
+ if (!Number.isInteger(coordinateScale) || !Number.isInteger(edgeDistanceScale) || !Number.isInteger(edgeDurationScale) || coordinateScale <= 0 || edgeDistanceScale <= 0 || edgeDurationScale <= 0 || edgeDistanceScale > 65535 || edgeDurationScale > 65535) throw new Error("Invalid compact graph scales.");
894
+ const nodes = graph.nodeLatitudes.length, edges = graph.edgeTargets.length;
895
+ if (graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) throw new Error("Invalid road graph");
896
+ const sequenceEdges = graph.turnRestrictionSequenceEdges?.length ?? 0;
897
+ const sequences = sequenceEdges ? (graph.turnRestrictionSequenceOffsets?.length ?? 1) - 1 : 0;
898
+ if (sequences && (!graph.turnRestrictionSequenceOffsets || graph.turnRestrictionSequenceOffsets[sequences] !== sequenceEdges)) throw new Error("Invalid compact turn restriction sequences.");
899
+ for (let index = 0; index < sequenceEdges; index += 1) if (graph.turnRestrictionSequenceEdges[index] >= edges) throw new Error("Turn restriction references an edge outside the graph.");
900
+ const restricted = Boolean(graph.turnRestrictionFromEdges?.length || graph.turnRestrictionToEdges?.length) || sequences > 0;
901
+ if ((graph.turnRestrictionFromEdges?.length ?? 0) !== (graph.turnRestrictionToEdges?.length ?? 0)) throw new Error("Invalid compact turn restrictions.");
902
+ const restrictions = graph.turnRestrictionFromEdges?.length ?? 0;
903
+ for (let pair = 0; pair < restrictions; pair += 1) if (graph.turnRestrictionFromEdges[pair] >= edges || graph.turnRestrictionToEdges[pair] >= edges) throw new Error("Turn restriction references an edge outside the graph.");
904
+ const hasShapes = Boolean(graph.edgeShapeOffsets || graph.edgeShapeLatitudes || graph.edgeShapeLongitudes);
905
+ if (hasShapes && (!graph.edgeShapeOffsets || !graph.edgeShapeLatitudes || !graph.edgeShapeLongitudes || graph.edgeShapeOffsets.length !== edges + 1 || graph.edgeShapeLatitudes.length !== graph.edgeShapeLongitudes.length || graph.edgeShapeOffsets[edges] !== graph.edgeShapeLatitudes.length)) throw new Error("Invalid compact edge geometry.");
906
+ const shaped = hasShapes || restricted;
907
+ const version = sequences ? SEQUENCED_COMPACT_VERSION : restricted ? RESTRICTED_COMPACT_VERSION : hasShapes ? SHAPED_COMPACT_VERSION : COMPACT_VERSION;
908
+ const points = graph.edgeShapeLatitudes?.length ?? 0, header = shaped ? SHAPED_COMPACT_HEADER_BYTES : COMPACT_HEADER_BYTES;
909
+ const buffer = new ArrayBuffer(header + nodes * 8 + (nodes + 1) * 4 + edges * 8 + (shaped ? (edges + 1) * 4 + points * 8 : 0) + restrictions * 8 + (sequences ? (sequences + 1) * 4 + sequenceEdges * 4 : 0));
910
+ const view = new DataView(buffer);
911
+ view.setUint32(0, COMPACT_MAGIC, true);
912
+ view.setUint16(4, version, true);
913
+ view.setUint32(8, nodes, true);
914
+ view.setUint32(12, edges, true);
915
+ view.setUint32(16, coordinateScale, true);
916
+ view.setUint16(20, edgeDistanceScale, true);
917
+ view.setUint16(22, edgeDurationScale, true);
918
+ if (shaped) {
919
+ view.setUint32(24, points, true);
920
+ view.setUint32(28, restrictions, true);
921
+ view.setUint32(32, sequences, true);
922
+ view.setUint32(36, sequenceEdges, true);
923
+ }
924
+ let offset = header;
925
+ new Int32Array(buffer, offset, nodes).set(graph.nodeLatitudes);
926
+ offset += nodes * 4;
927
+ new Int32Array(buffer, offset, nodes).set(graph.nodeLongitudes);
928
+ offset += nodes * 4;
929
+ new Uint32Array(buffer, offset, nodes + 1).set(graph.edgeOffsets);
930
+ offset += (nodes + 1) * 4;
931
+ new Uint32Array(buffer, offset, edges).set(graph.edgeTargets);
932
+ offset += edges * 4;
933
+ new Uint16Array(buffer, offset, edges).set(graph.edgeDistancesMeters);
934
+ offset += edges * 2;
935
+ new Uint16Array(buffer, offset, edges).set(graph.edgeDurationsSeconds);
936
+ offset += edges * 2;
937
+ if (shaped) {
938
+ if (hasShapes) new Uint32Array(buffer, offset, edges + 1).set(graph.edgeShapeOffsets);
939
+ offset += (edges + 1) * 4;
940
+ new Int32Array(buffer, offset, points).set(graph.edgeShapeLatitudes ?? []);
941
+ offset += points * 4;
942
+ new Int32Array(buffer, offset, points).set(graph.edgeShapeLongitudes ?? []);
943
+ offset += points * 4;
944
+ }
945
+ if (restrictions) {
946
+ new Uint32Array(buffer, offset, restrictions).set(graph.turnRestrictionFromEdges);
947
+ offset += restrictions * 4;
948
+ new Uint32Array(buffer, offset, restrictions).set(graph.turnRestrictionToEdges);
949
+ offset += restrictions * 4;
950
+ }
951
+ if (sequences) {
952
+ new Uint32Array(buffer, offset, sequences + 1).set(graph.turnRestrictionSequenceOffsets);
953
+ offset += (sequences + 1) * 4;
954
+ new Uint32Array(buffer, offset, sequenceEdges).set(graph.turnRestrictionSequenceEdges);
955
+ }
956
+ return buffer;
957
+ }
958
+ function decodeCompactGraph(buffer) {
959
+ if (buffer.byteLength < COMPACT_HEADER_BYTES) throw new Error("Invalid compact routing graph header");
960
+ const view = new DataView(buffer), version = view.getUint16(4, true), nodes = view.getUint32(8, true), edges = view.getUint32(12, true), coordinateScale = view.getUint32(16, true), edgeDistanceScale = view.getUint16(20, true), edgeDurationScale = view.getUint16(22, true);
961
+ if (version === 6) return decodePackedGraph(buffer);
962
+ if (version === SPLIT_COMPACT_VERSION) {
963
+ throw new Error(view.getUint8(6) === ChunkKind.profile ? "This is a routing profile chunk; it has to be combined with the shared chunk it belongs to." : "This routing graph stores several profiles; load it with a profile chunk rather than on its own.");
964
+ }
965
+ const shaped = version >= SHAPED_COMPACT_VERSION, points = shaped ? view.getUint32(24, true) : 0, header = shaped ? SHAPED_COMPACT_HEADER_BYTES : COMPACT_HEADER_BYTES;
966
+ const restrictions = version >= RESTRICTED_COMPACT_VERSION ? view.getUint32(28, true) : 0;
967
+ const sequences = version >= SEQUENCED_COMPACT_VERSION ? view.getUint32(32, true) : 0;
968
+ const sequenceEdges = version >= SEQUENCED_COMPACT_VERSION ? view.getUint32(36, true) : 0;
969
+ const expected = header + nodes * 8 + (nodes + 1) * 4 + edges * 8 + (shaped ? (edges + 1) * 4 + points * 8 : 0) + restrictions * 8 + (sequences ? (sequences + 1) * 4 + sequenceEdges * 4 : 0);
970
+ if (!coordinateScale || !edgeDistanceScale || !edgeDurationScale || buffer.byteLength !== expected) throw new Error("Truncated compact routing graph");
971
+ let offset = header;
972
+ const nodeLatitudes = new Int32Array(buffer.slice(offset, offset += nodes * 4));
973
+ const nodeLongitudes = new Int32Array(buffer.slice(offset, offset += nodes * 4));
974
+ const edgeOffsets = new Uint32Array(buffer.slice(offset, offset += (nodes + 1) * 4));
975
+ const edgeTargets = new Uint32Array(buffer.slice(offset, offset += edges * 4));
976
+ const edgeDistancesMeters = new Uint16Array(buffer.slice(offset, offset += edges * 2));
977
+ const edgeDurationsSeconds = new Uint16Array(buffer.slice(offset, offset += edges * 2));
978
+ assertGeographic(nodeLatitudes, nodeLongitudes, coordinateScale);
979
+ for (const target of edgeTargets) if (target >= nodes) throw new Error("Invalid routing graph: edge target outside the graph.");
980
+ assertOffsets(edgeOffsets, nodes, edges, "edge offsets");
981
+ const graph = { nodeLatitudes, nodeLongitudes, edgeOffsets, edgeTargets, edgeDistancesMeters, edgeDurationsSeconds, coordinateScale, edgeDistanceScale, edgeDurationScale };
982
+ if (!shaped) return graph;
983
+ const edgeShapeOffsets = new Uint32Array(buffer.slice(offset, offset += (edges + 1) * 4));
984
+ const edgeShapeLatitudes = new Int32Array(buffer.slice(offset, offset += points * 4));
985
+ const edgeShapeLongitudes = new Int32Array(buffer.slice(offset, offset += points * 4));
986
+ if (points) {
987
+ assertOffsets(edgeShapeOffsets, edges, points, "shape offsets");
988
+ assertGeographic(edgeShapeLatitudes, edgeShapeLongitudes, coordinateScale);
989
+ Object.assign(graph, { edgeShapeOffsets, edgeShapeLatitudes, edgeShapeLongitudes });
990
+ }
991
+ if (restrictions) {
992
+ graph.turnRestrictionFromEdges = new Uint32Array(buffer.slice(offset, offset += restrictions * 4));
993
+ graph.turnRestrictionToEdges = new Uint32Array(buffer.slice(offset, offset += restrictions * 4));
994
+ }
995
+ if (sequences) {
996
+ graph.turnRestrictionSequenceOffsets = new Uint32Array(buffer.slice(offset, offset += (sequences + 1) * 4));
997
+ graph.turnRestrictionSequenceEdges = new Uint32Array(buffer.slice(offset, offset += sequenceEdges * 4));
998
+ assertOffsets(graph.turnRestrictionSequenceOffsets, sequences, sequenceEdges, "turn restriction offsets");
999
+ }
1000
+ for (const edge of [...graph.turnRestrictionFromEdges ?? [], ...graph.turnRestrictionToEdges ?? [], ...graph.turnRestrictionSequenceEdges ?? []]) {
1001
+ if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
1002
+ }
1003
+ return graph;
1004
+ }
1005
+ var PACKED_COMPACT_VERSION = 6;
1006
+ var SPLIT_COMPACT_VERSION = 7;
1007
+ var ChunkKind = { shared: 0, profile: 1 };
1008
+ var PACKED_HEADER_BYTES = 64;
1009
+ var MAX_PLAUSIBLE_SPEED_KPH = 400;
1010
+ function alignUp(offset) {
1011
+ return offset + 3 & ~3;
1012
+ }
1013
+ function assertOffsets(offsets, entries, total, what) {
1014
+ if (offsets.length !== entries + 1 || offsets[0] !== 0 || offsets[entries] !== total) throw new Error(`Invalid routing graph: ${what} do not span the data.`);
1015
+ for (let index = 1; index <= entries; index += 1) {
1016
+ if (offsets[index] < offsets[index - 1] || offsets[index] > total) throw new Error(`Invalid routing graph: ${what} are not ordered.`);
1017
+ }
1018
+ }
1019
+ function assertGeographic(latitudes, longitudes, scale) {
1020
+ for (let index = 0; index < latitudes.length; index += 1) {
1021
+ if (!(Math.abs(latitudes[index] / scale) <= 90) || !(Math.abs(longitudes[index] / scale) <= 180)) {
1022
+ throw new Error("Invalid routing graph: coordinate outside the world.");
1023
+ }
1024
+ }
1025
+ }
1026
+ function buildShapeTable(graph) {
1027
+ const edges = graph.edgeTargets.length;
1028
+ const offsets = graph.edgeShapeOffsets;
1029
+ const latitudes = graph.edgeShapeLatitudes;
1030
+ const longitudes = graph.edgeShapeLongitudes;
1031
+ const references = new Uint32Array(edges);
1032
+ const shapes = [];
1033
+ if (!offsets || !latitudes || !longitudes) return { references, shapes, latitudes, longitudes };
1034
+ const seen = /* @__PURE__ */ new Map();
1035
+ for (let edge = 0; edge < edges; edge += 1) {
1036
+ const start = offsets[edge];
1037
+ const count = offsets[edge + 1] - start;
1038
+ if (!count) continue;
1039
+ const forward = [];
1040
+ const backward = [];
1041
+ for (let point = 0; point < count; point += 1) {
1042
+ forward.push(latitudes[start + point], longitudes[start + point]);
1043
+ backward.push(latitudes[start + count - 1 - point], longitudes[start + count - 1 - point]);
1044
+ }
1045
+ const forwardKey = forward.join(",");
1046
+ const existing = seen.get(forwardKey);
1047
+ if (existing !== void 0) {
1048
+ references[edge] = (existing + 1) * 2;
1049
+ continue;
1050
+ }
1051
+ const mirrored = seen.get(backward.join(","));
1052
+ if (mirrored !== void 0) {
1053
+ references[edge] = (mirrored + 1) * 2 + 1;
1054
+ continue;
1055
+ }
1056
+ const id = shapes.length;
1057
+ shapes.push({ start, count });
1058
+ seen.set(forwardKey, id);
1059
+ references[edge] = (id + 1) * 2;
1060
+ }
1061
+ return { references, shapes, latitudes, longitudes };
1062
+ }
1063
+ function encodeSharedGraph(graph) {
1064
+ return encodePackedGraph(graph, SPLIT_COMPACT_VERSION);
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
+ }
1092
+ function encodePackedGraph(graph, version = PACKED_COMPACT_VERSION) {
1093
+ const nodes = graph.nodeLatitudes.length;
1094
+ const edges = graph.edgeTargets.length;
1095
+ if (!(graph.nodeLatitudes instanceof Int32Array) || !(graph.nodeLongitudes instanceof Int32Array) || !(graph.edgeDistancesMeters instanceof Uint16Array) || !(graph.edgeDurationsSeconds instanceof Uint16Array)) throw new Error("Compact graphs require Int32 coordinates and Uint16 edge weights.");
1096
+ if (graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) throw new Error("Invalid road graph");
1097
+ const coordinateScale = graph.coordinateScale ?? 0;
1098
+ if (!Number.isInteger(coordinateScale) || coordinateScale <= 0) throw new Error("Invalid compact graph scales.");
1099
+ if ((graph.turnRestrictionFromEdges?.length ?? 0) !== (graph.turnRestrictionToEdges?.length ?? 0)) throw new Error("Invalid compact turn restrictions.");
1100
+ for (const edge of graph.turnRestrictionFromEdges ?? []) if (edge >= edges) throw new Error("Turn restriction references an edge outside the graph.");
1101
+ for (const edge of graph.turnRestrictionToEdges ?? []) if (edge >= edges) throw new Error("Turn restriction references an edge outside the graph.");
1102
+ for (const edge of graph.turnRestrictionSequenceEdges ?? []) if (edge >= edges) throw new Error("Turn restriction references an edge outside the graph.");
1103
+ const { references, shapes, latitudes, longitudes } = buildShapeTable(graph);
1104
+ const degrees = new VarintWriter(nodes + 16);
1105
+ for (let node = 0; node < nodes; node += 1) degrees.push(graph.edgeOffsets[node + 1] - graph.edgeOffsets[node]);
1106
+ const targets = new VarintWriter(edges + 16);
1107
+ for (let node = 0; node < nodes; node += 1) {
1108
+ for (let edge = graph.edgeOffsets[node]; edge < graph.edgeOffsets[node + 1]; edge += 1) targets.pushSigned(graph.edgeTargets[edge] - node);
1109
+ }
1110
+ const shapeRefs = new VarintWriter(edges + 16);
1111
+ for (let edge = 0; edge < edges; edge += 1) shapeRefs.push(references[edge]);
1112
+ const shapeSizes = new VarintWriter(shapes.length + 16);
1113
+ for (const shape of shapes) shapeSizes.push(shape.count);
1114
+ const shapePoints = new VarintWriter(shapes.length * 8 + 16);
1115
+ for (const shape of shapes) {
1116
+ let previousLat = 0;
1117
+ let previousLng = 0;
1118
+ for (let point = 0; point < shape.count; point += 1) {
1119
+ const lat = latitudes[shape.start + point];
1120
+ const lng = longitudes[shape.start + point];
1121
+ shapePoints.pushSigned(point ? lat - previousLat : lat);
1122
+ shapePoints.pushSigned(point ? lng - previousLng : lng);
1123
+ previousLat = lat;
1124
+ previousLng = lng;
1125
+ }
1126
+ }
1127
+ const pairs = graph.turnRestrictionFromEdges?.length ?? 0;
1128
+ const runs = graph.turnRestrictionSequenceEdges?.length ? (graph.turnRestrictionSequenceOffsets?.length ?? 1) - 1 : 0;
1129
+ const runEdges = graph.turnRestrictionSequenceEdges?.length ?? 0;
1130
+ const shared = version === SPLIT_COMPACT_VERSION;
1131
+ const coordinates = new VarintWriter(nodes * 4 + 16);
1132
+ if (shared) {
1133
+ let previous = 0;
1134
+ for (let node = 0; node < nodes; node += 1) {
1135
+ coordinates.pushSigned(graph.nodeLatitudes[node] - previous);
1136
+ previous = graph.nodeLatitudes[node];
1137
+ }
1138
+ previous = 0;
1139
+ for (let node = 0; node < nodes; node += 1) {
1140
+ coordinates.pushSigned(graph.nodeLongitudes[node] - previous);
1141
+ previous = graph.nodeLongitudes[node];
1142
+ }
1143
+ }
1144
+ const sections = shared ? [coordinates, degrees, targets, shapeRefs, shapeSizes, shapePoints] : [degrees, targets, shapeRefs, shapeSizes, shapePoints];
1145
+ const variableBytes = sections.reduce((sum, section) => sum + section.byteLength, 0);
1146
+ const weightBytes = edges * (shared ? 2 : 4);
1147
+ const restrictionsAt = alignUp(PACKED_HEADER_BYTES + (shared ? 0 : nodes * 8) + weightBytes + variableBytes);
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);
1156
+ const view = new DataView(buffer);
1157
+ view.setUint32(0, COMPACT_MAGIC, true);
1158
+ view.setUint16(4, version, true);
1159
+ view.setUint8(6, shared ? ChunkKind.shared : ChunkKind.profile);
1160
+ view.setUint32(8, nodes, true);
1161
+ view.setUint32(12, edges, true);
1162
+ view.setUint32(16, graph.coordinateScale ?? 0, true);
1163
+ view.setUint16(20, graph.edgeDistanceScale ?? 1, true);
1164
+ view.setUint16(22, graph.edgeDurationScale ?? 1, true);
1165
+ view.setUint32(24, shapes.length, true);
1166
+ view.setUint32(28, pairs, true);
1167
+ view.setUint32(32, runs, true);
1168
+ view.setUint32(36, runEdges, true);
1169
+ for (let index = 0; index < sections.length; index += 1) view.setUint32(40 + index * 4, sections[index].byteLength, true);
1170
+ if (!shared) {
1171
+ view.setUint8(60, graph.profileId ?? 0);
1172
+ view.setUint8(61, conditionRows ? 1 : 0);
1173
+ view.setUint16(62, Math.round(graph.maxSpeedKph ?? 0), true);
1174
+ }
1175
+ let offset = PACKED_HEADER_BYTES;
1176
+ if (!shared) {
1177
+ new Int32Array(buffer, offset, nodes).set(graph.nodeLatitudes);
1178
+ offset += nodes * 4;
1179
+ new Int32Array(buffer, offset, nodes).set(graph.nodeLongitudes);
1180
+ offset += nodes * 4;
1181
+ }
1182
+ new Uint16Array(buffer, offset, edges).set(graph.edgeDistancesMeters);
1183
+ offset += edges * 2;
1184
+ if (!shared) {
1185
+ new Uint16Array(buffer, offset, edges).set(graph.edgeDurationsSeconds);
1186
+ offset += edges * 2;
1187
+ }
1188
+ const bytes = new Uint8Array(buffer);
1189
+ for (const section of sections) {
1190
+ bytes.set(section.toUint8Array(), offset);
1191
+ offset += section.byteLength;
1192
+ }
1193
+ offset = restrictionsAt;
1194
+ if (pairs) {
1195
+ new Uint32Array(buffer, offset, pairs).set(graph.turnRestrictionFromEdges);
1196
+ offset += pairs * 4;
1197
+ new Uint32Array(buffer, offset, pairs).set(graph.turnRestrictionToEdges);
1198
+ offset += pairs * 4;
1199
+ }
1200
+ if (runs) {
1201
+ new Uint32Array(buffer, offset, runs + 1).set(graph.turnRestrictionSequenceOffsets);
1202
+ offset += (runs + 1) * 4;
1203
+ new Uint32Array(buffer, offset, runEdges).set(graph.turnRestrictionSequenceEdges);
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
+ }
1213
+ return buffer;
1214
+ }
1215
+ function decodePackedGraph(buffer, shared = false) {
1216
+ const view = new DataView(buffer);
1217
+ const nodes = view.getUint32(8, true);
1218
+ const edges = view.getUint32(12, true);
1219
+ const coordinateScale = view.getUint32(16, true);
1220
+ const edgeDistanceScale = view.getUint16(20, true);
1221
+ const edgeDurationScale = view.getUint16(22, true);
1222
+ const shapeCount = view.getUint32(24, true);
1223
+ const pairs = view.getUint32(28, true);
1224
+ const runs = view.getUint32(32, true);
1225
+ const runEdges = view.getUint32(36, true);
1226
+ const lengths = Array.from({ length: shared ? 6 : 5 }, (_, index) => view.getUint32(40 + index * 4, true));
1227
+ const variableBytes = lengths.reduce((sum, length) => sum + length, 0);
1228
+ const restrictionsAt = alignUp(PACKED_HEADER_BYTES + (shared ? 0 : nodes * 8) + edges * (shared ? 2 : 4) + variableBytes);
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
+ }
1240
+ if (!coordinateScale || !edgeDistanceScale || !edgeDurationScale || buffer.byteLength !== expected) throw new Error("Truncated compact routing graph");
1241
+ if (shapeCount > edges) throw new Error("Invalid compact routing graph: more shapes than edges.");
1242
+ const profileId = shared ? 0 : view.getUint8(60);
1243
+ const maxSpeedKph = shared ? 0 : view.getUint16(62, true);
1244
+ if (profileId && !getRoutingProfileById(profileId)) throw new Error(`Routing graph was compiled for an unsupported profile (id ${profileId}).`);
1245
+ if (maxSpeedKph > MAX_PLAUSIBLE_SPEED_KPH) throw new Error("Invalid compact routing graph: implausible profile speed ceiling.");
1246
+ let offset = PACKED_HEADER_BYTES;
1247
+ const nodeLatitudes = new Int32Array(nodes);
1248
+ const nodeLongitudes = new Int32Array(nodes);
1249
+ if (!shared) {
1250
+ nodeLatitudes.set(new Int32Array(buffer.slice(offset, offset += nodes * 4)));
1251
+ nodeLongitudes.set(new Int32Array(buffer.slice(offset, offset += nodes * 4)));
1252
+ }
1253
+ const edgeDistancesMeters = new Uint16Array(buffer.slice(offset, offset += edges * 2));
1254
+ const edgeDurationsSeconds = shared ? new Uint16Array(edges) : new Uint16Array(buffer.slice(offset, offset += edges * 2));
1255
+ const bytes = new Uint8Array(buffer);
1256
+ const starts = [];
1257
+ for (const length of lengths) {
1258
+ starts.push(offset);
1259
+ offset += length;
1260
+ }
1261
+ offset = restrictionsAt;
1262
+ if (shared) {
1263
+ const reader = new VarintReader(bytes, starts[0]);
1264
+ let previous = 0;
1265
+ for (let node = 0; node < nodes; node += 1) {
1266
+ previous += reader.readSigned();
1267
+ nodeLatitudes[node] = previous;
1268
+ }
1269
+ previous = 0;
1270
+ for (let node = 0; node < nodes; node += 1) {
1271
+ previous += reader.readSigned();
1272
+ nodeLongitudes[node] = previous;
1273
+ }
1274
+ starts.shift();
1275
+ }
1276
+ const edgeOffsets = new Uint32Array(nodes + 1);
1277
+ const degrees = new VarintReader(bytes, starts[0]);
1278
+ for (let node = 0; node < nodes; node += 1) {
1279
+ const degree = degrees.read();
1280
+ if (degree > edges - edgeOffsets[node]) throw new Error("Invalid compact routing graph: node degrees exceed the edge count.");
1281
+ edgeOffsets[node + 1] = edgeOffsets[node] + degree;
1282
+ }
1283
+ if (edgeOffsets[nodes] !== edges) throw new Error("Invalid compact routing graph: node degrees do not sum to the edge count.");
1284
+ const edgeTargets = new Uint32Array(edges);
1285
+ const targets = new VarintReader(bytes, starts[1]);
1286
+ for (let node = 0; node < nodes; node += 1) {
1287
+ for (let edge = edgeOffsets[node]; edge < edgeOffsets[node + 1]; edge += 1) {
1288
+ const target = node + targets.readSigned();
1289
+ if (target < 0 || target >= nodes) throw new Error("Invalid compact routing graph: edge target outside the graph.");
1290
+ edgeTargets[edge] = target;
1291
+ }
1292
+ }
1293
+ assertGeographic(nodeLatitudes, nodeLongitudes, coordinateScale);
1294
+ const graph = { nodeLatitudes, nodeLongitudes, edgeOffsets, edgeTargets, edgeDistancesMeters, edgeDurationsSeconds, coordinateScale, edgeDistanceScale, edgeDurationScale };
1295
+ if (profileId) graph.profileId = profileId;
1296
+ if (maxSpeedKph) graph.maxSpeedKph = maxSpeedKph;
1297
+ const references = new Uint32Array(edges);
1298
+ const shapeRefs = new VarintReader(bytes, starts[2]);
1299
+ for (let edge = 0; edge < edges; edge += 1) {
1300
+ references[edge] = shapeRefs.read();
1301
+ if (references[edge] >>> 1 > shapeCount) throw new Error("Invalid compact routing graph: edge references a missing shape.");
1302
+ }
1303
+ const shapeSizes = new VarintReader(bytes, starts[3]);
1304
+ const sizes = new Uint32Array(shapeCount);
1305
+ const pointCeiling = Math.floor(lengths[lengths.length - 1] / 2);
1306
+ let totalPoints = 0;
1307
+ for (let shape = 0; shape < shapeCount; shape += 1) {
1308
+ sizes[shape] = shapeSizes.read();
1309
+ totalPoints += sizes[shape];
1310
+ if (totalPoints > pointCeiling) throw new Error("Invalid compact routing graph: shape sizes exceed the stored geometry.");
1311
+ }
1312
+ const tableLatitudes = new Int32Array(totalPoints);
1313
+ const tableLongitudes = new Int32Array(totalPoints);
1314
+ const tableStarts = new Uint32Array(shapeCount + 1);
1315
+ const points = new VarintReader(bytes, starts[4]);
1316
+ let at = 0;
1317
+ for (let shape = 0; shape < shapeCount; shape += 1) {
1318
+ tableStarts[shape] = at;
1319
+ let lat = 0;
1320
+ let lng = 0;
1321
+ for (let point = 0; point < sizes[shape]; point += 1) {
1322
+ lat = point ? lat + points.readSigned() : points.readSigned();
1323
+ lng = point ? lng + points.readSigned() : points.readSigned();
1324
+ tableLatitudes[at] = lat;
1325
+ tableLongitudes[at] = lng;
1326
+ at += 1;
1327
+ }
1328
+ }
1329
+ tableStarts[shapeCount] = at;
1330
+ const edgeShapeOffsets = new Uint32Array(edges + 1);
1331
+ for (let edge = 0; edge < edges; edge += 1) {
1332
+ const reference = references[edge];
1333
+ edgeShapeOffsets[edge + 1] = edgeShapeOffsets[edge] + (reference ? sizes[(reference >>> 1) - 1] : 0);
1334
+ }
1335
+ const expandedPoints = edgeShapeOffsets[edges];
1336
+ if (expandedPoints) {
1337
+ const edgeShapeLatitudes = new Int32Array(expandedPoints);
1338
+ const edgeShapeLongitudes = new Int32Array(expandedPoints);
1339
+ for (let edge = 0; edge < edges; edge += 1) {
1340
+ const reference = references[edge];
1341
+ if (!reference) continue;
1342
+ const shape = (reference >>> 1) - 1;
1343
+ const reversed = (reference & 1) === 1;
1344
+ const count = sizes[shape];
1345
+ const from = tableStarts[shape];
1346
+ const to = edgeShapeOffsets[edge];
1347
+ for (let point = 0; point < count; point += 1) {
1348
+ const source = from + (reversed ? count - 1 - point : point);
1349
+ edgeShapeLatitudes[to + point] = tableLatitudes[source];
1350
+ edgeShapeLongitudes[to + point] = tableLongitudes[source];
1351
+ }
1352
+ }
1353
+ assertGeographic(edgeShapeLatitudes, edgeShapeLongitudes, coordinateScale);
1354
+ Object.assign(graph, { edgeShapeOffsets, edgeShapeLatitudes, edgeShapeLongitudes });
1355
+ }
1356
+ if (pairs) {
1357
+ graph.turnRestrictionFromEdges = new Uint32Array(buffer.slice(offset, offset += pairs * 4));
1358
+ graph.turnRestrictionToEdges = new Uint32Array(buffer.slice(offset, offset += pairs * 4));
1359
+ }
1360
+ if (runs) {
1361
+ graph.turnRestrictionSequenceOffsets = new Uint32Array(buffer.slice(offset, offset += (runs + 1) * 4));
1362
+ graph.turnRestrictionSequenceEdges = new Uint32Array(buffer.slice(offset, offset += runEdges * 4));
1363
+ assertOffsets(graph.turnRestrictionSequenceOffsets, runs, runEdges, "turn restriction offsets");
1364
+ for (const edge of graph.turnRestrictionSequenceEdges) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
1365
+ }
1366
+ for (const edge of graph.turnRestrictionFromEdges ?? []) if (edge >= edges) throw new Error("Invalid routing graph: turn restriction references an edge outside the graph.");
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);
1369
+ return graph;
1370
+ }
1371
+ var PROFILE_CHUNK_HEADER_BYTES = 64;
1372
+ function encodeProfileChunk(chunk) {
1373
+ const edges = chunk.edgeDurationsSeconds.length;
1374
+ const limits = chunk.edgeLimits?.edges.length ? chunk.edgeLimits : void 0;
1375
+ if (limits) {
1376
+ const rows = limits.edges.length;
1377
+ if (limits.heightDecimetres.length !== rows || limits.widthDecimetres.length !== rows || limits.lengthDecimetres.length !== rows || limits.weightHalfTonnes.length !== rows) {
1378
+ throw new Error("Edge limit columns must all be the same length.");
1379
+ }
1380
+ for (let index = 0; index < rows; index += 1) {
1381
+ if (limits.edges[index] >= edges) throw new Error("Edge limit references an edge outside the graph.");
1382
+ if (index && limits.edges[index] <= limits.edges[index - 1]) throw new Error("Edge limits must be sorted and unique.");
1383
+ }
1384
+ }
1385
+ if (!(chunk.edgeDurationsSeconds instanceof Uint16Array)) throw new Error("Profile durations must be a Uint16Array.");
1386
+ if (!Number.isInteger(chunk.edgeDurationScale) || chunk.edgeDurationScale <= 0 || chunk.edgeDurationScale > 65535) throw new Error("Invalid profile duration scale.");
1387
+ if (!getRoutingProfileById(chunk.profileId) || !chunk.profileId) throw new Error(`Cannot write a profile chunk for an unsupported profile (id ${chunk.profileId}).`);
1388
+ if (chunk.maxSpeedKph <= 0 || chunk.maxSpeedKph > MAX_PLAUSIBLE_SPEED_KPH) throw new Error("Invalid profile speed ceiling.");
1389
+ const mask = chunk.edgeAccess && !allowsEverything(chunk.edgeAccess, edges) ? chunk.edgeAccess : void 0;
1390
+ if (mask && mask.length !== edges + 7 >> 3) throw new Error("Profile access mask does not cover every edge.");
1391
+ const restrictions = chunk.restrictionCount ?? 0;
1392
+ const turns = chunk.restrictionMask && restrictions && !allowsEverything(chunk.restrictionMask, restrictions) ? chunk.restrictionMask : void 0;
1393
+ if (turns && turns.length !== restrictions + 7 >> 3) throw new Error("Profile restriction mask does not cover every restriction.");
1394
+ const variableAt = PROFILE_CHUNK_HEADER_BYTES + edges * 2 + (mask?.byteLength ?? 0) + (turns?.byteLength ?? 0);
1395
+ const limitsAt = alignUp(variableAt);
1396
+ const limitRows = limits?.edges.length ?? 0;
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);
1405
+ const view = new DataView(buffer);
1406
+ view.setUint32(0, COMPACT_MAGIC, true);
1407
+ view.setUint16(4, SPLIT_COMPACT_VERSION, true);
1408
+ view.setUint8(6, ChunkKind.profile);
1409
+ view.setUint8(7, chunk.profileId);
1410
+ view.setUint32(8, edges, true);
1411
+ view.setUint32(12, mask?.byteLength ?? 0, true);
1412
+ view.setUint16(16, chunk.edgeDurationScale, true);
1413
+ view.setUint16(18, Math.round(chunk.maxSpeedKph), true);
1414
+ view.setUint32(20, limitRows, true);
1415
+ view.setUint32(24, restrictions, true);
1416
+ view.setUint32(28, turns?.byteLength ?? 0, true);
1417
+ view.setUint32(32, conditionRows, true);
1418
+ view.setUint32(36, conditionText?.byteLength ?? 0, true);
1419
+ new Uint16Array(buffer, PROFILE_CHUNK_HEADER_BYTES, edges).set(chunk.edgeDurationsSeconds);
1420
+ if (mask) new Uint8Array(buffer, PROFILE_CHUNK_HEADER_BYTES + edges * 2, mask.byteLength).set(mask);
1421
+ if (turns) new Uint8Array(buffer, PROFILE_CHUNK_HEADER_BYTES + edges * 2 + (mask?.byteLength ?? 0), turns.byteLength).set(turns);
1422
+ if (limits && limitRows) {
1423
+ new Uint32Array(buffer, limitsAt, limitRows).set(limits.edges);
1424
+ const columns = new Uint8Array(buffer, limitsAt + limitRows * 4, limitRows * 4);
1425
+ columns.set(limits.heightDecimetres, 0);
1426
+ columns.set(limits.widthDecimetres, limitRows);
1427
+ columns.set(limits.lengthDecimetres, limitRows * 2);
1428
+ columns.set(limits.weightHalfTonnes, limitRows * 3);
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
+ }
1436
+ return buffer;
1437
+ }
1438
+ function decodeProfileChunk(buffer) {
1439
+ if (buffer.byteLength < PROFILE_CHUNK_HEADER_BYTES) throw new Error("Invalid routing profile chunk header");
1440
+ const view = new DataView(buffer);
1441
+ if (view.getUint32(0, true) !== COMPACT_MAGIC || view.getUint16(4, true) !== SPLIT_COMPACT_VERSION || view.getUint8(6) !== ChunkKind.profile) {
1442
+ throw new Error("Unsupported routing profile chunk format");
1443
+ }
1444
+ const profileId = view.getUint8(7);
1445
+ const edges = view.getUint32(8, true);
1446
+ const maskBytes = view.getUint32(12, true);
1447
+ const edgeDurationScale = view.getUint16(16, true);
1448
+ const maxSpeedKph = view.getUint16(18, true);
1449
+ const limitRows = view.getUint32(20, true);
1450
+ const restrictionCount = view.getUint32(24, true);
1451
+ const turnMaskBytes = view.getUint32(28, true);
1452
+ const conditionRows = view.getUint32(32, true);
1453
+ const conditionTextBytes = view.getUint32(36, true);
1454
+ if (!profileId || !getRoutingProfileById(profileId)) throw new Error(`Routing graph was compiled for an unsupported profile (id ${profileId}).`);
1455
+ if (!edgeDurationScale) throw new Error("Invalid routing profile chunk: zero duration scale.");
1456
+ if (maxSpeedKph > MAX_PLAUSIBLE_SPEED_KPH) throw new Error("Invalid routing profile chunk: implausible profile speed ceiling.");
1457
+ if (maskBytes && maskBytes !== edges + 7 >> 3) throw new Error("Invalid routing profile chunk: access mask does not cover every edge.");
1458
+ if (turnMaskBytes && turnMaskBytes !== restrictionCount + 7 >> 3) throw new Error("Invalid routing profile chunk: restriction mask does not cover every restriction.");
1459
+ const variableAt = PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes + turnMaskBytes;
1460
+ const limitsAt = alignUp(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;
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.");
1467
+ if (buffer.byteLength !== expected) throw new Error("Truncated routing profile chunk");
1468
+ let edgeLimits;
1469
+ if (limitRows) {
1470
+ const edgeIds = new Uint32Array(buffer.slice(limitsAt, limitsAt + limitRows * 4));
1471
+ for (let index = 0; index < limitRows; index += 1) {
1472
+ if (edgeIds[index] >= edges) throw new Error("Invalid routing profile chunk: limit references an edge outside the graph.");
1473
+ if (index && edgeIds[index] <= edgeIds[index - 1]) throw new Error("Invalid routing profile chunk: limits are not sorted.");
1474
+ }
1475
+ const columns = new Uint8Array(buffer, limitsAt + limitRows * 4, limitRows * 4);
1476
+ edgeLimits = {
1477
+ edges: edgeIds,
1478
+ heightDecimetres: columns.slice(0, limitRows),
1479
+ widthDecimetres: columns.slice(limitRows, limitRows * 2),
1480
+ lengthDecimetres: columns.slice(limitRows * 2, limitRows * 3),
1481
+ weightHalfTonnes: columns.slice(limitRows * 3)
1482
+ };
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
+ }
1503
+ return {
1504
+ profileId,
1505
+ maxSpeedKph,
1506
+ edgeDurationScale,
1507
+ edgeDurationsSeconds: new Uint16Array(buffer.slice(PROFILE_CHUNK_HEADER_BYTES, PROFILE_CHUNK_HEADER_BYTES + edges * 2)),
1508
+ ...maskBytes ? { edgeAccess: new Uint8Array(buffer.slice(PROFILE_CHUNK_HEADER_BYTES + edges * 2, PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes)) } : {},
1509
+ ...restrictionCount ? { restrictionCount } : {},
1510
+ ...turnMaskBytes ? { restrictionMask: new Uint8Array(buffer.slice(PROFILE_CHUNK_HEADER_BYTES + edges * 2 + maskBytes, variableAt)) } : {},
1511
+ ...edgeLimits ? { edgeLimits } : {},
1512
+ ...edgeConditions ? { edgeConditions } : {}
1513
+ };
1514
+ }
1515
+ function decodeSharedGraph(buffer) {
1516
+ if (buffer.byteLength < PACKED_HEADER_BYTES) throw new Error("Invalid shared routing graph header");
1517
+ const view = new DataView(buffer);
1518
+ if (view.getUint32(0, true) !== COMPACT_MAGIC || view.getUint16(4, true) !== SPLIT_COMPACT_VERSION || view.getUint8(6) !== ChunkKind.shared) {
1519
+ throw new Error("Unsupported shared routing graph format");
1520
+ }
1521
+ const { edgeDurationsSeconds: _durations, edgeDurationScale: _scale, profileId: _profile, maxSpeedKph: _speed, ...shared } = decodePackedGraph(buffer, true);
1522
+ return shared;
1523
+ }
1524
+ function combineProfiledGraph(shared, profile) {
1525
+ if (profile.edgeDurationsSeconds.length !== shared.edgeTargets.length) {
1526
+ throw new Error("Routing profile chunk does not match the shared chunk it was loaded with.");
1527
+ }
1528
+ return {
1529
+ ...shared,
1530
+ edgeDurationsSeconds: profile.edgeDurationsSeconds,
1531
+ edgeDurationScale: profile.edgeDurationScale,
1532
+ profileId: profile.profileId,
1533
+ maxSpeedKph: profile.maxSpeedKph,
1534
+ ...profile.edgeAccess ? { edgeAccess: profile.edgeAccess } : {},
1535
+ ...profile.edgeLimits ? { edgeLimits: profile.edgeLimits } : {},
1536
+ ...profile.edgeConditions ? { edgeConditions: profile.edgeConditions } : {},
1537
+ ...profile.restrictionMask ? { restrictionMask: profile.restrictionMask } : {}
1538
+ };
1539
+ }
1540
+
1541
+ // src/search-bounds.ts
1542
+ var RADIANS = Math.PI / 180;
1543
+ var METRES_PER_DEGREE_LATITUDE = 110574;
1544
+ var METRES_PER_DEGREE_LONGITUDE = 111320;
1545
+ var SAFETY = 0.999;
1546
+ var cache = /* @__PURE__ */ new WeakMap();
1547
+ function searchBoundsFor(graph) {
1548
+ const held = cache.get(graph);
1549
+ if (held) return held;
1550
+ const distanceScale = graph.edgeDistanceScale ?? 1;
1551
+ const durationScale = graph.edgeDurationScale ?? 1;
1552
+ let fastest = 0;
1553
+ for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) {
1554
+ const seconds = graph.edgeDurationsSeconds[edge] * durationScale;
1555
+ if (seconds <= 0) continue;
1556
+ const speed = graph.edgeDistancesMeters[edge] * distanceScale / seconds;
1557
+ if (speed > fastest) fastest = speed;
1558
+ }
1559
+ const coordinateScale = graph.coordinateScale ?? 1;
1560
+ let highest = 0;
1561
+ for (let node = 0; node < graph.nodeLatitudes.length; node += 1) {
1562
+ const latitude = Math.abs(graph.nodeLatitudes[node] / coordinateScale);
1563
+ if (latitude > highest) highest = latitude;
1564
+ }
1565
+ const bounds = {
1566
+ // An empty or zero-duration graph falls back to the profile's ceiling.
1567
+ maxSpeedMps: Math.max(fastest, maxSpeedMpsFor(graph)),
1568
+ longitudeMetresPerDegree: METRES_PER_DEGREE_LONGITUDE * Math.cos(Math.min(highest, 89.9) * RADIANS) * SAFETY
1569
+ };
1570
+ cache.set(graph, bounds);
1571
+ return bounds;
1572
+ }
1573
+ function straightLineAtMost(latitudeA, longitudeA, latitudeB, longitudeB, longitudeMetresPerDegree) {
1574
+ const north = (latitudeA - latitudeB) * METRES_PER_DEGREE_LATITUDE * SAFETY;
1575
+ const east = (longitudeA - longitudeB) * longitudeMetresPerDegree;
1576
+ return Math.sqrt(north * north + east * east);
1577
+ }
1578
+
1579
+ // src/connectivity.ts
1580
+ var cache2 = /* @__PURE__ */ new WeakMap();
1581
+ function componentsFor(graph) {
1582
+ const held = cache2.get(graph);
1583
+ if (held) return held;
1584
+ const nodes = graph.nodeLatitudes.length;
1585
+ const labels = new Int32Array(nodes).fill(-1);
1586
+ const incomingStart = new Uint32Array(nodes + 1);
1587
+ for (let edge = 0; edge < graph.edgeTargets.length; edge += 1) incomingStart[graph.edgeTargets[edge] + 1] += 1;
1588
+ for (let node = 1; node <= nodes; node += 1) incomingStart[node] += incomingStart[node - 1];
1589
+ const cursor = incomingStart.slice(0, nodes);
1590
+ const incoming = new Uint32Array(graph.edgeTargets.length);
1591
+ for (let node = 0; node < nodes; node += 1) {
1592
+ for (let edge = graph.edgeOffsets[node]; edge < graph.edgeOffsets[node + 1]; edge += 1) incoming[cursor[graph.edgeTargets[edge]]++] = node;
1593
+ }
1594
+ const stack = new Int32Array(nodes);
1595
+ let label = 0;
1596
+ for (let start = 0; start < nodes; start += 1) {
1597
+ if (labels[start] !== -1) continue;
1598
+ let top = 0;
1599
+ stack[top++] = start;
1600
+ labels[start] = label;
1601
+ while (top > 0) {
1602
+ const node = stack[--top];
1603
+ for (let edge = graph.edgeOffsets[node]; edge < graph.edgeOffsets[node + 1]; edge += 1) {
1604
+ const next = graph.edgeTargets[edge];
1605
+ if (labels[next] === -1) {
1606
+ labels[next] = label;
1607
+ stack[top++] = next;
1608
+ }
1609
+ }
1610
+ for (let slot = incomingStart[node]; slot < incomingStart[node + 1]; slot += 1) {
1611
+ const previous = incoming[slot];
1612
+ if (labels[previous] === -1) {
1613
+ labels[previous] = label;
1614
+ stack[top++] = previous;
1615
+ }
1616
+ }
1617
+ }
1618
+ label += 1;
1619
+ }
1620
+ cache2.set(graph, labels);
1621
+ return labels;
1622
+ }
1623
+ function componentSizes(labels) {
1624
+ const sizes = /* @__PURE__ */ new Map();
1625
+ for (let node = 0; node < labels.length; node += 1) sizes.set(labels[node], (sizes.get(labels[node]) ?? 0) + 1);
1626
+ return sizes;
1627
+ }
1628
+
1629
+ // src/edge-index.ts
1630
+ var MAX_CELLS_PER_EDGE = 4096;
1631
+ function cellKey(latitudeCell, longitudeCell) {
1632
+ return `${latitudeCell}:${longitudeCell}`;
1633
+ }
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;
1641
+ }
1642
+ return low;
1643
+ }
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
+ }
1652
+ }
1653
+ points.push({ lat: graph.nodeLatitudes[target] / scale, lng: graph.nodeLongitudes[target] / scale });
1654
+ return points;
1655
+ }
1656
+ function createEdgeSpatialIndex(graph, cellSizeDegrees = 0.02) {
1657
+ if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Edge index cell size must be positive.");
1658
+ const scale = graph.coordinateScale ?? 1;
1659
+ const buckets = /* @__PURE__ */ new Map();
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
+ }
1700
+ }
1701
+ return { cellSizeDegrees, cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)])), oversized: Uint32Array.from(oversized) };
1702
+ }
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) {
1713
+ const latitudeDelta = maxDistanceMeters / 110574;
1714
+ const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
1715
+ const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
1716
+ const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
1717
+ const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
1718
+ const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
1719
+ const ids = new Set(index.oversized);
1720
+ for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
1721
+ for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
1722
+ const bucket = index.cells.get(cellKey(latitudeCell, longitudeCell));
1723
+ if (bucket) for (const id of bucket) ids.add(id);
1724
+ }
1725
+ }
1726
+ return ids;
1727
+ }
1728
+
1729
+ // src/spatial-index.ts
1730
+ function cellKey2(latitude, longitude, cellSizeDegrees) {
1731
+ return `${Math.floor(latitude / cellSizeDegrees)}:${Math.floor(longitude / cellSizeDegrees)}`;
1732
+ }
1733
+ function coordinateFor(graph, nodeId) {
1734
+ const scale = graph.coordinateScale ?? 1;
1735
+ return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
1736
+ }
1737
+ function createSpatialIndex(graph, cellSizeDegrees = 0.02) {
1738
+ if (!Number.isFinite(cellSizeDegrees) || cellSizeDegrees <= 0) throw new Error("Spatial index cell size must be positive.");
1739
+ const buckets = /* @__PURE__ */ new Map();
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]);
1746
+ }
1747
+ return {
1748
+ cellSizeDegrees,
1749
+ cells: new Map([...buckets].map(([key, ids]) => [key, Uint32Array.from(ids)]))
1750
+ };
1751
+ }
1752
+ function withSpatialIndex(graph, cellSizeDegrees) {
1753
+ return { ...graph, spatialIndex: createSpatialIndex(graph, cellSizeDegrees) };
1754
+ }
1755
+ function nearbyNodeIds(index, coordinate, maxDistanceMeters) {
1756
+ const latitudeDelta = maxDistanceMeters / 110574;
1757
+ const longitudeDelta = maxDistanceMeters / Math.max(1, 111320 * Math.cos(coordinate.lat * Math.PI / 180));
1758
+ const minLatitudeCell = Math.floor((coordinate.lat - latitudeDelta) / index.cellSizeDegrees);
1759
+ const maxLatitudeCell = Math.floor((coordinate.lat + latitudeDelta) / index.cellSizeDegrees);
1760
+ const minLongitudeCell = Math.floor((coordinate.lng - longitudeDelta) / index.cellSizeDegrees);
1761
+ const maxLongitudeCell = Math.floor((coordinate.lng + longitudeDelta) / index.cellSizeDegrees);
1762
+ const ids = [];
1763
+ for (let latitudeCell = minLatitudeCell; latitudeCell <= maxLatitudeCell; latitudeCell += 1) {
1764
+ for (let longitudeCell = minLongitudeCell; longitudeCell <= maxLongitudeCell; longitudeCell += 1) {
1765
+ const bucket = index.cells.get(`${latitudeCell}:${longitudeCell}`);
1766
+ if (bucket) ids.push(...bucket);
1767
+ }
1768
+ }
1769
+ return ids;
1770
+ }
1771
+
1772
+ // src/snap.ts
1773
+ var EARTH_RADIUS_METERS = 6371e3;
1774
+ var METERS_PER_DEGREE_LATITUDE = 110574;
1775
+ var METERS_PER_DEGREE_LONGITUDE = 111320;
1776
+ var NODE_SNAP_TOLERANCE_METERS = 1;
1777
+ var TIE_TOLERANCE_METERS = 0.5;
1778
+ var DEFAULT_SNAP_RADIUS_METERS = 500;
1779
+ var FALLBACK_SNAP_RADIUS_METERS = 1e3;
1780
+ function haversineMeters(a, b) {
1781
+ const radians = Math.PI / 180;
1782
+ const dLat = (b.lat - a.lat) * radians;
1783
+ const dLng = (b.lng - a.lng) * radians;
1784
+ const latA = a.lat * radians;
1785
+ const latB = b.lat * radians;
1786
+ const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLng / 2) ** 2;
1787
+ return EARTH_RADIUS_METERS * 2 * Math.atan2(Math.sqrt(value), Math.sqrt(1 - value));
1788
+ }
1789
+ function coordinateFor2(graph, nodeId) {
1790
+ const scale = graph.coordinateScale ?? 1;
1791
+ return { lat: graph.nodeLatitudes[nodeId] / scale, lng: graph.nodeLongitudes[nodeId] / scale };
1792
+ }
1793
+ function projectOntoPolyline(points, query) {
1794
+ const longitudeScale = METERS_PER_DEGREE_LONGITUDE * Math.cos(query.lat * Math.PI / 180);
1795
+ const x = (point) => (point.lng - query.lng) * longitudeScale;
1796
+ const y = (point) => (point.lat - query.lat) * METERS_PER_DEGREE_LATITUDE;
1797
+ let best = { distanceMeters: Infinity, vertexIndex: 0, along: 0, point: points[0] };
1798
+ let travelled = 0;
1799
+ let total = 0;
1800
+ for (let index = 1; index < points.length; index += 1) {
1801
+ const start = points[index - 1];
1802
+ const end = points[index];
1803
+ const segmentMeters = haversineMeters(start, end);
1804
+ const dx = x(end) - x(start);
1805
+ const dy = y(end) - y(start);
1806
+ const lengthSquared = dx * dx + dy * dy;
1807
+ const t = lengthSquared ? Math.max(0, Math.min(1, (-x(start) * dx + -y(start) * dy) / lengthSquared)) : 0;
1808
+ const closest = { lat: start.lat + (end.lat - start.lat) * t, lng: start.lng + (end.lng - start.lng) * t };
1809
+ const distanceMeters = haversineMeters(query, closest);
1810
+ if (distanceMeters < best.distanceMeters) {
1811
+ best = { distanceMeters, vertexIndex: index - 1, along: travelled + segmentMeters * t, point: closest };
1812
+ }
1813
+ travelled += segmentMeters;
1814
+ total += segmentMeters;
1815
+ }
1816
+ return { ...best, totalMeters: total };
1817
+ }
1818
+ function snapToGraph(graph, coordinate, maxDistanceMeters = DEFAULT_SNAP_RADIUS_METERS, access = graph.edgeAccess, accept, everyNearbyRoad = false) {
1819
+ const candidates = graph.edgeTargets.length ? nearbyEdgeIds(edgeSpatialIndexFor(graph), coordinate, maxDistanceMeters) : /* @__PURE__ */ new Set();
1820
+ let best = null;
1821
+ const projections = [];
1822
+ for (const edge of candidates) {
1823
+ if (!edgeAllowed(access, edge)) continue;
1824
+ const source = edgeSource(graph, edge);
1825
+ if (accept && !(accept(source) && accept(graph.edgeTargets[edge]))) continue;
1826
+ const polyline = edgePolyline(graph, edge, source);
1827
+ const projected = projectOntoPolyline(polyline, coordinate);
1828
+ if (projected.distanceMeters > maxDistanceMeters) continue;
1829
+ const projection = {
1830
+ edge,
1831
+ source,
1832
+ target: graph.edgeTargets[edge],
1833
+ fraction: projected.totalMeters ? projected.along / projected.totalMeters : 0,
1834
+ point: projected.point,
1835
+ distanceMeters: projected.distanceMeters,
1836
+ vertexIndex: projected.vertexIndex,
1837
+ polyline
1838
+ };
1839
+ projections.push(projection);
1840
+ if (!best || projection.distanceMeters < best.distanceMeters) best = projection;
1841
+ }
1842
+ if (!best) {
1843
+ const node = nearestNodeWithin(graph, coordinate, maxDistanceMeters, accept);
1844
+ return node === null ? null : { kind: "node", node, point: coordinateFor2(graph, node), distanceMeters: haversineMeters(coordinate, coordinateFor2(graph, node)) };
1845
+ }
1846
+ const startMeters = best.fraction * totalLengthOf(best);
1847
+ const endMeters = (1 - best.fraction) * totalLengthOf(best);
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);
1851
+ return { kind: "edge", projections: sameRoad, point: best.point, distanceMeters: best.distanceMeters };
1852
+ }
1853
+ function totalLengthOf(projection) {
1854
+ let total = 0;
1855
+ for (let index = 1; index < projection.polyline.length; index += 1) total += haversineMeters(projection.polyline[index - 1], projection.polyline[index]);
1856
+ return total;
1857
+ }
1858
+ function nearestNodeWithin(graph, coordinate, maxDistanceMeters, accept) {
1859
+ let closest = null;
1860
+ let closestDistance = maxDistanceMeters;
1861
+ const check2 = (nodeId) => {
1862
+ if (accept && !accept(nodeId)) return;
1863
+ const distance = haversineMeters(coordinate, coordinateFor2(graph, nodeId));
1864
+ if (distance <= closestDistance) {
1865
+ closest = nodeId;
1866
+ closestDistance = distance;
1867
+ }
1868
+ };
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);
1871
+ return closest;
1872
+ }
1873
+ function geometryAfter(projection) {
1874
+ return [projection.point, ...projection.polyline.slice(projection.vertexIndex + 1, projection.polyline.length - 1)];
1875
+ }
1876
+ function geometryBefore(projection) {
1877
+ return [...projection.polyline.slice(1, projection.vertexIndex + 1), projection.point];
1878
+ }
1879
+ function geometryBetween(from, to) {
1880
+ return [from.point, ...from.polyline.slice(from.vertexIndex + 1, to.vertexIndex + 1), to.point];
1881
+ }
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
+
2107
+ // src/priority-queue.ts
2108
+ var MinPriorityQueue = class {
2109
+ values = [];
2110
+ get size() {
2111
+ return this.values.length;
2112
+ }
2113
+ /** The smallest priority still queued, or Infinity when empty. A* uses it to
2114
+ * stop once no queued state can beat the best finish found so far. */
2115
+ get minimumPriority() {
2116
+ return this.values.length ? this.values[0].priority : Infinity;
2117
+ }
2118
+ push(value, priority) {
2119
+ const entry = { value, priority };
2120
+ this.values.push(entry);
2121
+ let index = this.values.length - 1;
2122
+ while (index > 0) {
2123
+ const parent = Math.floor((index - 1) / 2);
2124
+ if (this.values[parent].priority <= entry.priority) break;
2125
+ this.values[index] = this.values[parent];
2126
+ index = parent;
2127
+ }
2128
+ this.values[index] = entry;
2129
+ }
2130
+ pop() {
2131
+ if (!this.values.length) return void 0;
2132
+ const first = this.values[0];
2133
+ const last = this.values.pop();
2134
+ if (this.values.length) {
2135
+ let index = 0;
2136
+ while (true) {
2137
+ let child = index * 2 + 1;
2138
+ if (child >= this.values.length) break;
2139
+ if (child + 1 < this.values.length && this.values[child + 1].priority < this.values[child].priority) child++;
2140
+ if (this.values[child].priority >= last.priority) break;
2141
+ this.values[index] = this.values[child];
2142
+ index = child;
2143
+ }
2144
+ this.values[index] = last;
2145
+ }
2146
+ return first.value;
2147
+ }
2148
+ };
2149
+
2150
+ // src/router.ts
2151
+ var ROADS_TO_TRY = 6;
2152
+ function assertGraph(graph) {
2153
+ const nodes = graph.nodeLatitudes.length;
2154
+ const edges = graph.edgeTargets.length;
2155
+ if (!nodes || graph.nodeLongitudes.length !== nodes || graph.edgeOffsets.length !== nodes + 1 || graph.edgeDistancesMeters.length !== edges || graph.edgeDurationsSeconds.length !== edges || graph.edgeOffsets[nodes] !== edges) {
2156
+ throw new Error("Invalid road graph");
2157
+ }
2158
+ }
2159
+ function findNearestNode(graph, coordinate, maxDistanceMeters = 500) {
2160
+ let closest = null;
2161
+ let closestDistance = maxDistanceMeters;
2162
+ const checkNode = (nodeId) => {
2163
+ const distance = haversineMeters(coordinate, coordinateFor2(graph, nodeId));
2164
+ if (distance <= closestDistance) {
2165
+ closest = nodeId;
2166
+ closestDistance = distance;
2167
+ }
2168
+ };
2169
+ if (graph.spatialIndex) {
2170
+ for (const nodeId of nearbyNodeIds(graph.spatialIndex, coordinate, maxDistanceMeters)) checkNode(nodeId);
2171
+ } else {
2172
+ for (let nodeId = 0; nodeId < graph.nodeLatitudes.length; nodeId += 1) checkNode(nodeId);
2173
+ }
2174
+ return closest;
2175
+ }
2176
+ function searchContext(graph, destinationPoint, preference, slowed) {
2177
+ const distanceScale = graph.edgeDistanceScale ?? 1;
2178
+ const durationScale = graph.edgeDurationScale ?? 1;
2179
+ const { maxSpeedMps, longitudeMetresPerDegree } = searchBoundsFor(graph);
2180
+ const coordinateScale = graph.coordinateScale ?? 1;
2181
+ const latitudes = graph.nodeLatitudes;
2182
+ const longitudes = graph.nodeLongitudes;
2183
+ const destinationLatitude = destinationPoint.lat;
2184
+ const destinationLongitude = destinationPoint.lng;
2185
+ const toCost = preference === "fastest" ? 1 / maxSpeedMps : 1;
2186
+ return {
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),
2192
+ // Called once per relaxation, millions of times on a long route, so it
2193
+ // reads the coordinate arrays directly and avoids a haversine's four
2194
+ // trigonometric calls for a number that only has to be a lower bound.
2195
+ heuristicFor: (node) => straightLineAtMost(
2196
+ latitudes[node] / coordinateScale,
2197
+ longitudes[node] / coordinateScale,
2198
+ destinationLatitude,
2199
+ destinationLongitude,
2200
+ longitudeMetresPerDegree
2201
+ ) * toCost
2202
+ };
2203
+ }
2204
+ function searchByNode(graph, seeds, finishers, destinationPoint, preference, costBound, access, slowed) {
2205
+ const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference, slowed);
2206
+ const nodeCount = graph.nodeLatitudes.length;
2207
+ const costs = new Float64Array(nodeCount).fill(Infinity);
2208
+ const previousNode = new Int32Array(nodeCount).fill(-1);
2209
+ const previousEdge = new Int32Array(nodeCount).fill(-1);
2210
+ const seedAt = /* @__PURE__ */ new Map();
2211
+ const queue = new MinPriorityQueue();
2212
+ for (const seed of seeds) {
2213
+ if (seed.cost >= costs[seed.node]) continue;
2214
+ costs[seed.node] = seed.cost;
2215
+ seedAt.set(seed.node, seed);
2216
+ queue.push(seed.node, seed.cost + heuristicFor(seed.node));
2217
+ }
2218
+ let best = costBound;
2219
+ let bestNode = -1;
2220
+ let bestFinisher = null;
2221
+ while (queue.size) {
2222
+ if (queue.minimumPriority >= best) break;
2223
+ const node2 = queue.pop();
2224
+ for (const finisher of finishers.get(node2) ?? []) {
2225
+ const total = costs[node2] + finisher.cost;
2226
+ if (total < best) {
2227
+ best = total;
2228
+ bestNode = node2;
2229
+ bestFinisher = finisher;
2230
+ }
2231
+ }
2232
+ for (let edge = graph.edgeOffsets[node2]; edge < graph.edgeOffsets[node2 + 1]; edge += 1) {
2233
+ if (!edgeAllowed(access, edge)) continue;
2234
+ const target = graph.edgeTargets[edge];
2235
+ const cost = costs[node2] + costOf(edge);
2236
+ if (cost >= costs[target]) continue;
2237
+ costs[target] = cost;
2238
+ previousNode[target] = node2;
2239
+ previousEdge[target] = edge;
2240
+ queue.push(target, cost + heuristicFor(target));
2241
+ }
2242
+ }
2243
+ if (!bestFinisher) return null;
2244
+ const edges = [];
2245
+ let node = bestNode;
2246
+ while (previousNode[node] !== -1) {
2247
+ edges.push(previousEdge[node]);
2248
+ node = previousNode[node];
2249
+ }
2250
+ edges.reverse();
2251
+ return { edges, finisher: bestFinisher, seed: seedAt.get(node) };
2252
+ }
2253
+ function searchWithRestrictions(graph, seeds, finishers, destinationPoint, preference, restrictions, sequenceIndex, costBound, access, slowed) {
2254
+ const { costOf, heuristicFor } = searchContext(graph, destinationPoint, preference, slowed);
2255
+ const targets = graph.edgeTargets;
2256
+ const { guarded, stateOfEdge, edgeOfState, count: guardedStates } = guardedJunctionsFor(graph, restrictions, sequenceIndex);
2257
+ const nodes = graph.nodeLatitudes.length;
2258
+ const plainBase = guardedStates;
2259
+ const stateCount = guardedStates + nodes;
2260
+ const costs = new Float64Array(stateCount).fill(Infinity);
2261
+ const cameFrom = new Int32Array(stateCount).fill(-1);
2262
+ const cameVia = new Int32Array(stateCount).fill(-1);
2263
+ const seedOf = /* @__PURE__ */ new Map();
2264
+ const queue = new MinPriorityQueue();
2265
+ const sequences = sequenceIndex?.sequences ?? [];
2266
+ const startingAt = sequenceIndex?.startingAt;
2267
+ const partialEdge = [];
2268
+ const partialMatches = [];
2269
+ const partialCost = [];
2270
+ const partialPrevious = [];
2271
+ const partialIds = /* @__PURE__ */ new Map();
2272
+ const stride = sequenceIndex?.stride ?? 1;
2273
+ const encode = (sequence, position) => sequence * stride + position;
2274
+ const nodeOf = (state) => {
2275
+ if (state < guardedStates) return targets[edgeOfState[state]];
2276
+ if (state < stateCount) return state - plainBase;
2277
+ return targets[partialEdge[state - stateCount]];
2278
+ };
2279
+ const arrivalOf = (state) => {
2280
+ if (state < guardedStates) return edgeOfState[state];
2281
+ if (state < stateCount) return cameVia[state];
2282
+ return partialEdge[state - stateCount];
2283
+ };
2284
+ const matchesOf = (state) => state < stateCount ? void 0 : partialMatches[state - stateCount];
2285
+ const costOfState = (state) => state < stateCount ? costs[state] : partialCost[state - stateCount];
2286
+ const bansAt = (state) => state < guardedStates || state >= stateCount ? restrictions?.get(arrivalOf(state)) : void 0;
2287
+ const relax = (state, cost, from, via, seed2) => {
2288
+ if (state < stateCount) {
2289
+ if (cost >= costs[state]) return;
2290
+ costs[state] = cost;
2291
+ cameFrom[state] = from;
2292
+ cameVia[state] = via;
2293
+ if (from === -1 && seed2) seedOf.set(state, seed2);
2294
+ } else {
2295
+ const slot = state - stateCount;
2296
+ if (cost >= partialCost[slot]) return;
2297
+ partialCost[slot] = cost;
2298
+ partialPrevious[slot] = from;
2299
+ }
2300
+ queue.push(state, cost + heuristicFor(nodeOf(state)));
2301
+ };
2302
+ const stateFor = (edge, matches) => {
2303
+ if (!matches.length) {
2304
+ const node = targets[edge];
2305
+ return guarded[node] ? stateOfEdge[edge] : plainBase + node;
2306
+ }
2307
+ const key = `${edge}:${matches.sort((a, b) => a - b).join(",")}`;
2308
+ let id = partialIds.get(key);
2309
+ if (id === void 0) {
2310
+ id = stateCount + partialEdge.length;
2311
+ partialIds.set(key, id);
2312
+ partialEdge.push(edge);
2313
+ partialMatches.push(matches);
2314
+ partialCost.push(Infinity);
2315
+ partialPrevious.push(-1);
2316
+ }
2317
+ return id;
2318
+ };
2319
+ const advance = (edge, matches, next) => {
2320
+ const carried = [];
2321
+ if (matches) {
2322
+ for (const match of matches) {
2323
+ const sequence = sequences[Math.floor(match / stride)];
2324
+ const position = match % stride;
2325
+ if (sequence[position + 1] !== next) continue;
2326
+ if (position + 2 === sequence.length) return null;
2327
+ carried.push(encode(Math.floor(match / stride), position + 1));
2328
+ }
2329
+ }
2330
+ for (const id of startingAt?.get(edge) ?? []) {
2331
+ if (sequences[id][1] !== next) continue;
2332
+ if (sequences[id].length === 2) return null;
2333
+ carried.push(encode(id, 1));
2334
+ }
2335
+ return carried;
2336
+ };
2337
+ let best = costBound;
2338
+ let bestArrival = -1;
2339
+ let bestFinisher = null;
2340
+ let bestSeed = null;
2341
+ const considerFinishers = (state, node, seed2) => {
2342
+ const banned = bansAt(state);
2343
+ const edge = arrivalOf(state);
2344
+ for (const finisher of finishers.get(node) ?? []) {
2345
+ if (finisher.edge >= 0 && banned?.has(finisher.edge)) continue;
2346
+ if (finisher.edge >= 0 && edge >= 0 && advance(edge, matchesOf(state), finisher.edge) === null) continue;
2347
+ const total = costOfState(state) + finisher.cost;
2348
+ if (total < best) {
2349
+ best = total;
2350
+ bestArrival = state;
2351
+ bestFinisher = finisher;
2352
+ bestSeed = seed2;
2353
+ }
2354
+ }
2355
+ };
2356
+ for (const seed2 of seeds) {
2357
+ if (seed2.edge >= 0) {
2358
+ const state = stateFor(seed2.edge, []);
2359
+ relax(state, seed2.cost, -1, seed2.edge, seed2);
2360
+ considerFinishers(state, seed2.node, seed2);
2361
+ continue;
2362
+ }
2363
+ for (const finisher of finishers.get(seed2.node) ?? []) {
2364
+ const total = seed2.cost + finisher.cost;
2365
+ if (total < best) {
2366
+ best = total;
2367
+ bestArrival = -1;
2368
+ bestFinisher = finisher;
2369
+ bestSeed = seed2;
2370
+ }
2371
+ }
2372
+ for (let edge = graph.edgeOffsets[seed2.node]; edge < graph.edgeOffsets[seed2.node + 1]; edge += 1) {
2373
+ if (!edgeAllowed(access, edge)) continue;
2374
+ relax(stateFor(edge, []), seed2.cost + costOf(edge), -1, edge, seed2);
2375
+ }
2376
+ }
2377
+ while (queue.size) {
2378
+ if (queue.minimumPriority >= best) break;
2379
+ const state = queue.pop();
2380
+ const node = nodeOf(state);
2381
+ const matches = matchesOf(state);
2382
+ const banned = bansAt(state);
2383
+ const arrival = arrivalOf(state);
2384
+ considerFinishers(state, node, null);
2385
+ for (let next = graph.edgeOffsets[node]; next < graph.edgeOffsets[node + 1]; next += 1) {
2386
+ if (banned?.has(next) || !edgeAllowed(access, next)) continue;
2387
+ const carried = arrival >= 0 && (state < guardedStates || state >= stateCount) ? advance(arrival, matches, next) : [];
2388
+ if (carried === null) continue;
2389
+ relax(stateFor(next, carried), costOfState(state) + costOf(next), state, next, null);
2390
+ }
2391
+ }
2392
+ if (!bestFinisher) return null;
2393
+ const chain = [];
2394
+ for (let state = bestArrival; state !== -1; ) {
2395
+ const via = state < stateCount ? cameVia[state] : partialEdge[state - stateCount];
2396
+ if (via >= 0) chain.push(via);
2397
+ state = state < stateCount ? cameFrom[state] : partialPrevious[state - stateCount];
2398
+ }
2399
+ chain.reverse();
2400
+ const seed = bestSeed ?? (chain.length ? seedOf.get(stateFor(chain[0], [])) ?? seeds[0] : seeds[0]);
2401
+ if (seed.edge >= 0 && chain[0] === seed.edge) chain.shift();
2402
+ return { edges: chain, finisher: bestFinisher, seed };
2403
+ }
2404
+ function seedsFor(snap, costOf) {
2405
+ if (snap.kind === "node") return [{ node: snap.node, edge: -1, cost: 0 }];
2406
+ return snap.projections.map((projection) => ({
2407
+ node: projection.target,
2408
+ edge: projection.edge,
2409
+ cost: (1 - projection.fraction) * costOf(projection.edge)
2410
+ }));
2411
+ }
2412
+ function finishersFor(snap, costOf) {
2413
+ if (snap.kind === "node") return [{ node: snap.node, edge: -1, cost: 0 }];
2414
+ return snap.projections.map((projection) => ({
2415
+ node: projection.source,
2416
+ edge: projection.edge,
2417
+ cost: projection.fraction * costOf(projection.edge)
2418
+ }));
2419
+ }
2420
+ function projectionOf(snap, edge) {
2421
+ return snap.kind === "edge" ? snap.projections.find((projection) => projection.edge === edge) : void 0;
2422
+ }
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 };
2441
+ }
2442
+ function nodeOfSnap(snap) {
2443
+ return snap.kind === "node" ? snap.node : snap.projections[0].source;
2444
+ }
2445
+ function reconnect(graph, from, to, preference, options, access) {
2446
+ const originSnap = snapToGraph(graph, from, void 0, access);
2447
+ const destinationSnap = snapToGraph(graph, to, void 0, access);
2448
+ if (!originSnap || !destinationSnap) return null;
2449
+ const labels = componentsFor(graph);
2450
+ const originLabel = labels[nodeOfSnap(originSnap)];
2451
+ const destinationLabel = labels[nodeOfSnap(destinationSnap)];
2452
+ if (originLabel === destinationLabel) return null;
2453
+ const sizes = componentSizes(labels);
2454
+ const moveOrigin = (sizes.get(originLabel) ?? 0) <= (sizes.get(destinationLabel) ?? 0);
2455
+ const wanted = moveOrigin ? destinationLabel : originLabel;
2456
+ const accept = (node) => labels[node] === wanted;
2457
+ try {
2458
+ return routeBetween(graph, from, to, preference, options, access, moveOrigin ? accept : void 0, moveOrigin ? void 0 : accept);
2459
+ } catch {
2460
+ return null;
2461
+ }
2462
+ }
2463
+ function route(graph, from, to, preference = "fastest", options = {}) {
2464
+ assertGraph(graph);
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);
2468
+ try {
2469
+ return routeBetween(graph, from, to, preference, options, access);
2470
+ } catch (error) {
2471
+ if (!(error instanceof RouterError) || error.code !== "NO_ROUTE") throw error;
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) ?? (() => {
2476
+ throw error;
2477
+ })();
2478
+ }
2479
+ }
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);
2517
+ if (!originSnap) throw new RouterError("ORIGIN_NOT_ON_GRAPH", "No routable road is close to the origin.");
2518
+ const destinationSnap = chosen?.destination ?? snapToGraph(graph, to, void 0, access, acceptDestination);
2519
+ if (!destinationSnap) throw new RouterError("DESTINATION_NOT_ON_GRAPH", "No routable road is close to the destination.");
2520
+ const distanceScale = graph.edgeDistanceScale ?? 1;
2521
+ const durationScale = graph.edgeDurationScale ?? 1;
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;
2526
+ const seeds = seedsFor(originSnap, costOf);
2527
+ const finisherList = finishersFor(destinationSnap, costOf);
2528
+ const finishers = /* @__PURE__ */ new Map();
2529
+ for (const finisher of finisherList) {
2530
+ const bucket = finishers.get(finisher.node);
2531
+ if (bucket) bucket.push(finisher);
2532
+ else finishers.set(finisher.node, [finisher]);
2533
+ }
2534
+ let direct = null;
2535
+ if (originSnap.kind === "edge" && destinationSnap.kind === "edge") {
2536
+ for (const origin of originSnap.projections) {
2537
+ const destination = projectionOf(destinationSnap, origin.edge);
2538
+ if (!destination || destination.fraction < origin.fraction) continue;
2539
+ const cost = (destination.fraction - origin.fraction) * costOf(origin.edge);
2540
+ if (!direct || cost < direct.cost) direct = { origin, destination, cost };
2541
+ }
2542
+ }
2543
+ const sameNode = originSnap.kind === "node" && destinationSnap.kind === "node" && originSnap.node === destinationSnap.node;
2544
+ const costBound = options.costBound ?? Infinity;
2545
+ const restrictions = buildTurnRestrictionIndex(graph);
2546
+ const sequenceIndex = buildTurnSequenceIndex(graph);
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);
2548
+ if (direct && direct.cost >= costBound) direct = null;
2549
+ if (!outcome && !direct) throw new RouterError("NO_ROUTE", `No ${profileOf(graph).name} route connects the selected points.`);
2550
+ const searchCost = outcome ? outcome.seed.cost + outcome.edges.reduce((sum, edge) => sum + costOf(edge), 0) + outcome.finisher.cost : Infinity;
2551
+ const partial = (projection, fraction) => ({
2552
+ distance: fraction * graph.edgeDistancesMeters[projection.edge] * distanceScale,
2553
+ duration: fraction * graph.edgeDurationsSeconds[projection.edge] * durationScale
2554
+ });
2555
+ const coordinates = [];
2556
+ const push = (point) => coordinates.push([point.lng, point.lat]);
2557
+ const nodeIds = [];
2558
+ let edgeIds = [];
2559
+ let distanceMeters = 0;
2560
+ let durationSeconds = 0;
2561
+ let travelledAtOrigin = 0;
2562
+ let travelledAtDestination = 0;
2563
+ if (direct && direct.cost <= searchCost) {
2564
+ for (const point of geometryBetween(direct.origin, direct.destination)) push(point);
2565
+ const span = partial(direct.origin, direct.destination.fraction - direct.origin.fraction);
2566
+ distanceMeters = span.distance;
2567
+ durationSeconds = span.duration;
2568
+ travelledAtOrigin = span.distance;
2569
+ } else {
2570
+ const { edges, finisher, seed } = outcome;
2571
+ edgeIds = edges;
2572
+ const entry = seed.edge >= 0 ? projectionOf(originSnap, seed.edge) : void 0;
2573
+ push(originSnap.point);
2574
+ if (entry) {
2575
+ for (const point of geometryAfter(entry).slice(1)) push(point);
2576
+ const span = partial(entry, 1 - entry.fraction);
2577
+ distanceMeters += span.distance;
2578
+ durationSeconds += span.duration;
2579
+ travelledAtOrigin = span.distance;
2580
+ nodeIds.push(entry.target);
2581
+ push(coordinateFor2(graph, entry.target));
2582
+ } else {
2583
+ nodeIds.push(seed.node);
2584
+ }
2585
+ const scale = graph.coordinateScale ?? 1;
2586
+ for (const edge of edges) {
2587
+ distanceMeters += graph.edgeDistancesMeters[edge] * distanceScale;
2588
+ durationSeconds += conditionallySlowed?.get(edge) ?? graph.edgeDurationsSeconds[edge] * durationScale;
2589
+ if (graph.edgeShapeOffsets && graph.edgeShapeLatitudes && graph.edgeShapeLongitudes) {
2590
+ for (let point = graph.edgeShapeOffsets[edge]; point < graph.edgeShapeOffsets[edge + 1]; point += 1) {
2591
+ coordinates.push([graph.edgeShapeLongitudes[point] / scale, graph.edgeShapeLatitudes[point] / scale]);
2592
+ }
2593
+ }
2594
+ nodeIds.push(graph.edgeTargets[edge]);
2595
+ push(coordinateFor2(graph, graph.edgeTargets[edge]));
2596
+ }
2597
+ const exit = finisher.edge >= 0 ? projectionOf(destinationSnap, finisher.edge) : void 0;
2598
+ if (exit) {
2599
+ for (const point of geometryBefore(exit)) push(point);
2600
+ const span = partial(exit, exit.fraction);
2601
+ distanceMeters += span.distance;
2602
+ durationSeconds += span.duration;
2603
+ travelledAtDestination = span.distance;
2604
+ }
2605
+ }
2606
+ return {
2607
+ nodeIds,
2608
+ edgeIds,
2609
+ distanceMeters,
2610
+ durationSeconds,
2611
+ originSnap: snapSummary(originSnap, travelledAtOrigin),
2612
+ destinationSnap: snapSummary(destinationSnap, travelledAtDestination),
2613
+ geometry: { type: "LineString", coordinates }
2614
+ };
2615
+ }
2616
+
2617
+ export {
2618
+ accessMaskFor,
2619
+ edgeAllowed,
2620
+ allowsEverything,
2621
+ zigzag,
2622
+ unzigzag,
2623
+ VarintWriter,
2624
+ VarintReader,
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,
2641
+ parseLengthMeters,
2642
+ parseWeightTonnes,
2643
+ carRoutingProfile,
2644
+ motorcycleRoutingProfile,
2645
+ truckRoutingProfile,
2646
+ routingProfiles,
2647
+ getRoutingProfile,
2648
+ getRoutingProfileById,
2649
+ profileOf,
2650
+ maxSpeedMpsFor,
2651
+ encodeGraph,
2652
+ decodeGraph,
2653
+ encodeCompactGraph,
2654
+ encodeSharedGraph,
2655
+ encodeProfileChunk,
2656
+ decodeProfileChunk,
2657
+ decodeSharedGraph,
2658
+ combineProfiledGraph,
2659
+ searchBoundsFor,
2660
+ straightLineAtMost,
2661
+ componentsFor,
2662
+ componentSizes,
2663
+ edgeSource,
2664
+ edgePolyline,
2665
+ createEdgeSpatialIndex,
2666
+ edgeSpatialIndexFor,
2667
+ nearbyEdgeIds,
2668
+ createSpatialIndex,
2669
+ withSpatialIndex,
2670
+ snapToGraph,
2671
+ touchesArea,
2672
+ accessAvoiding,
2673
+ countAvoided,
2674
+ toDecimetres,
2675
+ toHalfTonnes,
2676
+ fromDecimetres,
2677
+ fromHalfTonnes,
2678
+ packEdgeLimits,
2679
+ accessForVehicle,
2680
+ countBlocked,
2681
+ RouterError,
2682
+ buildTurnRestrictionIndex,
2683
+ isTurnBanned,
2684
+ packTurnRestrictions,
2685
+ buildTurnSequenceIndex,
2686
+ packTurnSequences,
2687
+ guardedJunctionsFor,
2688
+ findNearestNode,
2689
+ route
2690
+ };