@any-routing/here-data-provider 0.0.6 → 1.0.0-rc.1
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 -62
- 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 -153
- 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
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
This library was generated with [Nx](https://nx.dev).
|
|
4
4
|
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build here-data-provider` to build the library.
|
|
8
|
+
|
|
5
9
|
## Running unit tests
|
|
6
10
|
|
|
7
|
-
Run `nx test here-data-provider` to execute the unit tests via [
|
|
11
|
+
Run `nx test here-data-provider` to execute the unit tests via [Vitest](https://vitest.dev/).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import baseConfig from "../../eslint.config.mjs";
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: [
|
|
7
|
+
"**/*.json"
|
|
8
|
+
],
|
|
9
|
+
rules: {
|
|
10
|
+
"@nx/dependency-checks": [
|
|
11
|
+
"error",
|
|
12
|
+
{
|
|
13
|
+
"ignoredFiles": [
|
|
14
|
+
"{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}",
|
|
15
|
+
"{projectRoot}/vite.config.{js,ts,mjs,mts}"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
languageOptions: {
|
|
21
|
+
parser: await import("jsonc-eslint-parser")
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
];
|
package/package.json
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@any-routing/here-data-provider",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
5
7
|
"dependencies": {
|
|
6
|
-
"@
|
|
7
|
-
"
|
|
8
|
-
"@
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
},
|
|
16
|
-
"main": "./src/index.js",
|
|
17
|
-
"types": "./src/index.d.ts"
|
|
18
|
-
}
|
|
8
|
+
"@any-routing/core": "1.0.0-rc.1",
|
|
9
|
+
"comlink": "4.4.2",
|
|
10
|
+
"@here/flexpolyline": "0.1.0",
|
|
11
|
+
"simplify-js": "1.2.4",
|
|
12
|
+
"@turf/bbox": "7.4.0",
|
|
13
|
+
"@turf/helpers": "7.4.0",
|
|
14
|
+
"@types/geojson": "7946.0.16"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "here-data-provider",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/here-data-provider/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"// targets": "to see all targets run: nx show project here-data-provider --web",
|
|
8
|
+
"targets": {}
|
|
9
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { wrap, Remote } from 'comlink';
|
|
2
|
+
|
|
3
|
+
import { ExecutorRequestOptions, HereExecutor } from './here.executor';
|
|
4
|
+
import type {
|
|
5
|
+
AnyRoutingDataProvider,
|
|
6
|
+
RequestOptions,
|
|
7
|
+
Waypoint,
|
|
8
|
+
WaypointPosition,
|
|
9
|
+
} from '@any-routing/core';
|
|
10
|
+
import { HereRoutingData, PluginOptions } from './here-provider.types';
|
|
11
|
+
|
|
12
|
+
const defaultOptions: Partial<PluginOptions> = {
|
|
13
|
+
baseUrl: 'https://router.hereapi.com/v8/routes',
|
|
14
|
+
transportMode: 'car',
|
|
15
|
+
worker: true,
|
|
16
|
+
alternatives: 2,
|
|
17
|
+
shapePolylinePrecision: 0.0000065,
|
|
18
|
+
routeExcludeNotice: {
|
|
19
|
+
critical: 'all',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class HereProvider implements AnyRoutingDataProvider {
|
|
24
|
+
private worker?: Worker;
|
|
25
|
+
// Comlink's `wrap` returns a `Remote<T>` proxy whose methods always
|
|
26
|
+
// resolve to promises, even for originally-synchronous ones — typing
|
|
27
|
+
// this union up front avoids the `@ts-ignore` the previous version
|
|
28
|
+
// needed on assignment.
|
|
29
|
+
private executorAPI: HereExecutor | Remote<HereExecutor>;
|
|
30
|
+
private _options: PluginOptions;
|
|
31
|
+
|
|
32
|
+
public get options(): PluginOptions {
|
|
33
|
+
return this._options;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(defaultModeOptions: PluginOptions) {
|
|
37
|
+
this._options = { ...defaultOptions, ...defaultModeOptions };
|
|
38
|
+
|
|
39
|
+
if (this.options.worker === true) {
|
|
40
|
+
this.worker = new Worker(new URL('./here.worker', import.meta.url), {
|
|
41
|
+
type: 'module',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.executorAPI = wrap<HereExecutor>(this.worker);
|
|
45
|
+
} else {
|
|
46
|
+
this.executorAPI = new HereExecutor();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public destroy(): void {
|
|
51
|
+
if (this.worker) {
|
|
52
|
+
this.worker.terminate();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public request(waypoints: Waypoint[], opts: Record<string, unknown>): Promise<HereRoutingData> {
|
|
57
|
+
const configOptions = this.options;
|
|
58
|
+
|
|
59
|
+
const url = this.buildUrl(waypoints, { ...configOptions, ...opts });
|
|
60
|
+
|
|
61
|
+
// `buildUrl` is a function and must never be forwarded into
|
|
62
|
+
// `requestParams` below — when running with `worker: true`,
|
|
63
|
+
// `executorAPI` is a Comlink `Remote<HereExecutor>` and the call goes
|
|
64
|
+
// through `postMessage`'s structured clone algorithm, which cannot
|
|
65
|
+
// clone functions and throws `DataCloneError` at runtime. A previous
|
|
66
|
+
// revision of this file destructured `buildUrl` out for exactly this
|
|
67
|
+
// reason; that got lost in the last edit — restoring it here.
|
|
68
|
+
const requestOptions = { ...configOptions, ...opts, url };
|
|
69
|
+
delete requestOptions.buildUrl;
|
|
70
|
+
const requestParams: ExecutorRequestOptions = requestOptions;
|
|
71
|
+
|
|
72
|
+
return this.executorAPI.request(requestParams);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async abortAllRequests(): Promise<void> {
|
|
76
|
+
await this.executorAPI.abortAllRequests();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public setOption<T extends keyof PluginOptions>(optionKey: T, value: PluginOptions[T]): void {
|
|
80
|
+
this.options[optionKey] = value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public async hasPendingRequests(): Promise<boolean> {
|
|
84
|
+
return await this.executorAPI.hasPendingRequests();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private buildUrl(waypoints: Waypoint[], opts: PluginOptions & RequestOptions): string {
|
|
88
|
+
if (!waypoints[0] || !waypoints[waypoints.length - 1]) {
|
|
89
|
+
throw new Error('At least two waypoints are required');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const start = waypoints[0].position;
|
|
93
|
+
const end = waypoints[waypoints.length - 1].position;
|
|
94
|
+
|
|
95
|
+
const spans = opts.spans?.join(',') ?? null;
|
|
96
|
+
|
|
97
|
+
const returnFields = [...(opts.return || []), 'polyline', 'summary', 'routeLabels'].join(',');
|
|
98
|
+
|
|
99
|
+
const queryParamsObj: Record<string, string | number | undefined | null> = {
|
|
100
|
+
apiKey: opts.apiKey,
|
|
101
|
+
origin: this.formatWp(start),
|
|
102
|
+
destination: this.formatWp(end),
|
|
103
|
+
transportMode: opts.transportMode || '',
|
|
104
|
+
spans,
|
|
105
|
+
return: returnFields,
|
|
106
|
+
alternatives: opts?.alternatives ?? 0,
|
|
107
|
+
currency: opts.currency || undefined,
|
|
108
|
+
...opts.queryParams,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (this.options.selectRouteStrategy === 'cheapest' && !String(queryParamsObj['return']).includes('tolls')) {
|
|
112
|
+
queryParamsObj['return'] = `${queryParamsObj['return']},tolls`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Preserves the original truthy filter (drops 0 / '' / null / undefined),
|
|
116
|
+
// just stringifies everything up front so `URLSearchParams` type-checks.
|
|
117
|
+
const queryParams: Record<string, string> = Object.entries(queryParamsObj).reduce(
|
|
118
|
+
(acc, [key, value]) => {
|
|
119
|
+
if (value) {
|
|
120
|
+
acc[key] = String(value);
|
|
121
|
+
}
|
|
122
|
+
return acc;
|
|
123
|
+
},
|
|
124
|
+
{} as Record<string, string>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
let qp = new URLSearchParams(queryParams).toString();
|
|
128
|
+
|
|
129
|
+
if (waypoints.length > 2) {
|
|
130
|
+
qp += `&${this.serializeWaypoints(waypoints)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const url = `${opts.baseUrl}?${qp}`;
|
|
134
|
+
|
|
135
|
+
return opts.buildUrl ? opts.buildUrl({ waypoints, options: opts }, url) : url;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private serializeWaypoints(waypoints: Waypoint[]): string {
|
|
139
|
+
return waypoints
|
|
140
|
+
.slice(1, waypoints.length - 1)
|
|
141
|
+
.map((w) => `via=${this.formatWp(w.position)}`)
|
|
142
|
+
.join('&');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private formatWp({ lat, lng }: WaypointPosition): string {
|
|
146
|
+
return `${lat},${lng}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { AnyRoutingDataResponse, LngLatPosition, RequestOptions, RouteSummary, Waypoint } from '@any-routing/core';
|
|
2
|
+
import type { ExecutorRequestOptions } from './here.executor';
|
|
3
|
+
|
|
4
|
+
export type SelectRouteStrategy = 'fastest' | 'shortest' | 'cheapest' | 'none';
|
|
5
|
+
|
|
6
|
+
export type RouteExcludeNoticeDefinitions = {
|
|
7
|
+
[key in 'critical' | 'info']?: string[] | 'all';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* HERE's `action` values on `turnByTurnActions` are documented as an
|
|
12
|
+
* "extensible enum" — HERE can add new maneuver types over time, and
|
|
13
|
+
* clients are expected to handle unknown values gracefully rather than
|
|
14
|
+
* fail. We type the values we know about explicitly (for autocomplete /
|
|
15
|
+
* exhaustiveness where it matters) but keep the type open to any other
|
|
16
|
+
* string so a future HERE rollout doesn't produce values TypeScript
|
|
17
|
+
* silently rejects at compile time / mangles at runtime.
|
|
18
|
+
*
|
|
19
|
+
* Source: HERE Routing API v8 docs — "What is an action".
|
|
20
|
+
*/
|
|
21
|
+
export type HereActionType =
|
|
22
|
+
| 'depart'
|
|
23
|
+
| 'arrive'
|
|
24
|
+
| 'continue'
|
|
25
|
+
| 'turn'
|
|
26
|
+
| 'uTurn'
|
|
27
|
+
| 'ramp'
|
|
28
|
+
| 'keep'
|
|
29
|
+
| 'roundaboutEnter'
|
|
30
|
+
| 'roundaboutPass'
|
|
31
|
+
| 'roundaboutExit'
|
|
32
|
+
| 'exit'
|
|
33
|
+
| 'ferry'
|
|
34
|
+
| (string & {});
|
|
35
|
+
|
|
36
|
+
export type TurnByTurnAction = {
|
|
37
|
+
action: HereActionType;
|
|
38
|
+
duration: number;
|
|
39
|
+
length: number;
|
|
40
|
+
position: { lat: number; lng: number };
|
|
41
|
+
offset: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type RoutePath = LngLatPosition[];
|
|
45
|
+
|
|
46
|
+
export interface HereRouteSummary extends RouteSummary {
|
|
47
|
+
turnByTurnActions: TurnByTurnAction[];
|
|
48
|
+
shapePath: RoutePath;
|
|
49
|
+
rawRoute: HereRawRoute;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface HereRoutingData extends AnyRoutingDataResponse {
|
|
53
|
+
routes: HereRouteSummary[];
|
|
54
|
+
requestOptions: ExecutorRequestOptions;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type GeoJsonSplitStrategy = 'jamFactor';
|
|
58
|
+
|
|
59
|
+
export type BaseOptions = {
|
|
60
|
+
alternatives?: number;
|
|
61
|
+
worker?: boolean;
|
|
62
|
+
apiKey: string;
|
|
63
|
+
baseUrl?: string;
|
|
64
|
+
selectRouteStrategy?: SelectRouteStrategy;
|
|
65
|
+
spans?: string[];
|
|
66
|
+
return?: string[];
|
|
67
|
+
currency?: string;
|
|
68
|
+
transportMode?: string;
|
|
69
|
+
queryParams?: Record<string, unknown>;
|
|
70
|
+
requestParams?: RequestInit;
|
|
71
|
+
routeExcludeNotice?: RouteExcludeNoticeDefinitions;
|
|
72
|
+
shapePolylinePrecision?: number;
|
|
73
|
+
geoJSONShapeSplitStrategies?: Array<GeoJsonSplitStrategy>;
|
|
74
|
+
buildUrl?: (ctx: { waypoints: Waypoint[]; options: BaseOptions & RequestOptions }, url: string) => string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type PluginOptions = BaseOptions;
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// HERE Routing API v8 raw response types (the subset the executor reads).
|
|
81
|
+
// These model the JSON documented at
|
|
82
|
+
// https://docs.here.com/routing/docs/routing-v8-get-started#response and the
|
|
83
|
+
// "What is a span" / "What is a notice" guides. Fields not consumed by this
|
|
84
|
+
// package are intentionally left out rather than fully modeled, and open
|
|
85
|
+
// dictionaries (`spans`) are typed loosely since their shape depends on the
|
|
86
|
+
// `spans=` request parameter the caller chose.
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
export interface HereRawPlace {
|
|
90
|
+
type: string;
|
|
91
|
+
location: { lat: number; lng: number };
|
|
92
|
+
/** Present only when this endpoint was geocoded from a raw waypoint. */
|
|
93
|
+
originalLocation?: { lat: number; lng: number };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface HereRawSectionEndpoint {
|
|
97
|
+
place: HereRawPlace;
|
|
98
|
+
time?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface HereRawSummary {
|
|
102
|
+
duration?: number;
|
|
103
|
+
length?: number;
|
|
104
|
+
baseDuration?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface HereRawFarePrice {
|
|
108
|
+
value: number;
|
|
109
|
+
currency: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface HereRawFare {
|
|
113
|
+
price?: HereRawFarePrice;
|
|
114
|
+
convertedPrice?: HereRawFarePrice;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface HereRawToll {
|
|
118
|
+
fares?: HereRawFare[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface HereRawDynamicSpeedInfo {
|
|
122
|
+
baseSpeed: number;
|
|
123
|
+
trafficSpeed: number;
|
|
124
|
+
turnTime?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A single `spans[]` entry. Which attributes are present depends entirely
|
|
129
|
+
* on the `spans=` query param sent in the request (see "What is a span" in
|
|
130
|
+
* the HERE docs), so beyond the `offset` every requested attribute name
|
|
131
|
+
* (`dynamicSpeedInfo`, `names`, `countryCode`, ...) shows up as an optional
|
|
132
|
+
* key — hence the index signature rather than an exhaustive interface.
|
|
133
|
+
*/
|
|
134
|
+
export interface HereRawSpan {
|
|
135
|
+
offset: number;
|
|
136
|
+
dynamicSpeedInfo?: HereRawDynamicSpeedInfo;
|
|
137
|
+
[attribute: string]: unknown;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface HereRawAction {
|
|
141
|
+
action: HereActionType;
|
|
142
|
+
duration: number;
|
|
143
|
+
length: number;
|
|
144
|
+
offset: number;
|
|
145
|
+
instruction?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface HereRawNotice {
|
|
149
|
+
code: string;
|
|
150
|
+
severity: 'critical' | 'info';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface HereRawSection {
|
|
154
|
+
id: string;
|
|
155
|
+
type: string;
|
|
156
|
+
departure: HereRawSectionEndpoint;
|
|
157
|
+
arrival: HereRawSectionEndpoint;
|
|
158
|
+
summary?: HereRawSummary;
|
|
159
|
+
polyline: string;
|
|
160
|
+
spans?: HereRawSpan[];
|
|
161
|
+
tolls?: HereRawToll[];
|
|
162
|
+
turnByTurnActions?: HereRawAction[];
|
|
163
|
+
notices?: HereRawNotice[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface HereRawRouteLabel {
|
|
167
|
+
name: { value: string; language?: string };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface HereRawRoute {
|
|
171
|
+
id: string;
|
|
172
|
+
sections: HereRawSection[];
|
|
173
|
+
routeLabels?: HereRawRouteLabel[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface HereApiResponse {
|
|
177
|
+
routes: HereRawRoute[];
|
|
178
|
+
notices?: HereRawNotice[];
|
|
179
|
+
}
|