@tileflow/routing 0.1.0-alpha.1 → 0.1.0-alpha.2

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 CHANGED
@@ -47,12 +47,14 @@ for (const step of fastest?.legs[0]?.steps ?? []) console.log(step.instruction);
47
47
 
48
48
  **Request:**
49
49
 
50
- - `locations`: two to five `[longitude, latitude]` positions: an origin, optional stops and a
50
+ - `locations`: two to 25 `[longitude, latitude]` positions: an origin, optional stops and a
51
51
  destination. Each stretch between consecutive positions is a leg.
52
52
  - `mode`: `driving`, `walking`, `cycling` or `truck`.
53
53
  - `alternatives` (optional, 0–2): only between two positions. Fewer come back when no distinct
54
54
  alternative exists.
55
55
  - `language` (optional): writes the instructions in one of `routingLanguages`.
56
+ - `optimize` (optional): see [Visit stops in the fastest order](#visit-stops-in-the-fastest-order).
57
+ - `departAt` (optional): see [Choose a departure time](#choose-a-departure-time).
56
58
  - `vehicle`, `avoid` and `traffic` (optional): see
57
59
  [Route a truck around tolls](#route-a-truck-around-tolls).
58
60
 
@@ -67,6 +69,41 @@ for (const step of fastest?.legs[0]?.steps ?? []) console.log(step.instruction);
67
69
 
68
70
  Durations are estimates without traffic; see [Travel times and traffic](#travel-times-and-traffic).
69
71
 
72
+ ## Visit stops in the fastest order
73
+
74
+ Set `optimize` to visit the stops between the first and the last location in the fastest order the
75
+ service finds. The first and the last location stay in place; repeat the first to return to it.
76
+
77
+ ```ts
78
+ import {route} from '@tileflow/routing';
79
+
80
+ const stops: [number, number][] = [
81
+ [-80.1918, 25.7617],
82
+ [-80.1312, 25.7795],
83
+ [-80.2569, 25.7215],
84
+ [-80.2195, 25.7655],
85
+ [-80.1918, 25.7617],
86
+ ];
87
+
88
+ const answer = await route({locations: stops, mode: 'driving', optimize: true}, {apiKey});
89
+
90
+ // The requested index of each location, in the order the route visits them.
91
+ for (const index of answer.locationOrder ?? []) console.log(stops[index]);
92
+ ```
93
+
94
+ The legs of the route follow `locationOrder`. Every two locations must lie within the reach of a
95
+ [matrix](#calculate-a-travel-time-matrix) for the mode: 200 km driving, 10 km walking or
96
+ cycling. An optimized route counts 10 API units, whatever its locations; a route in the given
97
+ order counts 5.
98
+
99
+ ## Choose a departure time
100
+
101
+ `departAt` says when the route leaves the first location, as `YYYY-MM-DDTHH:mm` in that location's
102
+ local time. The route then respects roads closed at that time where OpenStreetMap records it, and
103
+ each leg states `arriveAt`: its local time at the end of the leg with the UTC offset, such as
104
+ `2027-03-01T16:47-06:00`. It does not add traffic: durations stay
105
+ [without traffic](#travel-times-and-traffic), whatever the time.
106
+
70
107
  ## Route a truck around tolls
71
108
 
72
109
  <!-- docs:check -->
@@ -12,7 +12,7 @@ import {
12
12
  routingNearestResponseSchema,
13
13
  routingRouteRequestSchema,
14
14
  routingRouteResponseSchema
15
- } from "./chunk-ECBK2QPL.js";
15
+ } from "./chunk-CDZKLI32.js";
16
16
 
17
17
  // src/client.ts
18
18
  import { z } from "zod";
@@ -30,16 +30,21 @@ var RoutingError = class extends Error {
30
30
  this.retryAfterSeconds = input.retryAfterSeconds ?? null;
31
31
  }
32
32
  };
33
+ function answersOrder(order, locations, optimize) {
34
+ if (!optimize) return order === void 0;
35
+ return order?.length === locations && new Set(order).size === locations && order[0] === 0 && order.at(-1) === locations - 1 && order.every((index) => index < locations);
36
+ }
33
37
  async function route(request, options) {
34
38
  const parsed = routingRouteRequestSchema.safeParse(request);
35
39
  if (!parsed.success) throw new Error("Invalid Tileflow route request");
36
- const legs = parsed.data.locations.length - 1;
40
+ const { locations, optimize } = parsed.data;
41
+ const legs = locations.length - 1;
37
42
  return requestRouting(
38
43
  "/v1/routing/route",
39
44
  parsed.data,
40
45
  options,
41
46
  routingRouteResponseSchema,
42
- (response) => response.routes.length <= (parsed.data.alternatives ?? 0) + 1 && response.routes.every((candidate) => candidate.legs.length === legs)
47
+ (response) => response.routes.length <= (parsed.data.alternatives ?? 0) + 1 && response.routes.every((candidate) => candidate.legs.length === legs) && answersOrder(response.locationOrder, locations.length, optimize)
43
48
  );
44
49
  }
45
50
  async function isochrone(request, options) {
@@ -42,7 +42,7 @@ var routingLimits = Object.freeze({
42
42
  maximumRequestBytes: 8 * 1024,
43
43
  maximumResponseBytes: 4 * 1024 * 1024,
44
44
  minimumLocations: 2,
45
- maximumLocations: 5,
45
+ maximumLocations: 25,
46
46
  maximumAlternatives: 2,
47
47
  /** Sum of straight-line distances between consecutive locations, per mode. */
48
48
  maximumDirectDistanceMeters: Object.freeze({
@@ -114,11 +114,22 @@ function refineTravelOptions(request, context) {
114
114
  context.addIssue({ code: "custom", path: ["avoid"], message: "Avoid each kind once." });
115
115
  }
116
116
  }
117
+ var localDateTime = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/u, "Use YYYY-MM-DDTHH:mm, local to the first location.").refine((value) => {
118
+ const date = /* @__PURE__ */ new Date(`${value}:00Z`);
119
+ return !Number.isNaN(date.getTime()) && date.toISOString().startsWith(value);
120
+ }, "This date does not exist.");
117
121
  var routingRouteRequestSchema = z.object({
118
122
  locations: z.array(routingPositionSchema).min(routingLimits.minimumLocations).max(routingLimits.maximumLocations),
119
123
  ...travelOptions,
120
124
  alternatives: z.number().int().min(0).max(routingLimits.maximumAlternatives).optional(),
121
- language: z.enum(routingLanguages).optional()
125
+ language: z.enum(routingLanguages).optional(),
126
+ /**
127
+ * Visits the stops between the first and the last location in the fastest order the engine
128
+ * finds. The first and the last location stay in place; repeat the first to return to it.
129
+ */
130
+ optimize: z.boolean().optional(),
131
+ /** When the route leaves the first location, in that location's local time. */
132
+ departAt: localDateTime.optional()
122
133
  }).strict().superRefine((request, context) => {
123
134
  refineTravelOptions(request, context);
124
135
  if ((request.alternatives ?? 0) > 0 && request.locations.length !== 2) {
@@ -128,6 +139,20 @@ var routingRouteRequestSchema = z.object({
128
139
  message: "Alternatives are available only between two locations."
129
140
  });
130
141
  }
142
+ if (request.optimize) {
143
+ const limit = routingLimits.maximumMatrixDistanceMeters[request.mode];
144
+ const tooFar = request.locations.some(
145
+ (a, index) => request.locations.slice(index + 1).some((b) => routingDirectDistance(a, b) > limit)
146
+ );
147
+ if (tooFar) {
148
+ context.addIssue({
149
+ code: "custom",
150
+ path: ["locations"],
151
+ message: `Two locations are too far apart to optimize for ${request.mode}.`
152
+ });
153
+ }
154
+ return;
155
+ }
131
156
  let distance = 0;
132
157
  for (let index = 1; index < request.locations.length; index += 1) {
133
158
  distance += routingDirectDistance(request.locations[index - 1], request.locations[index]);
@@ -279,6 +304,8 @@ var routingLineStringSchema = z.object({
279
304
  var routingLegSchema = z.object({
280
305
  distance: meters,
281
306
  duration: seconds,
307
+ /** With `departAt`: when the leg ends, in local time at its end, with that UTC offset. */
308
+ arriveAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}[+-]\d{2}:\d{2}$/u).optional(),
282
309
  steps: z.array(routingStepSchema).min(1).max(routingLimits.maximumSteps)
283
310
  }).strict();
284
311
  var routingRouteSchema = z.object({
@@ -351,6 +378,10 @@ var answer = {
351
378
  var routingRouteResponseSchema = z.object({
352
379
  ...answer,
353
380
  routes: z.array(routingRouteSchema).min(1).max(routingLimits.maximumAlternatives + 1),
381
+ /** With `optimize`: the requested index of each location, in the order the route visits them. */
382
+ locationOrder: z.array(
383
+ z.number().int().min(0).max(routingLimits.maximumLocations - 1)
384
+ ).min(routingLimits.minimumLocations).max(routingLimits.maximumLocations).optional(),
354
385
  usage: routingUsageSchema
355
386
  }).strict();
356
387
  var routingIsochroneResponseSchema = z.object({ ...answer, isochrones: routingIsochroneCollectionSchema, usage: routingUsageSchema }).strict();
package/dist/client.d.ts CHANGED
@@ -30,9 +30,10 @@ declare class RoutingError extends Error {
30
30
  });
31
31
  }
32
32
  /**
33
- * The fastest route through two to five positions, with up to two alternatives between two
33
+ * The fastest route through two to 25 positions, with up to two alternatives between two
34
34
  * positions. Each route has its line, distance, duration, turn-by-turn steps and whether it uses
35
- * tolls, highways or ferries.
35
+ * tolls, highways or ferries. With `optimize`, the stops between the first and the last position
36
+ * are visited in the fastest order found, which `locationOrder` states.
36
37
  */
37
38
  declare function route(request: RoutingRouteRequest, options: RoutingOptions): Promise<RoutingRouteResponse>;
38
39
  /** The areas reachable from a position in one to four times, one polygon per time. */
package/dist/client.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  matrix,
6
6
  nearest,
7
7
  route
8
- } from "./chunk-MCATD4FU.js";
9
- import "./chunk-ECBK2QPL.js";
8
+ } from "./chunk-3P5UQQH3.js";
9
+ import "./chunk-CDZKLI32.js";
10
10
  export {
11
11
  RoutingError,
12
12
  isochrone,
@@ -22,7 +22,7 @@ declare const routingLimits: Readonly<{
22
22
  maximumRequestBytes: number;
23
23
  maximumResponseBytes: number;
24
24
  minimumLocations: 2;
25
- maximumLocations: 5;
25
+ maximumLocations: 25;
26
26
  maximumAlternatives: 2;
27
27
  /** Sum of straight-line distances between consecutive locations, per mode. */
28
28
  maximumDirectDistanceMeters: Readonly<{
@@ -132,6 +132,8 @@ declare const routingRouteRequestSchema: z.ZodObject<{
132
132
  "uk-UA": "uk-UA";
133
133
  "vi-VN": "vi-VN";
134
134
  }>>;
135
+ optimize: z.ZodOptional<z.ZodBoolean>;
136
+ departAt: z.ZodOptional<z.ZodString>;
135
137
  }, z.core.$strict>;
136
138
  type RoutingRouteRequest = z.input<typeof routingRouteRequestSchema>;
137
139
  type NormalizedRoutingRouteRequest = z.output<typeof routingRouteRequestSchema>;
@@ -282,6 +284,7 @@ declare const routingLineStringSchema: z.ZodObject<{
282
284
  declare const routingLegSchema: z.ZodObject<{
283
285
  distance: z.ZodNumber;
284
286
  duration: z.ZodNumber;
287
+ arriveAt: z.ZodOptional<z.ZodString>;
285
288
  steps: z.ZodArray<z.ZodObject<{
286
289
  maneuver: z.ZodEnum<{
287
290
  arrive: "arrive";
@@ -338,6 +341,7 @@ declare const routingRouteSchema: z.ZodObject<{
338
341
  legs: z.ZodArray<z.ZodObject<{
339
342
  distance: z.ZodNumber;
340
343
  duration: z.ZodNumber;
344
+ arriveAt: z.ZodOptional<z.ZodString>;
341
345
  steps: z.ZodArray<z.ZodObject<{
342
346
  maneuver: z.ZodEnum<{
343
347
  arrive: "arrive";
@@ -480,6 +484,7 @@ declare const routingRouteResponseSchema: z.ZodObject<{
480
484
  legs: z.ZodArray<z.ZodObject<{
481
485
  distance: z.ZodNumber;
482
486
  duration: z.ZodNumber;
487
+ arriveAt: z.ZodOptional<z.ZodString>;
483
488
  steps: z.ZodArray<z.ZodObject<{
484
489
  maneuver: z.ZodEnum<{
485
490
  arrive: "arrive";
@@ -524,6 +529,7 @@ declare const routingRouteResponseSchema: z.ZodObject<{
524
529
  }, z.core.$strict>>;
525
530
  }, z.core.$strict>>;
526
531
  }, z.core.$strict>>;
532
+ locationOrder: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
527
533
  usage: z.ZodObject<{
528
534
  units: z.ZodNumber;
529
535
  }, z.core.$strict>;
package/dist/contract.js CHANGED
@@ -33,7 +33,7 @@ import {
33
33
  routingTrafficKinds,
34
34
  routingUsageSchema,
35
35
  routingVehicleSchema
36
- } from "./chunk-ECBK2QPL.js";
36
+ } from "./chunk-CDZKLI32.js";
37
37
  export {
38
38
  routingAttribution,
39
39
  routingAvoidances,
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  matrix,
6
6
  nearest,
7
7
  route
8
- } from "./chunk-MCATD4FU.js";
8
+ } from "./chunk-3P5UQQH3.js";
9
9
  import {
10
10
  routingAttribution,
11
11
  routingAvoidances,
@@ -41,7 +41,7 @@ import {
41
41
  routingTrafficKinds,
42
42
  routingUsageSchema,
43
43
  routingVehicleSchema
44
- } from "./chunk-ECBK2QPL.js";
44
+ } from "./chunk-CDZKLI32.js";
45
45
  export {
46
46
  RoutingError,
47
47
  isochrone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/routing",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Routes, isochrones, matrices, map matching and nearest-road client and wire contracts for Tileflow.",
6
6
  "type": "module",