@tileflow/routing 0.0.0-bootstrap.0 → 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +335 -4
- package/dist/chunk-ECBK2QPL.js +450 -0
- package/dist/chunk-MCATD4FU.js +256 -0
- package/dist/client.d.ts +53 -0
- package/dist/client.js +17 -0
- package/dist/contract.d.ts +708 -0
- package/dist/contract.js +72 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +86 -0
- package/package.json +51 -7
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
// src/contract.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var routingModes = ["driving", "walking", "cycling", "truck"];
|
|
4
|
+
var routingAvoidances = ["tolls", "highways", "ferries"];
|
|
5
|
+
var routingTrafficKinds = ["none"];
|
|
6
|
+
var routingLanguages = [
|
|
7
|
+
"ar-SA",
|
|
8
|
+
"bg-BG",
|
|
9
|
+
"ca-ES",
|
|
10
|
+
"cs-CZ",
|
|
11
|
+
"da-DK",
|
|
12
|
+
"de-DE",
|
|
13
|
+
"el-GR",
|
|
14
|
+
"en-AU",
|
|
15
|
+
"en-GB",
|
|
16
|
+
"en-US",
|
|
17
|
+
"es-ES",
|
|
18
|
+
"et-EE",
|
|
19
|
+
"fi-FI",
|
|
20
|
+
"fr-FR",
|
|
21
|
+
"hi-IN",
|
|
22
|
+
"hu-HU",
|
|
23
|
+
"it-IT",
|
|
24
|
+
"ja-JP",
|
|
25
|
+
"ko-KR",
|
|
26
|
+
"mn-MN",
|
|
27
|
+
"nb-NO",
|
|
28
|
+
"nl-NL",
|
|
29
|
+
"pl-PL",
|
|
30
|
+
"pt-BR",
|
|
31
|
+
"pt-PT",
|
|
32
|
+
"ro-RO",
|
|
33
|
+
"ru-RU",
|
|
34
|
+
"sk-SK",
|
|
35
|
+
"sl-SI",
|
|
36
|
+
"sv-SE",
|
|
37
|
+
"tr-TR",
|
|
38
|
+
"uk-UA",
|
|
39
|
+
"vi-VN"
|
|
40
|
+
];
|
|
41
|
+
var routingLimits = Object.freeze({
|
|
42
|
+
maximumRequestBytes: 8 * 1024,
|
|
43
|
+
maximumResponseBytes: 4 * 1024 * 1024,
|
|
44
|
+
minimumLocations: 2,
|
|
45
|
+
maximumLocations: 5,
|
|
46
|
+
maximumAlternatives: 2,
|
|
47
|
+
/** Sum of straight-line distances between consecutive locations, per mode. */
|
|
48
|
+
maximumDirectDistanceMeters: Object.freeze({
|
|
49
|
+
driving: 1e6,
|
|
50
|
+
walking: 1e5,
|
|
51
|
+
cycling: 3e5,
|
|
52
|
+
truck: 1e6
|
|
53
|
+
}),
|
|
54
|
+
maximumMatrixSources: 25,
|
|
55
|
+
maximumMatrixTargets: 25,
|
|
56
|
+
/** Straight-line distance between any matrix source and target, per mode. */
|
|
57
|
+
maximumMatrixDistanceMeters: Object.freeze({
|
|
58
|
+
driving: 2e5,
|
|
59
|
+
walking: 1e4,
|
|
60
|
+
cycling: 1e4,
|
|
61
|
+
truck: 2e5
|
|
62
|
+
}),
|
|
63
|
+
minimumTracePositions: 2,
|
|
64
|
+
maximumTracePositions: 100,
|
|
65
|
+
/** Sum of straight-line distances between consecutive trace positions. */
|
|
66
|
+
maximumTraceDistanceMeters: 2e5,
|
|
67
|
+
/** Straight-line distance between consecutive trace positions. */
|
|
68
|
+
maximumTraceGapMeters: 2e3,
|
|
69
|
+
maximumTraceAccuracyMeters: 100,
|
|
70
|
+
maximumRoads: 2e3,
|
|
71
|
+
maximumContours: 4,
|
|
72
|
+
minimumMinutes: 1,
|
|
73
|
+
maximumMinutes: 60,
|
|
74
|
+
maximumSteps: 2e3,
|
|
75
|
+
maximumGeometryPositions: 2e5,
|
|
76
|
+
maximumIsochroneVertices: 2e5
|
|
77
|
+
});
|
|
78
|
+
var routingAttribution = "\xA9 OpenStreetMap contributors";
|
|
79
|
+
var longitude = z.number().finite().min(-180).max(180);
|
|
80
|
+
var latitude = z.number().finite().min(-90).max(90);
|
|
81
|
+
var routingPositionSchema = z.tuple([longitude, latitude]);
|
|
82
|
+
function routingDirectDistance(a, b) {
|
|
83
|
+
const radians = Math.PI / 180;
|
|
84
|
+
const dLat = (b[1] - a[1]) * radians;
|
|
85
|
+
const dLng = (b[0] - a[0]) * radians;
|
|
86
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(a[1] * radians) * Math.cos(b[1] * radians) * Math.sin(dLng / 2) ** 2;
|
|
87
|
+
return 2 * 63710088e-1 * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
88
|
+
}
|
|
89
|
+
var routingVehicleSchema = z.object({
|
|
90
|
+
heightMeters: z.number().finite().min(1).max(10),
|
|
91
|
+
widthMeters: z.number().finite().min(1).max(5),
|
|
92
|
+
lengthMeters: z.number().finite().min(1).max(40),
|
|
93
|
+
weightTonnes: z.number().finite().min(0.5).max(100),
|
|
94
|
+
axleLoadTonnes: z.number().finite().min(0.5).max(40),
|
|
95
|
+
axleCount: z.number().int().min(2).max(20),
|
|
96
|
+
hazardousMaterials: z.boolean()
|
|
97
|
+
}).partial().strict();
|
|
98
|
+
var travelOptions = {
|
|
99
|
+
mode: z.enum(routingModes),
|
|
100
|
+
/** Only for `truck`. */
|
|
101
|
+
vehicle: routingVehicleSchema.optional(),
|
|
102
|
+
avoid: z.array(z.enum(routingAvoidances)).max(routingAvoidances.length).optional(),
|
|
103
|
+
traffic: z.enum(routingTrafficKinds).optional()
|
|
104
|
+
};
|
|
105
|
+
function refineTravelOptions(request, context) {
|
|
106
|
+
if (request.vehicle && request.mode !== "truck") {
|
|
107
|
+
context.addIssue({
|
|
108
|
+
code: "custom",
|
|
109
|
+
path: ["vehicle"],
|
|
110
|
+
message: "Vehicle dimensions apply only to truck."
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (request.avoid && new Set(request.avoid).size !== request.avoid.length) {
|
|
114
|
+
context.addIssue({ code: "custom", path: ["avoid"], message: "Avoid each kind once." });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
var routingRouteRequestSchema = z.object({
|
|
118
|
+
locations: z.array(routingPositionSchema).min(routingLimits.minimumLocations).max(routingLimits.maximumLocations),
|
|
119
|
+
...travelOptions,
|
|
120
|
+
alternatives: z.number().int().min(0).max(routingLimits.maximumAlternatives).optional(),
|
|
121
|
+
language: z.enum(routingLanguages).optional()
|
|
122
|
+
}).strict().superRefine((request, context) => {
|
|
123
|
+
refineTravelOptions(request, context);
|
|
124
|
+
if ((request.alternatives ?? 0) > 0 && request.locations.length !== 2) {
|
|
125
|
+
context.addIssue({
|
|
126
|
+
code: "custom",
|
|
127
|
+
path: ["alternatives"],
|
|
128
|
+
message: "Alternatives are available only between two locations."
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
let distance = 0;
|
|
132
|
+
for (let index = 1; index < request.locations.length; index += 1) {
|
|
133
|
+
distance += routingDirectDistance(request.locations[index - 1], request.locations[index]);
|
|
134
|
+
}
|
|
135
|
+
if (distance > routingLimits.maximumDirectDistanceMeters[request.mode]) {
|
|
136
|
+
context.addIssue({
|
|
137
|
+
code: "custom",
|
|
138
|
+
path: ["locations"],
|
|
139
|
+
message: `The locations are too far apart for ${request.mode}.`
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
var routingIsochroneRequestSchema = z.object({
|
|
144
|
+
location: routingPositionSchema,
|
|
145
|
+
...travelOptions,
|
|
146
|
+
minutes: z.array(z.number().int().min(routingLimits.minimumMinutes).max(routingLimits.maximumMinutes)).min(1).max(routingLimits.maximumContours)
|
|
147
|
+
}).strict().superRefine((request, context) => {
|
|
148
|
+
refineTravelOptions(request, context);
|
|
149
|
+
for (let index = 1; index < request.minutes.length; index += 1) {
|
|
150
|
+
if (request.minutes[index] <= request.minutes[index - 1]) {
|
|
151
|
+
context.addIssue({
|
|
152
|
+
code: "custom",
|
|
153
|
+
path: ["minutes"],
|
|
154
|
+
message: "Minutes rise strictly."
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
var routingMatrixRequestSchema = z.object({
|
|
161
|
+
sources: z.array(routingPositionSchema).min(1).max(routingLimits.maximumMatrixSources),
|
|
162
|
+
targets: z.array(routingPositionSchema).min(1).max(routingLimits.maximumMatrixTargets),
|
|
163
|
+
...travelOptions
|
|
164
|
+
}).strict().superRefine((request, context) => {
|
|
165
|
+
refineTravelOptions(request, context);
|
|
166
|
+
const limit = routingLimits.maximumMatrixDistanceMeters[request.mode];
|
|
167
|
+
for (const source of request.sources) {
|
|
168
|
+
for (const target of request.targets) {
|
|
169
|
+
if (routingDirectDistance(source, target) > limit) {
|
|
170
|
+
context.addIssue({
|
|
171
|
+
code: "custom",
|
|
172
|
+
path: ["targets"],
|
|
173
|
+
message: `A source and a target are too far apart for ${request.mode}.`
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
var routingMatchRequestSchema = z.object({
|
|
181
|
+
trace: z.array(routingPositionSchema).min(routingLimits.minimumTracePositions).max(routingLimits.maximumTracePositions),
|
|
182
|
+
/** Seconds since the Unix epoch at each trace position, non-decreasing. */
|
|
183
|
+
timestamps: z.array(z.number().int().min(0).max(4102444800)).optional(),
|
|
184
|
+
/** GPS accuracy in metres; the default suits a phone outdoors. */
|
|
185
|
+
accuracy: z.number().finite().min(1).max(routingLimits.maximumTraceAccuracyMeters).optional(),
|
|
186
|
+
mode: z.enum(routingModes),
|
|
187
|
+
vehicle: routingVehicleSchema.optional()
|
|
188
|
+
}).strict().superRefine((request, context) => {
|
|
189
|
+
refineTravelOptions(request, context);
|
|
190
|
+
if (request.timestamps) {
|
|
191
|
+
if (request.timestamps.length !== request.trace.length) {
|
|
192
|
+
context.addIssue({
|
|
193
|
+
code: "custom",
|
|
194
|
+
path: ["timestamps"],
|
|
195
|
+
message: "Give one timestamp per trace position."
|
|
196
|
+
});
|
|
197
|
+
} else if (request.timestamps.some(
|
|
198
|
+
(time, index) => index > 0 && time < request.timestamps[index - 1]
|
|
199
|
+
)) {
|
|
200
|
+
context.addIssue({
|
|
201
|
+
code: "custom",
|
|
202
|
+
path: ["timestamps"],
|
|
203
|
+
message: "Timestamps never go back."
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let distance = 0;
|
|
208
|
+
for (let index = 1; index < request.trace.length; index += 1) {
|
|
209
|
+
const gap = routingDirectDistance(request.trace[index - 1], request.trace[index]);
|
|
210
|
+
if (gap > routingLimits.maximumTraceGapMeters) {
|
|
211
|
+
context.addIssue({
|
|
212
|
+
code: "custom",
|
|
213
|
+
path: ["trace", index],
|
|
214
|
+
message: "Consecutive trace positions are more than 2 km apart."
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
distance += gap;
|
|
219
|
+
}
|
|
220
|
+
if (distance > routingLimits.maximumTraceDistanceMeters) {
|
|
221
|
+
context.addIssue({ code: "custom", path: ["trace"], message: "The trace is too long." });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
var routingNearestRequestSchema = z.object({ location: routingPositionSchema, mode: z.enum(routingModes) }).strict();
|
|
225
|
+
var routingManeuvers = [
|
|
226
|
+
"depart",
|
|
227
|
+
"arrive",
|
|
228
|
+
"continue",
|
|
229
|
+
"slight-right",
|
|
230
|
+
"right",
|
|
231
|
+
"sharp-right",
|
|
232
|
+
"u-turn",
|
|
233
|
+
"sharp-left",
|
|
234
|
+
"left",
|
|
235
|
+
"slight-left",
|
|
236
|
+
"ramp-straight",
|
|
237
|
+
"ramp-right",
|
|
238
|
+
"ramp-left",
|
|
239
|
+
"exit-right",
|
|
240
|
+
"exit-left",
|
|
241
|
+
"keep-straight",
|
|
242
|
+
"keep-right",
|
|
243
|
+
"keep-left",
|
|
244
|
+
"merge",
|
|
245
|
+
"merge-right",
|
|
246
|
+
"merge-left",
|
|
247
|
+
"roundabout-enter",
|
|
248
|
+
"roundabout-exit",
|
|
249
|
+
"ferry-enter",
|
|
250
|
+
"ferry-exit",
|
|
251
|
+
"elevator",
|
|
252
|
+
"stairs",
|
|
253
|
+
"escalator",
|
|
254
|
+
"building-enter",
|
|
255
|
+
"building-exit"
|
|
256
|
+
];
|
|
257
|
+
var meters = z.number().finite().min(0);
|
|
258
|
+
var seconds = z.number().finite().min(0);
|
|
259
|
+
var text = z.string().min(1).max(500);
|
|
260
|
+
var routingStepSchema = z.object({
|
|
261
|
+
maneuver: z.enum(routingManeuvers),
|
|
262
|
+
instruction: text,
|
|
263
|
+
/** Street names and road numbers the step follows, as OpenStreetMap records them. */
|
|
264
|
+
names: z.array(z.string().min(1).max(200)).max(8).optional(),
|
|
265
|
+
/** Exit number of a highway exit. */
|
|
266
|
+
exit: z.string().min(1).max(50).optional(),
|
|
267
|
+
/** Destinations signed at a highway exit or ramp. */
|
|
268
|
+
toward: z.array(z.string().min(1).max(200)).max(8).optional(),
|
|
269
|
+
/** Exit to take at a roundabout, counted from the entry. */
|
|
270
|
+
roundaboutExit: z.number().int().min(1).max(32).optional(),
|
|
271
|
+
distance: meters,
|
|
272
|
+
duration: seconds,
|
|
273
|
+
location: routingPositionSchema
|
|
274
|
+
}).strict();
|
|
275
|
+
var routingLineStringSchema = z.object({
|
|
276
|
+
type: z.literal("LineString"),
|
|
277
|
+
coordinates: z.array(routingPositionSchema).min(2).max(routingLimits.maximumGeometryPositions)
|
|
278
|
+
}).strict();
|
|
279
|
+
var routingLegSchema = z.object({
|
|
280
|
+
distance: meters,
|
|
281
|
+
duration: seconds,
|
|
282
|
+
steps: z.array(routingStepSchema).min(1).max(routingLimits.maximumSteps)
|
|
283
|
+
}).strict();
|
|
284
|
+
var routingRouteSchema = z.object({
|
|
285
|
+
distance: meters,
|
|
286
|
+
duration: seconds,
|
|
287
|
+
/** Whether the route uses a toll road, a highway or a ferry. */
|
|
288
|
+
hasTolls: z.boolean(),
|
|
289
|
+
hasHighways: z.boolean(),
|
|
290
|
+
hasFerries: z.boolean(),
|
|
291
|
+
geometry: routingLineStringSchema,
|
|
292
|
+
legs: z.array(routingLegSchema).min(1).max(routingLimits.maximumLocations - 1)
|
|
293
|
+
}).strict();
|
|
294
|
+
var routingNetworkSchema = z.object({ releaseId: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,126}[a-z0-9]$/u) }).strict();
|
|
295
|
+
var routingUsageSchema = z.object({ units: z.number().int().min(0) }).strict();
|
|
296
|
+
var routingRoadClasses = [
|
|
297
|
+
"motorway",
|
|
298
|
+
"trunk",
|
|
299
|
+
"primary",
|
|
300
|
+
"secondary",
|
|
301
|
+
"tertiary",
|
|
302
|
+
"unclassified",
|
|
303
|
+
"residential",
|
|
304
|
+
"service"
|
|
305
|
+
];
|
|
306
|
+
var routingRoadSurfaces = [
|
|
307
|
+
"paved-smooth",
|
|
308
|
+
"paved",
|
|
309
|
+
"paved-rough",
|
|
310
|
+
"compacted",
|
|
311
|
+
"dirt",
|
|
312
|
+
"gravel",
|
|
313
|
+
"path",
|
|
314
|
+
"impassable"
|
|
315
|
+
];
|
|
316
|
+
var routingRoadSchema = z.object({
|
|
317
|
+
names: z.array(z.string().min(1).max(200)).max(8),
|
|
318
|
+
/** The OpenStreetMap way the road belongs to. */
|
|
319
|
+
wayId: z.number().int().positive().optional(),
|
|
320
|
+
class: z.enum(routingRoadClasses),
|
|
321
|
+
surface: z.enum(routingRoadSurfaces).optional(),
|
|
322
|
+
lanes: z.number().int().min(1).max(16).optional(),
|
|
323
|
+
/** The speed limit as tagged in OpenStreetMap, or `null` where none is recorded. */
|
|
324
|
+
speedLimit: z.object({ kmh: z.number().int().min(1).max(300), source: z.literal("tagged") }).strict().nullable(),
|
|
325
|
+
toll: z.boolean(),
|
|
326
|
+
bridge: z.boolean(),
|
|
327
|
+
tunnel: z.boolean(),
|
|
328
|
+
roundabout: z.boolean(),
|
|
329
|
+
length: meters
|
|
330
|
+
}).strict();
|
|
331
|
+
var ring = z.array(routingPositionSchema).min(4);
|
|
332
|
+
var polygon = z.object({ type: z.literal("Polygon"), coordinates: z.array(ring).min(1) }).strict();
|
|
333
|
+
var multiPolygon = z.object({ type: z.literal("MultiPolygon"), coordinates: z.array(z.array(ring).min(1)).min(1) }).strict();
|
|
334
|
+
var routingIsochroneFeatureSchema = z.object({
|
|
335
|
+
type: z.literal("Feature"),
|
|
336
|
+
properties: z.object({
|
|
337
|
+
minutes: z.number().int().min(routingLimits.minimumMinutes).max(routingLimits.maximumMinutes)
|
|
338
|
+
}).strict(),
|
|
339
|
+
geometry: z.union([polygon, multiPolygon])
|
|
340
|
+
}).strict();
|
|
341
|
+
var routingIsochroneCollectionSchema = z.object({
|
|
342
|
+
type: z.literal("FeatureCollection"),
|
|
343
|
+
features: z.array(routingIsochroneFeatureSchema).min(1).max(routingLimits.maximumContours)
|
|
344
|
+
}).strict();
|
|
345
|
+
var answer = {
|
|
346
|
+
schemaVersion: z.literal(1),
|
|
347
|
+
network: routingNetworkSchema,
|
|
348
|
+
attribution: z.literal(routingAttribution),
|
|
349
|
+
traffic: z.enum(routingTrafficKinds)
|
|
350
|
+
};
|
|
351
|
+
var routingRouteResponseSchema = z.object({
|
|
352
|
+
...answer,
|
|
353
|
+
routes: z.array(routingRouteSchema).min(1).max(routingLimits.maximumAlternatives + 1),
|
|
354
|
+
usage: routingUsageSchema
|
|
355
|
+
}).strict();
|
|
356
|
+
var routingIsochroneResponseSchema = z.object({ ...answer, isochrones: routingIsochroneCollectionSchema, usage: routingUsageSchema }).strict();
|
|
357
|
+
var routingMatrixResponseSchema = z.object({
|
|
358
|
+
...answer,
|
|
359
|
+
/**
|
|
360
|
+
* Seconds from each source (rows) to each target (columns); `null` where no route was found,
|
|
361
|
+
* because none exists or because the service could not find it within its deadline.
|
|
362
|
+
*/
|
|
363
|
+
durations: z.array(z.array(seconds.nullable()).min(1).max(routingLimits.maximumMatrixTargets)).min(1).max(routingLimits.maximumMatrixSources),
|
|
364
|
+
/** Metres, in the same order; `null` where no route was found. */
|
|
365
|
+
distances: z.array(z.array(meters.nullable()).min(1).max(routingLimits.maximumMatrixTargets)).min(1).max(routingLimits.maximumMatrixSources),
|
|
366
|
+
usage: routingUsageSchema
|
|
367
|
+
}).strict();
|
|
368
|
+
var routingMatchResponseSchema = z.object({
|
|
369
|
+
...answer,
|
|
370
|
+
distance: meters,
|
|
371
|
+
duration: seconds,
|
|
372
|
+
geometry: routingLineStringSchema,
|
|
373
|
+
/** One entry per trace position: where it was matched, or `null` when it was not. */
|
|
374
|
+
points: z.array(z.object({ location: routingPositionSchema, distance: meters }).strict().nullable()).min(routingLimits.minimumTracePositions).max(routingLimits.maximumTracePositions),
|
|
375
|
+
/** The roads of the matched path, in order; consecutive pieces of one road are merged. */
|
|
376
|
+
roads: z.array(routingRoadSchema).min(1).max(routingLimits.maximumRoads),
|
|
377
|
+
usage: routingUsageSchema
|
|
378
|
+
}).strict();
|
|
379
|
+
var routingNearestResponseSchema = z.object({
|
|
380
|
+
...answer,
|
|
381
|
+
/** The point of the road nearest to the request, and its distance from it in metres. */
|
|
382
|
+
location: routingPositionSchema,
|
|
383
|
+
distance: meters,
|
|
384
|
+
road: routingRoadSchema,
|
|
385
|
+
usage: routingUsageSchema
|
|
386
|
+
}).strict();
|
|
387
|
+
var routingOperations = ["route", "isochrone", "matrix", "match", "nearest"];
|
|
388
|
+
var routingErrorCodes = [
|
|
389
|
+
"ROUTING_INVALID_REQUEST",
|
|
390
|
+
"ROUTING_REQUEST_TOO_LARGE",
|
|
391
|
+
"ROUTING_ACCESS_DENIED",
|
|
392
|
+
"ROUTING_ORIGIN_NOT_ALLOWED",
|
|
393
|
+
"ROUTING_NO_ROUTE",
|
|
394
|
+
"ROUTING_RATE_LIMITED",
|
|
395
|
+
"ROUTING_QUOTA_EXCEEDED",
|
|
396
|
+
"ROUTING_CANCELLED",
|
|
397
|
+
"ROUTING_INVALID_RESPONSE",
|
|
398
|
+
"ROUTING_UNAVAILABLE",
|
|
399
|
+
"ROUTING_TIMEOUT"
|
|
400
|
+
];
|
|
401
|
+
var routingErrorStatus = Object.freeze({
|
|
402
|
+
ROUTING_INVALID_REQUEST: 400,
|
|
403
|
+
ROUTING_REQUEST_TOO_LARGE: 413,
|
|
404
|
+
ROUTING_ACCESS_DENIED: 403,
|
|
405
|
+
ROUTING_ORIGIN_NOT_ALLOWED: 403,
|
|
406
|
+
ROUTING_NO_ROUTE: 422,
|
|
407
|
+
ROUTING_RATE_LIMITED: 429,
|
|
408
|
+
ROUTING_QUOTA_EXCEEDED: 429,
|
|
409
|
+
ROUTING_CANCELLED: 499,
|
|
410
|
+
ROUTING_INVALID_RESPONSE: 502,
|
|
411
|
+
ROUTING_UNAVAILABLE: 503,
|
|
412
|
+
ROUTING_TIMEOUT: 504
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
export {
|
|
416
|
+
routingModes,
|
|
417
|
+
routingAvoidances,
|
|
418
|
+
routingTrafficKinds,
|
|
419
|
+
routingLanguages,
|
|
420
|
+
routingLimits,
|
|
421
|
+
routingAttribution,
|
|
422
|
+
routingPositionSchema,
|
|
423
|
+
routingDirectDistance,
|
|
424
|
+
routingVehicleSchema,
|
|
425
|
+
routingRouteRequestSchema,
|
|
426
|
+
routingIsochroneRequestSchema,
|
|
427
|
+
routingMatrixRequestSchema,
|
|
428
|
+
routingMatchRequestSchema,
|
|
429
|
+
routingNearestRequestSchema,
|
|
430
|
+
routingManeuvers,
|
|
431
|
+
routingStepSchema,
|
|
432
|
+
routingLineStringSchema,
|
|
433
|
+
routingLegSchema,
|
|
434
|
+
routingRouteSchema,
|
|
435
|
+
routingNetworkSchema,
|
|
436
|
+
routingUsageSchema,
|
|
437
|
+
routingRoadClasses,
|
|
438
|
+
routingRoadSurfaces,
|
|
439
|
+
routingRoadSchema,
|
|
440
|
+
routingIsochroneFeatureSchema,
|
|
441
|
+
routingIsochroneCollectionSchema,
|
|
442
|
+
routingRouteResponseSchema,
|
|
443
|
+
routingIsochroneResponseSchema,
|
|
444
|
+
routingMatrixResponseSchema,
|
|
445
|
+
routingMatchResponseSchema,
|
|
446
|
+
routingNearestResponseSchema,
|
|
447
|
+
routingOperations,
|
|
448
|
+
routingErrorCodes,
|
|
449
|
+
routingErrorStatus
|
|
450
|
+
};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
routingErrorCodes,
|
|
3
|
+
routingErrorStatus,
|
|
4
|
+
routingIsochroneRequestSchema,
|
|
5
|
+
routingIsochroneResponseSchema,
|
|
6
|
+
routingLimits,
|
|
7
|
+
routingMatchRequestSchema,
|
|
8
|
+
routingMatchResponseSchema,
|
|
9
|
+
routingMatrixRequestSchema,
|
|
10
|
+
routingMatrixResponseSchema,
|
|
11
|
+
routingNearestRequestSchema,
|
|
12
|
+
routingNearestResponseSchema,
|
|
13
|
+
routingRouteRequestSchema,
|
|
14
|
+
routingRouteResponseSchema
|
|
15
|
+
} from "./chunk-ECBK2QPL.js";
|
|
16
|
+
|
|
17
|
+
// src/client.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var RoutingError = class extends Error {
|
|
20
|
+
code;
|
|
21
|
+
requestId;
|
|
22
|
+
status;
|
|
23
|
+
retryAfterSeconds;
|
|
24
|
+
constructor(message, input) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "RoutingError";
|
|
27
|
+
this.code = input.code ?? null;
|
|
28
|
+
this.requestId = input.requestId ?? null;
|
|
29
|
+
this.status = input.status;
|
|
30
|
+
this.retryAfterSeconds = input.retryAfterSeconds ?? null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
async function route(request, options) {
|
|
34
|
+
const parsed = routingRouteRequestSchema.safeParse(request);
|
|
35
|
+
if (!parsed.success) throw new Error("Invalid Tileflow route request");
|
|
36
|
+
const legs = parsed.data.locations.length - 1;
|
|
37
|
+
return requestRouting(
|
|
38
|
+
"/v1/routing/route",
|
|
39
|
+
parsed.data,
|
|
40
|
+
options,
|
|
41
|
+
routingRouteResponseSchema,
|
|
42
|
+
(response) => response.routes.length <= (parsed.data.alternatives ?? 0) + 1 && response.routes.every((candidate) => candidate.legs.length === legs)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
async function isochrone(request, options) {
|
|
46
|
+
const parsed = routingIsochroneRequestSchema.safeParse(request);
|
|
47
|
+
if (!parsed.success) throw new Error("Invalid Tileflow isochrone request");
|
|
48
|
+
return requestRouting(
|
|
49
|
+
"/v1/routing/isochrone",
|
|
50
|
+
parsed.data,
|
|
51
|
+
options,
|
|
52
|
+
routingIsochroneResponseSchema,
|
|
53
|
+
(response) => response.isochrones.features.length === parsed.data.minutes.length && response.isochrones.features.every(
|
|
54
|
+
(feature, index) => feature.properties.minutes === parsed.data.minutes[index]
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
async function matrix(request, options) {
|
|
59
|
+
const parsed = routingMatrixRequestSchema.safeParse(request);
|
|
60
|
+
if (!parsed.success) throw new Error("Invalid Tileflow matrix request");
|
|
61
|
+
const rows = parsed.data.sources.length;
|
|
62
|
+
const columns = parsed.data.targets.length;
|
|
63
|
+
const isGrid = (grid) => grid.length === rows && grid.every((row) => row.length === columns);
|
|
64
|
+
return requestRouting(
|
|
65
|
+
"/v1/routing/matrix",
|
|
66
|
+
parsed.data,
|
|
67
|
+
options,
|
|
68
|
+
routingMatrixResponseSchema,
|
|
69
|
+
(response) => isGrid(response.durations) && isGrid(response.distances)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
async function match(request, options) {
|
|
73
|
+
const parsed = routingMatchRequestSchema.safeParse(request);
|
|
74
|
+
if (!parsed.success) throw new Error("Invalid Tileflow match request");
|
|
75
|
+
return requestRouting(
|
|
76
|
+
"/v1/routing/match",
|
|
77
|
+
parsed.data,
|
|
78
|
+
options,
|
|
79
|
+
routingMatchResponseSchema,
|
|
80
|
+
(response) => response.points.length === parsed.data.trace.length
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
async function nearest(request, options) {
|
|
84
|
+
const parsed = routingNearestRequestSchema.safeParse(request);
|
|
85
|
+
if (!parsed.success) throw new Error("Invalid Tileflow nearest request");
|
|
86
|
+
return requestRouting(
|
|
87
|
+
"/v1/routing/nearest",
|
|
88
|
+
parsed.data,
|
|
89
|
+
options,
|
|
90
|
+
routingNearestResponseSchema,
|
|
91
|
+
() => true
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
var safeErrorText = z.string().trim().min(1).max(512).refine((value) => !/\p{Cc}/u.test(value));
|
|
95
|
+
var requestId = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u);
|
|
96
|
+
var routingErrorResponseSchema = z.object({ code: z.enum(routingErrorCodes), error: safeErrorText, requestId: requestId.optional() }).strict();
|
|
97
|
+
async function requestRouting(path, request, options, responseSchema, isExpectedResponse) {
|
|
98
|
+
const bodyText = JSON.stringify(request);
|
|
99
|
+
if (new TextEncoder().encode(bodyText).byteLength > routingLimits.maximumRequestBytes)
|
|
100
|
+
throw new Error("Tileflow Routing request is too large");
|
|
101
|
+
const apiKey = normalizeApiKey(options.apiKey);
|
|
102
|
+
const app = normalizeApp(options.app);
|
|
103
|
+
const apiUrl = normalizeApiUrl(options.apiUrl ?? "https://api.tileflow.dev");
|
|
104
|
+
const fetcher = options.fetch ?? fetch;
|
|
105
|
+
let response;
|
|
106
|
+
try {
|
|
107
|
+
response = await fetcher(`${apiUrl}${path}`, {
|
|
108
|
+
body: bodyText,
|
|
109
|
+
headers: {
|
|
110
|
+
Accept: "application/json",
|
|
111
|
+
Authorization: `Bearer ${apiKey}`,
|
|
112
|
+
"Content-Type": "application/json",
|
|
113
|
+
...app ? { "X-Tileflow-App": app.id } : {},
|
|
114
|
+
...app?.certificateSha256 ? { "X-Tileflow-App-Certificate": app.certificateSha256 } : {}
|
|
115
|
+
},
|
|
116
|
+
method: "POST",
|
|
117
|
+
redirect: "error",
|
|
118
|
+
signal: options.signal
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
122
|
+
throw new RoutingError("Tileflow Routing request failed", { status: 0 });
|
|
123
|
+
}
|
|
124
|
+
if (response.redirected) {
|
|
125
|
+
void response.body?.cancel().catch(() => void 0);
|
|
126
|
+
throw new RoutingError("Tileflow Routing request failed", { status: response.status });
|
|
127
|
+
}
|
|
128
|
+
if (!response.ok) throw await readRoutingError(response, options.signal);
|
|
129
|
+
let body;
|
|
130
|
+
try {
|
|
131
|
+
body = await readBoundedJson(response, routingLimits.maximumResponseBytes, options.signal);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
134
|
+
throw new RoutingError("Tileflow Routing returned an invalid response", {
|
|
135
|
+
status: response.status
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const parsed = responseSchema.safeParse(body);
|
|
139
|
+
if (!parsed.success || !isExpectedResponse(parsed.data))
|
|
140
|
+
throw new RoutingError("Tileflow Routing returned an invalid response", {
|
|
141
|
+
status: response.status
|
|
142
|
+
});
|
|
143
|
+
return parsed.data;
|
|
144
|
+
}
|
|
145
|
+
function normalizeApp(value) {
|
|
146
|
+
if (value === void 0) return null;
|
|
147
|
+
const certificate = value?.certificateSha256?.replaceAll(":", "").toLowerCase();
|
|
148
|
+
if (typeof value?.id !== "string" || value.id.length > 255 || !/^[A-Za-z][A-Za-z0-9_-]*(\.[A-Za-z0-9_-]+)+$/u.test(value.id) || certificate !== void 0 && !/^[0-9a-f]{64}$/u.test(certificate))
|
|
149
|
+
throw new Error("Invalid Tileflow app identity");
|
|
150
|
+
return { id: value.id, certificateSha256: certificate };
|
|
151
|
+
}
|
|
152
|
+
function normalizeApiKey(value) {
|
|
153
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 4096 || value !== value.trim() || /\p{Cc}/u.test(value))
|
|
154
|
+
throw new Error("Invalid Tileflow API key");
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
function normalizeApiUrl(value) {
|
|
158
|
+
let url;
|
|
159
|
+
try {
|
|
160
|
+
url = new URL(value);
|
|
161
|
+
} catch {
|
|
162
|
+
throw new Error("Invalid Tileflow API URL");
|
|
163
|
+
}
|
|
164
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
165
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "")
|
|
166
|
+
throw new Error("Invalid Tileflow API URL");
|
|
167
|
+
return url.href.replace(/\/$/u, "");
|
|
168
|
+
}
|
|
169
|
+
async function readRoutingError(response, signal) {
|
|
170
|
+
const value = response.headers.get("Retry-After");
|
|
171
|
+
const retryAfterSeconds = value && /^\d{1,8}$/u.test(value) ? Number(value) : null;
|
|
172
|
+
let body;
|
|
173
|
+
try {
|
|
174
|
+
body = await readBoundedJson(response, 8 * 1024, signal);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
177
|
+
return new RoutingError("Tileflow Routing request failed", {
|
|
178
|
+
status: response.status,
|
|
179
|
+
retryAfterSeconds
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const parsed = routingErrorResponseSchema.safeParse(body);
|
|
183
|
+
const expected = parsed.success && (routingErrorStatus[parsed.data.code] === response.status || parsed.data.code === "ROUTING_ACCESS_DENIED" && response.status === 401);
|
|
184
|
+
if (!parsed.success || !expected)
|
|
185
|
+
return new RoutingError("Tileflow Routing request failed", {
|
|
186
|
+
status: response.status,
|
|
187
|
+
retryAfterSeconds
|
|
188
|
+
});
|
|
189
|
+
return new RoutingError(parsed.data.error, {
|
|
190
|
+
code: parsed.data.code,
|
|
191
|
+
status: response.status,
|
|
192
|
+
retryAfterSeconds,
|
|
193
|
+
...parsed.data.requestId ? { requestId: parsed.data.requestId } : {}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async function readBoundedJson(response, maximumBytes, signal) {
|
|
197
|
+
const declared = response.headers.get("Content-Length");
|
|
198
|
+
if (declared !== null && (!/^\d+$/u.test(declared) || Number(declared) > maximumBytes)) {
|
|
199
|
+
void response.body?.cancel().catch(() => void 0);
|
|
200
|
+
throw new Error("Tileflow Routing response is too large");
|
|
201
|
+
}
|
|
202
|
+
if (!response.body) throw new Error("Tileflow Routing response is empty");
|
|
203
|
+
const reader = response.body.getReader();
|
|
204
|
+
const chunks = [];
|
|
205
|
+
let byteLength = 0;
|
|
206
|
+
try {
|
|
207
|
+
while (true) {
|
|
208
|
+
const { done, value } = await readResponseChunk(reader, signal);
|
|
209
|
+
if (done) break;
|
|
210
|
+
byteLength += value.byteLength;
|
|
211
|
+
if (byteLength > maximumBytes) {
|
|
212
|
+
void reader.cancel().catch(() => void 0);
|
|
213
|
+
throw new Error("Tileflow Routing response is too large");
|
|
214
|
+
}
|
|
215
|
+
chunks.push(value);
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
reader.releaseLock();
|
|
219
|
+
}
|
|
220
|
+
const bytes = new Uint8Array(byteLength);
|
|
221
|
+
let offset = 0;
|
|
222
|
+
for (const chunk of chunks) {
|
|
223
|
+
bytes.set(chunk, offset);
|
|
224
|
+
offset += chunk.byteLength;
|
|
225
|
+
}
|
|
226
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
227
|
+
}
|
|
228
|
+
async function readResponseChunk(reader, signal) {
|
|
229
|
+
if (!signal) return reader.read();
|
|
230
|
+
let aborted = false;
|
|
231
|
+
const abort = () => {
|
|
232
|
+
aborted = true;
|
|
233
|
+
void reader.cancel(signal.reason).catch(() => void 0);
|
|
234
|
+
};
|
|
235
|
+
if (signal.aborted) abort();
|
|
236
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
237
|
+
try {
|
|
238
|
+
const chunk = await reader.read();
|
|
239
|
+
if (aborted) throw signal.reason ?? new Error("Tileflow Routing request was aborted");
|
|
240
|
+
return chunk;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (aborted) throw signal.reason ?? error;
|
|
243
|
+
throw error;
|
|
244
|
+
} finally {
|
|
245
|
+
signal.removeEventListener("abort", abort);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export {
|
|
250
|
+
RoutingError,
|
|
251
|
+
route,
|
|
252
|
+
isochrone,
|
|
253
|
+
matrix,
|
|
254
|
+
match,
|
|
255
|
+
nearest
|
|
256
|
+
};
|