@any-routing/here-data-provider 0.1.0 → 1.0.0-rc.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 +5 -1
- package/eslint.config.mjs +24 -0
- package/package.json +13 -15
- package/project.json +9 -0
- package/src/{index.d.ts → index.ts} +1 -0
- package/src/lib/here-data-provider.spec.ts +7 -0
- package/src/lib/here-data-provider.ts +148 -0
- package/src/lib/here-provider.types.ts +179 -0
- package/src/lib/here.executor.ts +425 -0
- package/src/lib/{here.worker.js → here.worker.ts} +2 -1
- package/src/lib/utils/select-route-strategy.ts +40 -0
- package/tsconfig.json +23 -0
- package/tsconfig.lib.json +28 -0
- package/tsconfig.spec.json +28 -0
- package/vite.config.mts +51 -0
- package/src/index.js +0 -2
- package/src/index.js.map +0 -1
- package/src/lib/here-data-provider.d.ts +0 -66
- package/src/lib/here-data-provider.js +0 -89
- package/src/lib/here-data-provider.js.map +0 -1
- package/src/lib/here.executor.d.ts +0 -13
- package/src/lib/here.executor.js +0 -205
- package/src/lib/here.executor.js.map +0 -1
- package/src/lib/here.worker.d.ts +0 -1
- package/src/lib/here.worker.js.map +0 -1
- package/src/lib/utils/select-route-strategy.d.ts +0 -3
- package/src/lib/utils/select-route-strategy.js +0 -28
- package/src/lib/utils/select-route-strategy.js.map +0 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { decode } from '@here/flexpolyline';
|
|
2
|
+
import simplify from 'simplify-js';
|
|
3
|
+
import bbox from '@turf/bbox';
|
|
4
|
+
import { featureCollection, lineString } from '@turf/helpers';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
BaseOptions,
|
|
8
|
+
GeoJsonSplitStrategy,
|
|
9
|
+
HereApiResponse,
|
|
10
|
+
HereRawDynamicSpeedInfo,
|
|
11
|
+
HereRawRoute,
|
|
12
|
+
HereRawSection,
|
|
13
|
+
HereRawSpan,
|
|
14
|
+
HereRawToll,
|
|
15
|
+
HereRouteSummary,
|
|
16
|
+
HereRoutingData,
|
|
17
|
+
RouteExcludeNoticeDefinitions,
|
|
18
|
+
TurnByTurnAction,
|
|
19
|
+
} from './here-provider.types';
|
|
20
|
+
|
|
21
|
+
import { selectRouteByStrategy } from './utils/select-route-strategy';
|
|
22
|
+
import { Requester, UnauthorizedError, type WaypointPosition } from '@any-routing/core';
|
|
23
|
+
import type { BBox, Feature, FeatureCollection, LineString } from 'geojson';
|
|
24
|
+
|
|
25
|
+
export type ExecutorRequestOptions = { url: string } & BaseOptions;
|
|
26
|
+
|
|
27
|
+
/** Properties attached to each rendered segment of a route's shape. */
|
|
28
|
+
type ShapeFeatureProperties = {
|
|
29
|
+
waypoint: number;
|
|
30
|
+
routeId: number;
|
|
31
|
+
jamFactor?: number;
|
|
32
|
+
isMarginalChunk?: boolean;
|
|
33
|
+
selected?: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type SectionAccumulator = {
|
|
37
|
+
distance: number;
|
|
38
|
+
cost: number;
|
|
39
|
+
waypoints: WaypointPosition[];
|
|
40
|
+
path: number[][];
|
|
41
|
+
durationTime: number;
|
|
42
|
+
turnByTurnActions: TurnByTurnAction[];
|
|
43
|
+
shape: FeatureCollection<LineString, ShapeFeatureProperties>;
|
|
44
|
+
shapePath: number[][];
|
|
45
|
+
waypointIndex: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class HereExecutor {
|
|
49
|
+
private readonly requester = new Requester();
|
|
50
|
+
|
|
51
|
+
async request(opts: ExecutorRequestOptions): Promise<HereRoutingData> {
|
|
52
|
+
try {
|
|
53
|
+
const data = (await this.requester.request(opts.url, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
Accept: 'application/json',
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({}),
|
|
60
|
+
...opts.requestParams,
|
|
61
|
+
})) as HereApiResponse;
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
!data.routes?.length ||
|
|
65
|
+
(opts.routeExcludeNotice && violatedResponseNotices(data, opts.routeExcludeNotice))
|
|
66
|
+
) {
|
|
67
|
+
throw new Error('No routes found');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const routeSummaries = data.routes.map((route, routeId) =>
|
|
71
|
+
summarizeRoute(route, routeId, opts),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const selectedRouteId = selectRouteByStrategy(routeSummaries, opts.selectRouteStrategy);
|
|
75
|
+
|
|
76
|
+
const features = routeSummaries.flatMap((routeSummary, fid) =>
|
|
77
|
+
routeSummary.shape.features.map((feature) => {
|
|
78
|
+
return {
|
|
79
|
+
id: fid,
|
|
80
|
+
...feature,
|
|
81
|
+
properties: {
|
|
82
|
+
...feature.properties,
|
|
83
|
+
id: fid,
|
|
84
|
+
routeId: routeSummary.id,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const routesShapeGeojson = featureCollection(features);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
routesShapeBounds: bbox(routesShapeGeojson) as BBox,
|
|
94
|
+
rawResponse: data,
|
|
95
|
+
routes: routeSummaries,
|
|
96
|
+
selectedRouteId,
|
|
97
|
+
routesShapeGeojson,
|
|
98
|
+
version: performance.now(),
|
|
99
|
+
latest: !this.requester.hasPendingRequests,
|
|
100
|
+
requestOptions: opts,
|
|
101
|
+
};
|
|
102
|
+
} catch (error: unknown) {
|
|
103
|
+
if (error instanceof Response) {
|
|
104
|
+
const body = await error.json();
|
|
105
|
+
|
|
106
|
+
if (error.status === 401) {
|
|
107
|
+
throw new UnauthorizedError(body);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
hasPendingRequests() {
|
|
116
|
+
return this.requester.hasPendingRequests;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
abortAllRequests() {
|
|
120
|
+
this.requester.abortAllRequests();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Which split strategies apply for the current request mode, if any. */
|
|
125
|
+
function getSplitStrategies(options: ExecutorRequestOptions): GeoJsonSplitStrategy[] | undefined {
|
|
126
|
+
return options.geoJSONShapeSplitStrategies;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function shouldSplitShape(options: ExecutorRequestOptions): boolean {
|
|
130
|
+
const strategies = getSplitStrategies(options);
|
|
131
|
+
return !!strategies && strategies.length > 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Sum of all toll fares for a section.
|
|
136
|
+
*
|
|
137
|
+
* Bug fix (typing): `fare.convertedPrice || fare.price` can legitimately be
|
|
138
|
+
* `undefined` if HERE returns a fare with neither field populated; reading
|
|
139
|
+
* `.value` off that used to be an implicit `any` that would only blow up at
|
|
140
|
+
* runtime. With real types this is now enforced at compile time via `?.` +
|
|
141
|
+
* a `0` fallback instead of throwing on an unexpected payload.
|
|
142
|
+
*
|
|
143
|
+
* Bug fix (logic, unchanged from prior pass): the previous implementation
|
|
144
|
+
* returned the result of `Array#forEach` (always `undefined`) from the
|
|
145
|
+
* outer `reduce` callback instead of returning the running total, so `cost`
|
|
146
|
+
* collapsed to `undefined`/`NaN` for any section that had tolls.
|
|
147
|
+
*/
|
|
148
|
+
function computeSectionCost(section: HereRawSection): number {
|
|
149
|
+
return (section.tolls || []).reduce((costAcc: number, toll: HereRawToll) => {
|
|
150
|
+
const tollCost = (toll.fares || []).reduce(
|
|
151
|
+
(fareAcc: number, fare) => fareAcc + ((fare.convertedPrice ?? fare.price)?.value ?? 0),
|
|
152
|
+
0,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return costAcc + tollCost;
|
|
156
|
+
}, 0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Waypoints introduced by a section: the departure location (if it was an
|
|
161
|
+
* explicit, geocoded location) and, only for the section that ends the
|
|
162
|
+
* route, the arrival location.
|
|
163
|
+
*
|
|
164
|
+
* Simplified from the original condition
|
|
165
|
+
* `(sections.length >= 2 && index === last) || sections.length === 1`,
|
|
166
|
+
* which is logically identical to `index === last` (when there is only one
|
|
167
|
+
* section, index 0 already equals `sections.length - 1`).
|
|
168
|
+
*/
|
|
169
|
+
function extractSectionWaypoints(
|
|
170
|
+
section: HereRawSection,
|
|
171
|
+
isLastSection: boolean,
|
|
172
|
+
): WaypointPosition[] {
|
|
173
|
+
const waypoints: WaypointPosition[] = [];
|
|
174
|
+
|
|
175
|
+
// HERE's response typings don't expose `location` on a place even though
|
|
176
|
+
// the API always includes it for geocoded (`originalLocation`) points.
|
|
177
|
+
// `location` is already `{ lat, lng }` (see `HereRawPlace`), matching
|
|
178
|
+
// `WaypointPosition` exactly — no cast needed. The previous `as
|
|
179
|
+
// LngLatPosition` was papering over a shape mismatch: `LngLatPosition` is
|
|
180
|
+
// the tuple format `path`/`shapePath` use for GeoJSON coordinates, not
|
|
181
|
+
// the `{ lat, lng }` object format `RouteSummary.waypoints` expects.
|
|
182
|
+
if (section.departure.place.originalLocation) {
|
|
183
|
+
waypoints.push(section.departure.place.location);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (section.arrival.place.originalLocation && isLastSection) {
|
|
187
|
+
waypoints.push(section.arrival.place.location);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return waypoints;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Approximates HERE's `jamFactor` scale ([0, 10], 0 = free flow, 10 =
|
|
195
|
+
* stationary traffic — see HERE Traffic API's `jamFactor` docs) from a
|
|
196
|
+
* span's `dynamicSpeedInfo`.
|
|
197
|
+
*
|
|
198
|
+
* HERE doesn't publish the exact algorithm behind
|
|
199
|
+
* `DynamicSpeedInfo.calculateJamFactor()` in the SDK, so this is
|
|
200
|
+
* necessarily an approximation, not a reproduction of HERE's formula.
|
|
201
|
+
*
|
|
202
|
+
* Bug fix: the previous version returned `trafficSpeed / baseSpeed`
|
|
203
|
+
* directly, which (a) sits on a [0, 1]-ish scale rather than HERE's
|
|
204
|
+
* documented [0, 10] `jamFactor` range, so any downstream code branching
|
|
205
|
+
* on "> 5 means bad traffic" style thresholds was silently wrong, and (b)
|
|
206
|
+
* divides by zero (→ `Infinity`/`NaN`) whenever `baseSpeed` is `0`, which
|
|
207
|
+
* happens on spans where HERE has no free-flow baseline.
|
|
208
|
+
*/
|
|
209
|
+
function estimateJamFactor(info: HereRawDynamicSpeedInfo): number | undefined {
|
|
210
|
+
if (!info.baseSpeed || info.baseSpeed <= 0) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const speedRatio = Math.min(Math.max(info.trafficSpeed / info.baseSpeed, 0), 1);
|
|
215
|
+
|
|
216
|
+
return +(10 * (1 - speedRatio)).toFixed(2);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Builds the shape segments for a section, optionally split by the
|
|
221
|
+
* configured strategies (currently only `jamFactor`), merging consecutive
|
|
222
|
+
* spans that share the same jam factor into a single feature.
|
|
223
|
+
*/
|
|
224
|
+
function buildSectionShapes(
|
|
225
|
+
section: HereRawSection,
|
|
226
|
+
path: number[][],
|
|
227
|
+
shapePath: number[][],
|
|
228
|
+
routeId: number,
|
|
229
|
+
waypointIndex: number,
|
|
230
|
+
options: ExecutorRequestOptions,
|
|
231
|
+
): Feature<LineString, ShapeFeatureProperties>[] {
|
|
232
|
+
if (!shouldSplitShape(options)) {
|
|
233
|
+
return [
|
|
234
|
+
lineString(shapePath, { waypoint: waypointIndex, routeId }) as Feature<
|
|
235
|
+
LineString,
|
|
236
|
+
ShapeFeatureProperties
|
|
237
|
+
>,
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const spans: HereRawSpan[] = section.spans ?? [];
|
|
242
|
+
const splitByJamFactor = getSplitStrategies(options)?.includes('jamFactor');
|
|
243
|
+
|
|
244
|
+
return spans.reduce(
|
|
245
|
+
(
|
|
246
|
+
shapesAcc: Feature<LineString, ShapeFeatureProperties>[],
|
|
247
|
+
span: HereRawSpan,
|
|
248
|
+
index: number,
|
|
249
|
+
) => {
|
|
250
|
+
const closeOffset = index < spans.length - 1 ? spans[index + 1].offset : path.length - 1;
|
|
251
|
+
const spanPath = path.slice(span.offset, closeOffset + 1);
|
|
252
|
+
const spanShapePath = options.shapePolylinePrecision
|
|
253
|
+
? simplifyPath(spanPath, options.shapePolylinePrecision)
|
|
254
|
+
: spanPath;
|
|
255
|
+
const isMarginalChunk = index === 0 || index === spans.length - 1;
|
|
256
|
+
const lastShape = shapesAcc[shapesAcc.length - 1];
|
|
257
|
+
|
|
258
|
+
const jamFactor = span.dynamicSpeedInfo
|
|
259
|
+
? estimateJamFactor(span.dynamicSpeedInfo)
|
|
260
|
+
: undefined;
|
|
261
|
+
|
|
262
|
+
if (splitByJamFactor && jamFactor !== undefined) {
|
|
263
|
+
const prevJamFactor = lastShape?.properties?.jamFactor;
|
|
264
|
+
|
|
265
|
+
if (lastShape && jamFactor === prevJamFactor) {
|
|
266
|
+
lastShape.geometry.coordinates.push(...spanShapePath.slice(1));
|
|
267
|
+
lastShape.properties.isMarginalChunk = isMarginalChunk;
|
|
268
|
+
} else {
|
|
269
|
+
shapesAcc.push(
|
|
270
|
+
lineString(spanShapePath, {
|
|
271
|
+
waypoint: waypointIndex,
|
|
272
|
+
routeId,
|
|
273
|
+
jamFactor,
|
|
274
|
+
isMarginalChunk,
|
|
275
|
+
}) as Feature<LineString, ShapeFeatureProperties>,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
} else if (lastShape) {
|
|
279
|
+
lastShape.geometry.coordinates.push(...spanShapePath.slice(1));
|
|
280
|
+
} else {
|
|
281
|
+
shapesAcc.push(
|
|
282
|
+
lineString(spanShapePath, {
|
|
283
|
+
waypoint: waypointIndex,
|
|
284
|
+
routeId,
|
|
285
|
+
isMarginalChunk,
|
|
286
|
+
}) as Feature<LineString, ShapeFeatureProperties>,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return shapesAcc;
|
|
291
|
+
},
|
|
292
|
+
[],
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function createInitialSectionAccumulator(): SectionAccumulator {
|
|
297
|
+
return {
|
|
298
|
+
distance: 0,
|
|
299
|
+
cost: 0,
|
|
300
|
+
waypoints: [],
|
|
301
|
+
path: [],
|
|
302
|
+
durationTime: 0,
|
|
303
|
+
turnByTurnActions: [],
|
|
304
|
+
shape: featureCollection([]),
|
|
305
|
+
shapePath: [],
|
|
306
|
+
waypointIndex: 0,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const summarizeRoute = (
|
|
311
|
+
route: HereRawRoute,
|
|
312
|
+
routeId: number,
|
|
313
|
+
options: ExecutorRequestOptions,
|
|
314
|
+
): HereRouteSummary => {
|
|
315
|
+
const { distance, cost, durationTime, waypoints, path, shape, shapePath, turnByTurnActions } =
|
|
316
|
+
route.sections.reduce(
|
|
317
|
+
(acc: SectionAccumulator, section: HereRawSection, index: number): SectionAccumulator => {
|
|
318
|
+
const sectionCost = computeSectionCost(section);
|
|
319
|
+
const sectionDuration = section.summary?.duration ?? 0;
|
|
320
|
+
const sectionDistance = section.summary?.length ?? 0;
|
|
321
|
+
const isLastSection = index === route.sections.length - 1;
|
|
322
|
+
const sectionWaypoints = extractSectionWaypoints(section, isLastSection);
|
|
323
|
+
|
|
324
|
+
const sectionPath = decodePolyline(section.polyline);
|
|
325
|
+
const sectionShapePath = options.shapePolylinePrecision
|
|
326
|
+
? simplifyPath(sectionPath, options.shapePolylinePrecision)
|
|
327
|
+
: sectionPath;
|
|
328
|
+
|
|
329
|
+
const sectionTurnByTurnActions = (section.turnByTurnActions || []).map(
|
|
330
|
+
(action): TurnByTurnAction => {
|
|
331
|
+
const [lat, lng] = sectionPath[action.offset];
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
...action,
|
|
335
|
+
offset: acc.path.length + action.offset,
|
|
336
|
+
position: { lat, lng },
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const sectionShapes = buildSectionShapes(
|
|
342
|
+
section,
|
|
343
|
+
sectionPath,
|
|
344
|
+
sectionShapePath,
|
|
345
|
+
routeId,
|
|
346
|
+
acc.waypointIndex,
|
|
347
|
+
options,
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const nextSection = route.sections[index + 1];
|
|
351
|
+
const staysOnSameLeg = section.type === 'vehicle' && nextSection?.type === 'vehicle';
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
// Bug fix: previously `acc.distance + section.summary?.length ?? 0`,
|
|
355
|
+
// which (due to operator precedence) evaluated as
|
|
356
|
+
// `(acc.distance + section.summary?.length) ?? 0` and reset the
|
|
357
|
+
// whole accumulated distance to 0 whenever a section had no summary.
|
|
358
|
+
distance: acc.distance + sectionDistance,
|
|
359
|
+
durationTime: acc.durationTime + sectionDuration,
|
|
360
|
+
cost: acc.cost + sectionCost,
|
|
361
|
+
waypoints: [...acc.waypoints, ...sectionWaypoints],
|
|
362
|
+
path: [...acc.path, ...sectionPath],
|
|
363
|
+
turnByTurnActions: [...acc.turnByTurnActions, ...sectionTurnByTurnActions],
|
|
364
|
+
shape: featureCollection([...(acc.shape?.features || []), ...sectionShapes]),
|
|
365
|
+
shapePath: [...acc.shapePath, ...sectionShapePath],
|
|
366
|
+
waypointIndex: staysOnSameLeg ? acc.waypointIndex + 1 : acc.waypointIndex,
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
createInitialSectionAccumulator(),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
durationTime,
|
|
374
|
+
distance,
|
|
375
|
+
cost,
|
|
376
|
+
path,
|
|
377
|
+
arriveTime: new Date(route.sections[route.sections.length - 1].arrival.time ?? 0),
|
|
378
|
+
departureTime: new Date(route.sections[0].departure.time ?? 0),
|
|
379
|
+
id: routeId,
|
|
380
|
+
waypoints,
|
|
381
|
+
label: route.routeLabels ? route.routeLabels.map((l) => l.name.value).join(', ') : undefined,
|
|
382
|
+
shape,
|
|
383
|
+
turnByTurnActions,
|
|
384
|
+
shapePath,
|
|
385
|
+
rawRoute: route,
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const violatedResponseNotices = (
|
|
390
|
+
data: HereApiResponse,
|
|
391
|
+
routeExcludeNotice: RouteExcludeNoticeDefinitions,
|
|
392
|
+
): boolean => {
|
|
393
|
+
if (violatedNotices(data.notices || [], routeExcludeNotice)) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return (data.routes || []).some((route) =>
|
|
398
|
+
(route.sections || []).some((section) =>
|
|
399
|
+
violatedNotices(section.notices || [], routeExcludeNotice),
|
|
400
|
+
),
|
|
401
|
+
);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const violatedNotices = (
|
|
405
|
+
notices: { code: string; severity: 'critical' | 'info' }[],
|
|
406
|
+
routeExcludeNotice: RouteExcludeNoticeDefinitions,
|
|
407
|
+
): boolean => {
|
|
408
|
+
return notices.some((notice) => {
|
|
409
|
+
const definition = routeExcludeNotice[notice.severity];
|
|
410
|
+
|
|
411
|
+
return definition && (definition === 'all' || definition.includes(notice.code));
|
|
412
|
+
});
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
function decodePolyline(polyline: string): number[][] {
|
|
416
|
+
return decode(polyline).polyline;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function simplifyPath(path: number[][], precision: number): number[][] {
|
|
420
|
+
return simplify(
|
|
421
|
+
path.map(([x, y]) => ({ x, y })),
|
|
422
|
+
precision,
|
|
423
|
+
true,
|
|
424
|
+
).map((p) => [+p.y.toFixed(6), +p.x.toFixed(6)]);
|
|
425
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RouteSummary } from '@any-routing/core';
|
|
2
|
+
import { SelectRouteStrategy } from '../here-provider.types';
|
|
3
|
+
|
|
4
|
+
type RouteWithCost = RouteSummary & {
|
|
5
|
+
cost: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const hasCost = (route: RouteSummary): route is RouteWithCost =>
|
|
9
|
+
route.cost != null;
|
|
10
|
+
|
|
11
|
+
export function selectRouteByStrategy(routeSummaries: RouteSummary[], strategy?: SelectRouteStrategy) {
|
|
12
|
+
if (routeSummaries.length === 0) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (strategy === 'fastest') {
|
|
17
|
+
const fastest = routeSummaries.reduce(function (prev, current) {
|
|
18
|
+
return prev?.arriveTime.valueOf() < current?.arriveTime.valueOf() ? prev : current;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return fastest?.id;
|
|
22
|
+
} else if (strategy === 'shortest') {
|
|
23
|
+
const shortest = routeSummaries.reduce(function (prev, current) {
|
|
24
|
+
return prev?.distance < current?.distance ? prev : current;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return shortest?.id;
|
|
28
|
+
} else if (strategy === 'cheapest') {
|
|
29
|
+
const routesWithCost = routeSummaries.filter(hasCost);
|
|
30
|
+
const cheapest = routesWithCost.reduce(function (prev, current) {
|
|
31
|
+
return prev?.cost < current?.cost ? prev : current;
|
|
32
|
+
}, routesWithCost[0]);
|
|
33
|
+
|
|
34
|
+
return cheapest?.id;
|
|
35
|
+
} else if (strategy === 'none') {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "es2022",
|
|
5
|
+
"forceConsistentCasingInFileNames": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"importHelpers": true,
|
|
8
|
+
"noImplicitOverride": true,
|
|
9
|
+
"noImplicitReturns": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true,
|
|
11
|
+
"noPropertyAccessFromIndexSignature": true
|
|
12
|
+
},
|
|
13
|
+
"files": [],
|
|
14
|
+
"include": [],
|
|
15
|
+
"references": [
|
|
16
|
+
{
|
|
17
|
+
"path": "./tsconfig.lib.json"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "./tsconfig.spec.json"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"types": [
|
|
7
|
+
"node",
|
|
8
|
+
"vite/client"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"include": [
|
|
12
|
+
"src/**/*.ts"
|
|
13
|
+
],
|
|
14
|
+
"exclude": [
|
|
15
|
+
"vite.config.ts",
|
|
16
|
+
"vite.config.mts",
|
|
17
|
+
"vitest.config.ts",
|
|
18
|
+
"vitest.config.mts",
|
|
19
|
+
"src/**/*.test.ts",
|
|
20
|
+
"src/**/*.spec.ts",
|
|
21
|
+
"src/**/*.test.tsx",
|
|
22
|
+
"src/**/*.spec.tsx",
|
|
23
|
+
"src/**/*.test.js",
|
|
24
|
+
"src/**/*.spec.js",
|
|
25
|
+
"src/**/*.test.jsx",
|
|
26
|
+
"src/**/*.spec.jsx"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"types": [
|
|
6
|
+
"vitest/globals",
|
|
7
|
+
"vitest/importMeta",
|
|
8
|
+
"vite/client",
|
|
9
|
+
"node",
|
|
10
|
+
"vitest"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"vite.config.ts",
|
|
15
|
+
"vite.config.mts",
|
|
16
|
+
"vitest.config.ts",
|
|
17
|
+
"vitest.config.mts",
|
|
18
|
+
"src/**/*.test.ts",
|
|
19
|
+
"src/**/*.spec.ts",
|
|
20
|
+
"src/**/*.test.tsx",
|
|
21
|
+
"src/**/*.spec.tsx",
|
|
22
|
+
"src/**/*.test.js",
|
|
23
|
+
"src/**/*.spec.js",
|
|
24
|
+
"src/**/*.test.jsx",
|
|
25
|
+
"src/**/*.spec.jsx",
|
|
26
|
+
"src/**/*.d.ts"
|
|
27
|
+
]
|
|
28
|
+
}
|
package/vite.config.mts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/// <reference types='vitest' />
|
|
2
|
+
import { defineConfig } from 'vite';
|
|
3
|
+
import dts from 'vite-plugin-dts';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
|
6
|
+
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
|
7
|
+
|
|
8
|
+
export default defineConfig(() => ({
|
|
9
|
+
root: import.meta.dirname,
|
|
10
|
+
cacheDir: '../../node_modules/.vite/libs/here-data-provider',
|
|
11
|
+
plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), dts({ entryRoot: 'src', tsconfigPath: path.join(import.meta.dirname, 'tsconfig.lib.json'), pathsToAliases: false })],
|
|
12
|
+
// Uncomment this if you are using workers.
|
|
13
|
+
// worker: {
|
|
14
|
+
// plugins: () => [ nxViteTsPaths() ],
|
|
15
|
+
// },
|
|
16
|
+
// Configuration for building your library.
|
|
17
|
+
// See: https://vite.dev/guide/build.html#library-mode
|
|
18
|
+
build: {
|
|
19
|
+
outDir: '../../dist/libs/here-data-provider',
|
|
20
|
+
emptyOutDir: true,
|
|
21
|
+
reportCompressedSize: true,
|
|
22
|
+
commonjsOptions: {
|
|
23
|
+
transformMixedEsModules: true,
|
|
24
|
+
},
|
|
25
|
+
lib: {
|
|
26
|
+
// Could also be a dictionary or array of multiple entry points.
|
|
27
|
+
entry: 'src/index.ts',
|
|
28
|
+
name: 'here-data-provider',
|
|
29
|
+
fileName: 'index',
|
|
30
|
+
// Change this to the formats you want to support.
|
|
31
|
+
// Don't forget to update your package.json as well.
|
|
32
|
+
formats: ['es' as const]
|
|
33
|
+
},
|
|
34
|
+
rolldownOptions: {
|
|
35
|
+
// External packages that should not be bundled into your library.
|
|
36
|
+
external: []
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
test: {
|
|
40
|
+
name: 'here-data-provider',
|
|
41
|
+
watch: false,
|
|
42
|
+
globals: true,
|
|
43
|
+
environment: 'node',
|
|
44
|
+
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
45
|
+
reporters: ['default'],
|
|
46
|
+
coverage: {
|
|
47
|
+
reportsDirectory: '../../coverage/libs/here-data-provider',
|
|
48
|
+
provider: 'v8' as const,
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
}));
|
package/src/index.js
DELETED
package/src/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/here-data-provider/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { ExecutorRequestOptions } from './here.executor';
|
|
2
|
-
import type { AnyRoutingDataProvider, AnyRoutingDataResponse, RequestOptions, RouteSummary, LngLatPosition, Waypoint } from '@any-routing/core';
|
|
3
|
-
export type SelectRouteStrategy = 'fastest' | 'shortest' | 'cheapest' | 'none';
|
|
4
|
-
export type RouteExcludeNoticeDefinitions = {
|
|
5
|
-
[key in 'critical' | 'info']?: string[] | 'all';
|
|
6
|
-
};
|
|
7
|
-
export type TurnByTurnAction = {
|
|
8
|
-
action: 'arrive' | 'turn' | 'depart';
|
|
9
|
-
duration: number;
|
|
10
|
-
length: number;
|
|
11
|
-
position: {
|
|
12
|
-
lat: number;
|
|
13
|
-
lng: number;
|
|
14
|
-
};
|
|
15
|
-
offset: number;
|
|
16
|
-
};
|
|
17
|
-
export type RoutePath = LngLatPosition[];
|
|
18
|
-
export interface HereRouteSummary extends RouteSummary {
|
|
19
|
-
turnByTurnActions: TurnByTurnAction[];
|
|
20
|
-
shapePath: RoutePath;
|
|
21
|
-
rawRoute: any;
|
|
22
|
-
}
|
|
23
|
-
export { ExecutorRequestOptions } from './here.executor';
|
|
24
|
-
export interface HereRoutingData extends AnyRoutingDataResponse {
|
|
25
|
-
routes: HereRouteSummary[];
|
|
26
|
-
requestOptions: ExecutorRequestOptions;
|
|
27
|
-
}
|
|
28
|
-
export type GeoJsonSplitStrategy = 'jamFactor';
|
|
29
|
-
export type Options = {
|
|
30
|
-
alternatives?: number;
|
|
31
|
-
worker?: boolean;
|
|
32
|
-
apiKey: string;
|
|
33
|
-
baseUrl?: string;
|
|
34
|
-
selectRouteStrategy?: SelectRouteStrategy;
|
|
35
|
-
spans?: string[];
|
|
36
|
-
return?: string[];
|
|
37
|
-
currency?: string;
|
|
38
|
-
transportMode?: string;
|
|
39
|
-
queryParams?: Record<string, any>;
|
|
40
|
-
requestParams?: RequestInit;
|
|
41
|
-
routeExcludeNotice?: RouteExcludeNoticeDefinitions;
|
|
42
|
-
shapePolylinePrecision?: number;
|
|
43
|
-
geoJSONShapeSplitStrategies?: {
|
|
44
|
-
drag?: Array<GeoJsonSplitStrategy>;
|
|
45
|
-
default?: Array<GeoJsonSplitStrategy>;
|
|
46
|
-
};
|
|
47
|
-
buildUrl?: (ctx: {
|
|
48
|
-
waypoints: Waypoint[];
|
|
49
|
-
options: Options & RequestOptions;
|
|
50
|
-
}, url: string) => string;
|
|
51
|
-
};
|
|
52
|
-
export declare class HereProvider implements AnyRoutingDataProvider {
|
|
53
|
-
private worker?;
|
|
54
|
-
private executorAPI;
|
|
55
|
-
private _options;
|
|
56
|
-
get options(): Options;
|
|
57
|
-
constructor(options: Options);
|
|
58
|
-
destroy(): void;
|
|
59
|
-
request(waypoints: any, opts: RequestOptions): Promise<HereRoutingData>;
|
|
60
|
-
abortAllRequests(): void;
|
|
61
|
-
setOption<T extends keyof Options>(optionKey: T, value: Options[T]): void;
|
|
62
|
-
hasPendingRequests(): Promise<boolean>;
|
|
63
|
-
private buildUrl;
|
|
64
|
-
private serializeWaypoints;
|
|
65
|
-
private formatWp;
|
|
66
|
-
}
|