@any-routing/valhalla-data-provider 1.0.0-rc.2 → 1.0.3
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/LICENCE +21 -0
- package/assets/valhalla.worker-D8MKr8Z8.js +7 -0
- package/index.js +174 -0
- package/lib/valhalla-data-provider.d.ts +15 -0
- package/lib/valhalla-provider.types.d.ts +90 -0
- package/lib/valhalla.executor.d.ts +15 -0
- package/lib/valhalla.worker.d.ts +1 -0
- package/package.json +5 -2
- package/eslint.config.mjs +0 -22
- package/project.json +0 -21
- package/src/lib/valhalla-data-provider.spec.ts +0 -7
- package/src/lib/valhalla-data-provider.ts +0 -77
- package/src/lib/valhalla-provider.types.ts +0 -95
- package/src/lib/valhalla.executor.spec.ts +0 -56
- package/src/lib/valhalla.executor.ts +0 -180
- package/src/lib/valhalla.worker.ts +0 -4
- package/tsconfig.json +0 -16
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -8
- package/vite.config.mts +0 -41
- /package/{src/index.ts → index.d.ts} +0 -0
package/LICENCE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 marucjmar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
(function(){
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2019 Google LLC
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
let e=Symbol(`Comlink.proxy`),t=Symbol(`Comlink.endpoint`),n=Symbol(`Comlink.releaseProxy`),r=Symbol(`Comlink.finalizer`),i=Symbol(`Comlink.thrown`),a=e=>typeof e==`object`&&!!e||typeof e==`function`,o=new Map([[`proxy`,{canHandle:t=>a(t)&&t[e],serialize(e){let{port1:t,port2:n}=new MessageChannel;return c(e,t),[n,[n]]},deserialize(e){return e.start(),d(e)}}],[`throw`,{canHandle:e=>a(e)&&i in e,serialize({value:e}){let t;return t=e instanceof Error?{isError:!0,value:{message:e.message,name:e.name,stack:e.stack}}:{isError:!1,value:e},[t,[]]},deserialize(e){throw e.isError?Object.assign(Error(e.value.message),e.value):e.value}}]]);function s(e,t){for(let n of e)if(t===n||n===`*`||n instanceof RegExp&&n.test(t))return!0;return!1}function c(e,t=globalThis,n=[`*`]){t.addEventListener(`message`,function a(o){if(!o||!o.data)return;if(!s(n,o.origin)){console.warn(`Invalid origin '${o.origin}' for comlink proxy`);return}let{id:l,type:d,path:f}=Object.assign({path:[]},o.data),p=(o.data.argumentList||[]).map(T),m;try{let t=f.slice(0,-1).reduce((e,t)=>e[t],e),n=f.reduce((e,t)=>e[t],e);switch(d){case`GET`:m=n;break;case`SET`:t[f.slice(-1)[0]]=T(o.data.value),m=!0;break;case`APPLY`:m=n.apply(t,p);break;case`CONSTRUCT`:m=C(new n(...p));break;case`ENDPOINT`:{let{port1:t,port2:n}=new MessageChannel;c(e,n),m=S(t,[t])}break;case`RELEASE`:m=void 0;break;default:return}}catch(e){m={value:e,[i]:0}}Promise.resolve(m).catch(e=>({value:e,[i]:0})).then(n=>{let[i,o]=w(n);t.postMessage(Object.assign(Object.assign({},i),{id:l}),o),d===`RELEASE`&&(t.removeEventListener(`message`,a),u(t),r in e&&typeof e[r]==`function`&&e[r]())}).catch(e=>{let[n,r]=w({value:TypeError(`Unserializable return value`),[i]:0});t.postMessage(Object.assign(Object.assign({},n),{id:l}),r)})}),t.start&&t.start()}function l(e){return e.constructor.name===`MessagePort`}function u(e){l(e)&&e.close()}function d(e,t){let n=new Map;return e.addEventListener(`message`,function(e){let{data:t}=e;if(!t||!t.id)return;let r=n.get(t.id);if(r)try{r(t)}finally{n.delete(t.id)}}),v(e,n,[],t)}function f(e){if(e)throw Error(`Proxy has been released and is not useable`)}function p(e){return E(e,new Map,{type:`RELEASE`}).then(()=>{u(e)})}let m=new WeakMap,h=`FinalizationRegistry`in globalThis&&new FinalizationRegistry(e=>{let t=(m.get(e)||0)-1;m.set(e,t),t===0&&p(e)});function g(e,t){let n=(m.get(t)||0)+1;m.set(t,n),h&&h.register(e,t,e)}function _(e){h&&h.unregister(e)}function v(e,r,i=[],a=function(){}){let o=!1,s=new Proxy(a,{get(t,a){if(f(o),a===n)return()=>{_(s),p(e),r.clear(),o=!0};if(a===`then`){if(i.length===0)return{then:()=>s};let t=E(e,r,{type:`GET`,path:i.map(e=>e.toString())}).then(T);return t.then.bind(t)}return v(e,r,[...i,a])},set(t,n,a){f(o);let[s,c]=w(a);return E(e,r,{type:`SET`,path:[...i,n].map(e=>e.toString()),value:s},c).then(T)},apply(n,a,s){f(o);let c=i[i.length-1];if(c===t)return E(e,r,{type:`ENDPOINT`}).then(T);if(c===`bind`)return v(e,r,i.slice(0,-1));let[l,u]=b(s);return E(e,r,{type:`APPLY`,path:i.map(e=>e.toString()),argumentList:l},u).then(T)},construct(t,n){f(o);let[a,s]=b(n);return E(e,r,{type:`CONSTRUCT`,path:i.map(e=>e.toString()),argumentList:a},s).then(T)}});return g(s,e),s}function y(e){return Array.prototype.concat.apply([],e)}function b(e){let t=e.map(w);return[t.map(e=>e[0]),y(t.map(e=>e[1]))]}let x=new WeakMap;function S(e,t){return x.set(e,t),e}function C(t){return Object.assign(t,{[e]:!0})}function w(e){for(let[t,n]of o)if(n.canHandle(e)){let[r,i]=n.serialize(e);return[{type:`HANDLER`,name:t,value:r},i]}return[{type:`RAW`,value:e},x.get(e)||[]]}function T(e){switch(e.type){case`HANDLER`:return o.get(e.name).deserialize(e.value);case`RAW`:return e.value}}function E(e,t,n,r){return new Promise(i=>{let a=D();t.set(a,i),e.start&&e.start(),e.postMessage(Object.assign({id:a},n),r)})}function D(){return[,,,,].fill(0).map(()=>Math.floor(Math.random()*(2**53-1)).toString(16)).join(`-`)}var O=6371008.8;O*100,O*100,O*100,360/(2*Math.PI),360/(2*Math.PI),O*3.28084,O*3.28084,O*39.37,O*39.37,O/1e3,O/1e3,O/1e3,O/1609.344,O/1609.344,O*1e3,O*1e3,O*1e3,O/1852,O/1852,O*1.0936,O*1.0936;function k(e,t,n={}){let r={type:`Feature`};return(n.id===0||n.id)&&(r.id=n.id),n.bbox&&(r.bbox=n.bbox),r.properties=t||{},r.geometry=e,r}function A(e,t,n={}){if(e.length<2)throw Error(`coordinates must be an array of two or more positions`);return k({type:`LineString`,coordinates:e},t,n)}function j(e,t={}){let n={type:`FeatureCollection`};return t.id&&(n.id=t.id),t.bbox&&(n.bbox=t.bbox),n.features=e,n}function M(e,t,n){if(e!==null)for(var r,i,a,o,s,c,l,u=0,d=0,f,p=e.type,m=p===`FeatureCollection`,h=p===`Feature`,g=m?e.features.length:1,_=0;_<g;_++){l=m?e.features[_].geometry:h?e.geometry:e,f=l?l.type===`GeometryCollection`:!1,s=f?l.geometries.length:1;for(var v=0;v<s;v++){var y=0,b=0;if(o=f?l.geometries[v]:l,o!==null){c=o.coordinates;var x=o.type;switch(u=n&&(x===`Polygon`||x===`MultiPolygon`)?1:0,x){case null:break;case`Point`:if(t(c,d,_,y,b)===!1)return!1;d++,y++;break;case`LineString`:case`MultiPoint`:for(r=0;r<c.length;r++){if(t(c[r],d,_,y,b)===!1)return!1;d++,x===`MultiPoint`&&y++}x===`LineString`&&y++;break;case`Polygon`:case`MultiLineString`:for(r=0;r<c.length;r++){for(i=0;i<c[r].length-u;i++){if(t(c[r][i],d,_,y,b)===!1)return!1;d++}x===`MultiLineString`&&y++,x===`Polygon`&&b++}x===`Polygon`&&y++;break;case`MultiPolygon`:for(r=0;r<c.length;r++){for(b=0,i=0;i<c[r].length;i++){for(a=0;a<c[r][i].length-u;a++){if(t(c[r][i][a],d,_,y,b)===!1)return!1;d++}b++}y++}break;case`GeometryCollection`:for(r=0;r<o.geometries.length;r++)if(M(o.geometries[r],t,n)===!1)return!1;break;default:throw Error(`Unknown Geometry Type`)}}}}}function N(e,t={}){if(e.bbox!=null&&!0!==t.recompute)return e.bbox;let n=[1/0,1/0,-1/0,-1/0];return M(e,e=>{n[0]>e[0]&&(n[0]=e[0]),n[1]>e[1]&&(n[1]=e[1]),n[2]<e[0]&&(n[2]=e[0]),n[3]<e[1]&&(n[3]=e[1])}),n}var P=N,F=class{constructor(e){this.response=e,this.code=401,this.message=`Unauthorized`}},I=class{constructor(){this.buffer=[],this.maxBuffer=4}get hasPendingRequests(){return this.buffer.length>0}async request(e,t){let n=new AbortController;this.buffer.length>=this.maxBuffer&&this.buffer.shift()?.abort(),this.buffer.push(n);try{let r=await fetch(e,{...t,signal:n.signal});if(!r.ok)throw r;return await r.json()}finally{this.cleanBuffer(n)}}abortAllRequests(){this.buffer.forEach(e=>e.abort()),this.buffer=[]}cleanBuffer(e){this.buffer=this.buffer.filter(t=>t!==e)}},L=class extends Error{constructor(e,t,n){super(e),this.status=t,this.body=n,this.name=`ValhallaAPIError`}},R=class{constructor(){this.requester=new I}async request(e){let t={locations:e.requestLocations,costing:e.costing,costing_options:e.costingOptions,units:e.units,language:e.language,alternates:e.alternatives??0,shape_format:e.shapeFormat,directions_options:e.directionsOptions};try{let n=await this.requester.request(e.url,{...e.requestParams,method:`POST`,headers:{...e.requestParams?.headers,Accept:`application/json`,"Content-Type":`application/json`},body:JSON.stringify(t)});if(!n.trip||n.trip.status&&n.trip.status!==0)throw Error(n.error||n.status_message||n.trip?.status_message||`Valhalla returned no route`);let r=[n.trip,...(n.alternates??[]).map(({trip:e})=>e)].map((t,n)=>z(t,n,e.shapeFormat)),i=j(r.flatMap((e,t)=>e.shape.features.map(n=>({id:t,...n,properties:{...n.properties,routeId:e.id,id:t}}))));return{routesShapeBounds:P(i),rawResponse:n,routes:r,selectedRouteId:r.length?0:null,routesShapeGeojson:i,version:performance.now(),latest:!this.requester.hasPendingRequests,requestOptions:e}}catch(e){if(e instanceof Response){let t=await e.json();throw e.status===401?new F(t):new L(t.error||t.status_message||`Valhalla returned an error`,e.status,t)}throw e}}hasPendingRequests(){return this.requester.hasPendingRequests}abortAllRequests(){this.requester.abortAllRequests()}};function z(e,t,n){let r=new Date,i=n===`polyline5`?5:6,a=e.legs.map(e=>B(e.shape,i)),o=a.flatMap((e,t)=>t?e.slice(1):e),s=a.filter(e=>e.length>1).map((e,n)=>A(e.map(([e,t])=>[t,e]),{waypoint:n,routeId:t})),c=e.summary??{},l=c.time??e.legs.reduce((e,t)=>e+(t.summary?.time??0),0),u=c.length??e.legs.reduce((e,t)=>e+(t.summary?.length??0),0),d=e.locations.map(({lat:e,lon:t})=>({lat:e,lng:t}));return{id:t,durationTime:l,distance:u,arriveTime:new Date(r.getTime()+l*1e3),departureTime:r,path:o,waypoints:d,shape:j(s),rawRoute:e}}function B(e,t){let n=[],r=10**t,i=0,a=0,o=0;for(;i<e.length;){let t=V(e,i);i=t.index,a+=t.value;let s=V(e,i);i=s.index,o+=s.value,n.push([a/r,o/r])}return n}function V(e,t){let n=0,r=0,i=t,a;do{if(i>=e.length)throw Error(`Invalid Valhalla polyline geometry`);a=e.charCodeAt(i++)-63,n|=(a&31)<<r,r+=5}while(a>=32);return{value:n&1?~(n>>1):n>>1,index:i}}c(new R)})();
|
package/index.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { wrap as e } from "comlink";
|
|
2
|
+
import t from "@turf/bbox";
|
|
3
|
+
import { featureCollection as n, lineString as r } from "@turf/helpers";
|
|
4
|
+
import { Requester as i, UnauthorizedError as a } from "@any-routing/core";
|
|
5
|
+
//#region src/lib/valhalla-provider.types.ts
|
|
6
|
+
var o = class extends Error {
|
|
7
|
+
constructor(e, t, n) {
|
|
8
|
+
super(e), this.status = t, this.body = n, this.name = "ValhallaAPIError";
|
|
9
|
+
}
|
|
10
|
+
}, s = class {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.requester = new i();
|
|
13
|
+
}
|
|
14
|
+
async request(e) {
|
|
15
|
+
let r = {
|
|
16
|
+
locations: e.requestLocations,
|
|
17
|
+
costing: e.costing,
|
|
18
|
+
costing_options: e.costingOptions,
|
|
19
|
+
units: e.units,
|
|
20
|
+
language: e.language,
|
|
21
|
+
alternates: e.alternatives ?? 0,
|
|
22
|
+
shape_format: e.shapeFormat,
|
|
23
|
+
directions_options: e.directionsOptions
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
let i = await this.requester.request(e.url, {
|
|
27
|
+
...e.requestParams,
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
...e.requestParams?.headers,
|
|
31
|
+
Accept: "application/json",
|
|
32
|
+
"Content-Type": "application/json"
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify(r)
|
|
35
|
+
});
|
|
36
|
+
if (!i.trip || i.trip.status && i.trip.status !== 0) throw Error(i.error || i.status_message || i.trip?.status_message || "Valhalla returned no route");
|
|
37
|
+
let a = [i.trip, ...(i.alternates ?? []).map(({ trip: e }) => e)].map((t, n) => c(t, n, e.shapeFormat)), o = a.flatMap((e, t) => e.shape.features.map((n) => ({
|
|
38
|
+
id: t,
|
|
39
|
+
...n,
|
|
40
|
+
properties: {
|
|
41
|
+
...n.properties,
|
|
42
|
+
routeId: e.id,
|
|
43
|
+
id: t
|
|
44
|
+
}
|
|
45
|
+
}))), s = n(o);
|
|
46
|
+
return {
|
|
47
|
+
routesShapeBounds: t(s),
|
|
48
|
+
rawResponse: i,
|
|
49
|
+
routes: a,
|
|
50
|
+
selectedRouteId: a.length ? 0 : null,
|
|
51
|
+
routesShapeGeojson: s,
|
|
52
|
+
version: performance.now(),
|
|
53
|
+
latest: !this.requester.hasPendingRequests,
|
|
54
|
+
requestOptions: e
|
|
55
|
+
};
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (e instanceof Response) {
|
|
58
|
+
let t = await e.json();
|
|
59
|
+
throw e.status === 401 ? new a(t) : new o(t.error || t.status_message || "Valhalla returned an error", e.status, t);
|
|
60
|
+
}
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
hasPendingRequests() {
|
|
65
|
+
return this.requester.hasPendingRequests;
|
|
66
|
+
}
|
|
67
|
+
abortAllRequests() {
|
|
68
|
+
this.requester.abortAllRequests();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function c(e, t, i) {
|
|
72
|
+
let a = /* @__PURE__ */ new Date(), o = i === "polyline5" ? 5 : 6, s = e.legs.map((e) => l(e.shape, o)), c = s.flatMap((e, t) => t ? e.slice(1) : e), u = s.filter((e) => e.length > 1).map((e, n) => r(e.map(([e, t]) => [t, e]), {
|
|
73
|
+
waypoint: n,
|
|
74
|
+
routeId: t
|
|
75
|
+
})), d = e.summary ?? {}, f = d.time ?? e.legs.reduce((e, t) => e + (t.summary?.time ?? 0), 0), p = d.length ?? e.legs.reduce((e, t) => e + (t.summary?.length ?? 0), 0), m = e.locations.map(({ lat: e, lon: t }) => ({
|
|
76
|
+
lat: e,
|
|
77
|
+
lng: t
|
|
78
|
+
}));
|
|
79
|
+
return {
|
|
80
|
+
id: t,
|
|
81
|
+
durationTime: f,
|
|
82
|
+
distance: p,
|
|
83
|
+
arriveTime: new Date(a.getTime() + f * 1e3),
|
|
84
|
+
departureTime: a,
|
|
85
|
+
path: c,
|
|
86
|
+
waypoints: m,
|
|
87
|
+
shape: n(u),
|
|
88
|
+
rawRoute: e
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function l(e, t) {
|
|
92
|
+
let n = [], r = 10 ** t, i = 0, a = 0, o = 0;
|
|
93
|
+
for (; i < e.length;) {
|
|
94
|
+
let t = u(e, i);
|
|
95
|
+
i = t.index, a += t.value;
|
|
96
|
+
let s = u(e, i);
|
|
97
|
+
i = s.index, o += s.value, n.push([a / r, o / r]);
|
|
98
|
+
}
|
|
99
|
+
return n;
|
|
100
|
+
}
|
|
101
|
+
function u(e, t) {
|
|
102
|
+
let n = 0, r = 0, i = t, a;
|
|
103
|
+
do {
|
|
104
|
+
if (i >= e.length) throw Error("Invalid Valhalla polyline geometry");
|
|
105
|
+
a = e.charCodeAt(i++) - 63, n |= (a & 31) << r, r += 5;
|
|
106
|
+
} while (a >= 32);
|
|
107
|
+
return {
|
|
108
|
+
value: n & 1 ? ~(n >> 1) : n >> 1,
|
|
109
|
+
index: i
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/lib/valhalla-data-provider.ts
|
|
114
|
+
var d = {
|
|
115
|
+
baseUrl: "https://valhalla1.openstreetmap.de/route",
|
|
116
|
+
costing: "auto",
|
|
117
|
+
units: "kilometers",
|
|
118
|
+
language: "en-US",
|
|
119
|
+
worker: !0,
|
|
120
|
+
alternatives: 0,
|
|
121
|
+
shapeFormat: "polyline6"
|
|
122
|
+
}, f = class {
|
|
123
|
+
get options() {
|
|
124
|
+
return this._options;
|
|
125
|
+
}
|
|
126
|
+
constructor(t = {}) {
|
|
127
|
+
this._options = {
|
|
128
|
+
...d,
|
|
129
|
+
...t
|
|
130
|
+
}, this.options.worker ? (this.worker = new Worker(new URL(
|
|
131
|
+
/* @vite-ignore */
|
|
132
|
+
"/assets/valhalla.worker-D8MKr8Z8.js",
|
|
133
|
+
"" + import.meta.url
|
|
134
|
+
), { type: "module" }), this.executorAPI = e(this.worker)) : this.executorAPI = new s();
|
|
135
|
+
}
|
|
136
|
+
destroy() {
|
|
137
|
+
this.worker?.terminate();
|
|
138
|
+
}
|
|
139
|
+
request(e, t) {
|
|
140
|
+
let n = this.buildUrl(e, {
|
|
141
|
+
...this.options,
|
|
142
|
+
...t
|
|
143
|
+
}), r = {
|
|
144
|
+
...this.options,
|
|
145
|
+
...t,
|
|
146
|
+
url: n,
|
|
147
|
+
requestLocations: e.map(({ position: t }, n) => ({
|
|
148
|
+
lat: t.lat,
|
|
149
|
+
lon: t.lng,
|
|
150
|
+
type: n === 0 || n === e.length - 1 ? "break" : "via"
|
|
151
|
+
}))
|
|
152
|
+
};
|
|
153
|
+
return delete r.buildUrl, this.executorAPI.request(r);
|
|
154
|
+
}
|
|
155
|
+
async abortAllRequests() {
|
|
156
|
+
await this.executorAPI.abortAllRequests();
|
|
157
|
+
}
|
|
158
|
+
setOption(e, t) {
|
|
159
|
+
this.options[e] = t;
|
|
160
|
+
}
|
|
161
|
+
async hasPendingRequests() {
|
|
162
|
+
return await this.executorAPI.hasPendingRequests();
|
|
163
|
+
}
|
|
164
|
+
buildUrl(e, t) {
|
|
165
|
+
if (e.length < 2) throw Error("At least two waypoints are required");
|
|
166
|
+
let n = new URLSearchParams(Object.entries(t.queryParams ?? {}).map(([e, t]) => [e, String(t)])).toString(), r = `${t.baseUrl ?? d.baseUrl}${n ? `?${n}` : ""}`;
|
|
167
|
+
return t.buildUrl ? t.buildUrl({
|
|
168
|
+
waypoints: e,
|
|
169
|
+
options: t
|
|
170
|
+
}, r) : r;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
//#endregion
|
|
174
|
+
export { o as ValhallaAPIError, f as ValhallaProvider };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AnyRoutingDataProvider, RequestOptions, Waypoint } from '@any-routing/core';
|
|
2
|
+
import { Options, ValhallaRoutingData } from './valhalla-provider.types';
|
|
3
|
+
export declare class ValhallaProvider implements AnyRoutingDataProvider {
|
|
4
|
+
private worker?;
|
|
5
|
+
private executorAPI;
|
|
6
|
+
private _options;
|
|
7
|
+
get options(): Options;
|
|
8
|
+
constructor(options?: Options);
|
|
9
|
+
destroy(): void;
|
|
10
|
+
request(waypoints: Waypoint[], opts: RequestOptions): Promise<ValhallaRoutingData>;
|
|
11
|
+
abortAllRequests(): Promise<void>;
|
|
12
|
+
setOption<T extends keyof Options>(key: T, value: Options[T]): void;
|
|
13
|
+
hasPendingRequests(): Promise<boolean>;
|
|
14
|
+
private buildUrl;
|
|
15
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { AnyRoutingDataResponse, RequestOptions, RouteSummary, Waypoint } from '@any-routing/core';
|
|
2
|
+
import { ExecutorRequestOptions } from './valhalla.executor';
|
|
3
|
+
export type ValhallaLocationType = 'break' | 'through' | 'via' | 'break_through';
|
|
4
|
+
export type ValhallaUnits = 'kilometers' | 'miles';
|
|
5
|
+
export interface ValhallaRouteSummary extends RouteSummary {
|
|
6
|
+
rawRoute: ValhallaRawTrip;
|
|
7
|
+
}
|
|
8
|
+
export interface ValhallaRoutingData extends AnyRoutingDataResponse {
|
|
9
|
+
routes: ValhallaRouteSummary[];
|
|
10
|
+
requestOptions: ExecutorRequestOptions;
|
|
11
|
+
}
|
|
12
|
+
export interface ValhallaRawManeuver {
|
|
13
|
+
type?: number;
|
|
14
|
+
instruction?: string;
|
|
15
|
+
length?: number;
|
|
16
|
+
time?: number;
|
|
17
|
+
begin_shape_index?: number;
|
|
18
|
+
end_shape_index?: number;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface ValhallaRawLeg {
|
|
22
|
+
shape: string;
|
|
23
|
+
summary?: {
|
|
24
|
+
length?: number;
|
|
25
|
+
time?: number;
|
|
26
|
+
cost?: number;
|
|
27
|
+
};
|
|
28
|
+
maneuvers?: ValhallaRawManeuver[];
|
|
29
|
+
}
|
|
30
|
+
export interface ValhallaRawLocation {
|
|
31
|
+
lat: number;
|
|
32
|
+
lon: number;
|
|
33
|
+
type?: ValhallaLocationType;
|
|
34
|
+
original_index?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ValhallaRawTrip {
|
|
37
|
+
locations: ValhallaRawLocation[];
|
|
38
|
+
legs: ValhallaRawLeg[];
|
|
39
|
+
summary?: {
|
|
40
|
+
length?: number;
|
|
41
|
+
time?: number;
|
|
42
|
+
cost?: number;
|
|
43
|
+
};
|
|
44
|
+
status?: number;
|
|
45
|
+
status_message?: string;
|
|
46
|
+
units?: string;
|
|
47
|
+
[key: string]: unknown;
|
|
48
|
+
}
|
|
49
|
+
export interface ValhallaApiResponse {
|
|
50
|
+
trip?: ValhallaRawTrip;
|
|
51
|
+
alternates?: Array<{
|
|
52
|
+
trip: ValhallaRawTrip;
|
|
53
|
+
}>;
|
|
54
|
+
error_code?: number;
|
|
55
|
+
error?: string;
|
|
56
|
+
status?: number;
|
|
57
|
+
status_message?: string;
|
|
58
|
+
}
|
|
59
|
+
export type Options = {
|
|
60
|
+
baseUrl?: string;
|
|
61
|
+
worker?: boolean;
|
|
62
|
+
costing?: string;
|
|
63
|
+
costingOptions?: Record<string, unknown>;
|
|
64
|
+
units?: ValhallaUnits;
|
|
65
|
+
language?: string;
|
|
66
|
+
alternatives?: number;
|
|
67
|
+
shapeFormat?: 'polyline6' | 'polyline5';
|
|
68
|
+
directionsOptions?: Record<string, unknown>;
|
|
69
|
+
queryParams?: Record<string, string | number | boolean>;
|
|
70
|
+
requestParams?: RequestInit;
|
|
71
|
+
buildUrl?: (ctx: {
|
|
72
|
+
waypoints: Waypoint[];
|
|
73
|
+
options: Options & RequestOptions;
|
|
74
|
+
}, url: string) => string;
|
|
75
|
+
};
|
|
76
|
+
export declare class ValhallaAPIError extends Error {
|
|
77
|
+
readonly status?: number | undefined;
|
|
78
|
+
readonly body?: {
|
|
79
|
+
error?: string;
|
|
80
|
+
error_code?: number;
|
|
81
|
+
status_code?: number;
|
|
82
|
+
status: string;
|
|
83
|
+
} | undefined;
|
|
84
|
+
constructor(message: string, status?: number | undefined, body?: {
|
|
85
|
+
error?: string;
|
|
86
|
+
error_code?: number;
|
|
87
|
+
status_code?: number;
|
|
88
|
+
status: string;
|
|
89
|
+
} | undefined);
|
|
90
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Options, ValhallaRoutingData } from './valhalla-provider.types';
|
|
2
|
+
export type ExecutorRequestOptions = {
|
|
3
|
+
url: string;
|
|
4
|
+
requestLocations: Array<{
|
|
5
|
+
lat: number;
|
|
6
|
+
lon: number;
|
|
7
|
+
type: string;
|
|
8
|
+
}>;
|
|
9
|
+
} & Options;
|
|
10
|
+
export declare class ValhallaExecutor {
|
|
11
|
+
private readonly requester;
|
|
12
|
+
request(opts: ExecutorRequestOptions): Promise<ValhallaRoutingData>;
|
|
13
|
+
hasPendingRequests(): boolean;
|
|
14
|
+
abortAllRequests(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@any-routing/valhalla-data-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"directory": "../../dist/libs/valhalla-data-provider"
|
|
9
|
+
},
|
|
7
10
|
"dependencies": {
|
|
8
|
-
"@any-routing/core": "1.0.
|
|
11
|
+
"@any-routing/core": "1.0.2",
|
|
9
12
|
"@turf/bbox": "7.4.0",
|
|
10
13
|
"@turf/helpers": "7.4.0",
|
|
11
14
|
"@types/geojson": "7946.0.16",
|
package/eslint.config.mjs
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import baseConfig from '../../eslint.config.mjs';
|
|
2
|
-
|
|
3
|
-
export default [
|
|
4
|
-
...baseConfig,
|
|
5
|
-
{
|
|
6
|
-
files: ['**/*.json'],
|
|
7
|
-
rules: {
|
|
8
|
-
'@nx/dependency-checks': [
|
|
9
|
-
'error',
|
|
10
|
-
{
|
|
11
|
-
ignoredFiles: [
|
|
12
|
-
'{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}',
|
|
13
|
-
'{projectRoot}/vite.config.{js,ts,mjs,mts}',
|
|
14
|
-
],
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
},
|
|
18
|
-
languageOptions: {
|
|
19
|
-
parser: await import('jsonc-eslint-parser'),
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
];
|
package/project.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "valhalla-data-provider",
|
|
3
|
-
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "libs/valhalla-data-provider/src",
|
|
5
|
-
"projectType": "library",
|
|
6
|
-
"release": {
|
|
7
|
-
"version": {
|
|
8
|
-
"manifestRootsToUpdate": ["dist/{projectRoot}"],
|
|
9
|
-
"currentVersionResolver": "git-tag",
|
|
10
|
-
"fallbackCurrentVersionResolver": "disk"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
"tags": [],
|
|
14
|
-
"targets": {
|
|
15
|
-
"nx-release-publish": {
|
|
16
|
-
"options": {
|
|
17
|
-
"packageRoot": "dist/{projectRoot}"
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { Remote, wrap } from 'comlink';
|
|
2
|
-
import type { AnyRoutingDataProvider, RequestOptions, Waypoint } from '@any-routing/core';
|
|
3
|
-
import { ValhallaExecutor, type ExecutorRequestOptions } from './valhalla.executor';
|
|
4
|
-
import type { Options, ValhallaRoutingData } from './valhalla-provider.types';
|
|
5
|
-
|
|
6
|
-
const defaultOptions: Partial<Options> = {
|
|
7
|
-
baseUrl: 'https://valhalla1.openstreetmap.de/route',
|
|
8
|
-
costing: 'auto',
|
|
9
|
-
units: 'kilometers',
|
|
10
|
-
language: 'en-US',
|
|
11
|
-
worker: true,
|
|
12
|
-
alternatives: 0,
|
|
13
|
-
shapeFormat: 'polyline6',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export class ValhallaProvider implements AnyRoutingDataProvider {
|
|
17
|
-
private worker?: Worker;
|
|
18
|
-
private executorAPI: ValhallaExecutor | Remote<ValhallaExecutor>;
|
|
19
|
-
private _options: Options;
|
|
20
|
-
|
|
21
|
-
public get options(): Options {
|
|
22
|
-
return this._options;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
constructor(options: Options = {}) {
|
|
26
|
-
this._options = { ...defaultOptions, ...options };
|
|
27
|
-
if (this.options.worker) {
|
|
28
|
-
this.worker = new Worker(new URL('./valhalla.worker', import.meta.url), { type: 'module' });
|
|
29
|
-
this.executorAPI = wrap<ValhallaExecutor>(this.worker);
|
|
30
|
-
} else {
|
|
31
|
-
this.executorAPI = new ValhallaExecutor();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public destroy(): void {
|
|
36
|
-
this.worker?.terminate();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public request(waypoints: Waypoint[], opts: RequestOptions): Promise<ValhallaRoutingData> {
|
|
40
|
-
const url = this.buildUrl(waypoints, { ...this.options, ...opts });
|
|
41
|
-
const requestOptions = {
|
|
42
|
-
...this.options,
|
|
43
|
-
...opts,
|
|
44
|
-
url,
|
|
45
|
-
requestLocations: waypoints.map(({ position }, index) => ({
|
|
46
|
-
lat: position.lat,
|
|
47
|
-
lon: position.lng,
|
|
48
|
-
type: index === 0 || index === waypoints.length - 1 ? 'break' : 'via',
|
|
49
|
-
})),
|
|
50
|
-
};
|
|
51
|
-
delete requestOptions.buildUrl;
|
|
52
|
-
return this.executorAPI.request(requestOptions as ExecutorRequestOptions);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public async abortAllRequests(): Promise<void> {
|
|
56
|
-
await this.executorAPI.abortAllRequests();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public setOption<T extends keyof Options>(key: T, value: Options[T]): void {
|
|
60
|
-
this.options[key] = value;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public async hasPendingRequests(): Promise<boolean> {
|
|
64
|
-
return await this.executorAPI.hasPendingRequests();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private buildUrl(waypoints: Waypoint[], opts: Options & RequestOptions): string {
|
|
68
|
-
if (waypoints.length < 2) {
|
|
69
|
-
throw new Error('At least two waypoints are required');
|
|
70
|
-
}
|
|
71
|
-
const query = new URLSearchParams(
|
|
72
|
-
Object.entries(opts.queryParams ?? {}).map(([key, value]) => [key, String(value)]),
|
|
73
|
-
).toString();
|
|
74
|
-
const url = `${opts.baseUrl ?? defaultOptions.baseUrl}${query ? `?${query}` : ''}`;
|
|
75
|
-
return opts.buildUrl ? opts.buildUrl({ waypoints, options: opts }, url) : url;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AnyRoutingDataResponse,
|
|
3
|
-
RequestOptions,
|
|
4
|
-
RouteSummary,
|
|
5
|
-
Waypoint,
|
|
6
|
-
} from '@any-routing/core';
|
|
7
|
-
import type { ExecutorRequestOptions } from './valhalla.executor';
|
|
8
|
-
|
|
9
|
-
export type ValhallaLocationType = 'break' | 'through' | 'via' | 'break_through';
|
|
10
|
-
export type ValhallaUnits = 'kilometers' | 'miles';
|
|
11
|
-
|
|
12
|
-
export interface ValhallaRouteSummary extends RouteSummary {
|
|
13
|
-
rawRoute: ValhallaRawTrip;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ValhallaRoutingData extends AnyRoutingDataResponse {
|
|
17
|
-
routes: ValhallaRouteSummary[];
|
|
18
|
-
requestOptions: ExecutorRequestOptions;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ValhallaRawManeuver {
|
|
22
|
-
type?: number;
|
|
23
|
-
instruction?: string;
|
|
24
|
-
length?: number;
|
|
25
|
-
time?: number;
|
|
26
|
-
begin_shape_index?: number;
|
|
27
|
-
end_shape_index?: number;
|
|
28
|
-
[key: string]: unknown;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ValhallaRawLeg {
|
|
32
|
-
shape: string;
|
|
33
|
-
summary?: { length?: number; time?: number; cost?: number };
|
|
34
|
-
maneuvers?: ValhallaRawManeuver[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface ValhallaRawLocation {
|
|
38
|
-
lat: number;
|
|
39
|
-
lon: number;
|
|
40
|
-
type?: ValhallaLocationType;
|
|
41
|
-
original_index?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface ValhallaRawTrip {
|
|
45
|
-
locations: ValhallaRawLocation[];
|
|
46
|
-
legs: ValhallaRawLeg[];
|
|
47
|
-
summary?: { length?: number; time?: number; cost?: number };
|
|
48
|
-
status?: number;
|
|
49
|
-
status_message?: string;
|
|
50
|
-
units?: string;
|
|
51
|
-
[key: string]: unknown;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ValhallaApiResponse {
|
|
55
|
-
trip?: ValhallaRawTrip;
|
|
56
|
-
alternates?: Array<{ trip: ValhallaRawTrip }>;
|
|
57
|
-
error_code?: number;
|
|
58
|
-
error?: string;
|
|
59
|
-
status?: number;
|
|
60
|
-
status_message?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type Options = {
|
|
64
|
-
baseUrl?: string;
|
|
65
|
-
worker?: boolean;
|
|
66
|
-
costing?: string;
|
|
67
|
-
costingOptions?: Record<string, unknown>;
|
|
68
|
-
units?: ValhallaUnits;
|
|
69
|
-
language?: string;
|
|
70
|
-
alternatives?: number;
|
|
71
|
-
shapeFormat?: 'polyline6' | 'polyline5';
|
|
72
|
-
directionsOptions?: Record<string, unknown>;
|
|
73
|
-
queryParams?: Record<string, string | number | boolean>;
|
|
74
|
-
requestParams?: RequestInit;
|
|
75
|
-
buildUrl?: (
|
|
76
|
-
ctx: { waypoints: Waypoint[]; options: Options & RequestOptions },
|
|
77
|
-
url: string,
|
|
78
|
-
) => string;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export class ValhallaAPIError extends Error {
|
|
82
|
-
constructor(
|
|
83
|
-
message: string,
|
|
84
|
-
public readonly status?: number,
|
|
85
|
-
public readonly body?: {
|
|
86
|
-
error?: string;
|
|
87
|
-
error_code?: number;
|
|
88
|
-
status_code?: number;
|
|
89
|
-
status: string;
|
|
90
|
-
},
|
|
91
|
-
) {
|
|
92
|
-
super(message);
|
|
93
|
-
this.name = 'ValhallaAPIError';
|
|
94
|
-
}
|
|
95
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { ValhallaExecutor } from './valhalla.executor';
|
|
3
|
-
|
|
4
|
-
describe('ValhallaExecutor', () => {
|
|
5
|
-
it('posts a Valhalla request and normalizes polyline geometry', async () => {
|
|
6
|
-
const fetchMock = vi.fn().mockResolvedValue(
|
|
7
|
-
new Response(
|
|
8
|
-
JSON.stringify({
|
|
9
|
-
trip: {
|
|
10
|
-
locations: [
|
|
11
|
-
{ lat: 38.5, lon: -120.2 },
|
|
12
|
-
{ lat: 40.7, lon: -120.95 },
|
|
13
|
-
],
|
|
14
|
-
legs: [
|
|
15
|
-
{
|
|
16
|
-
shape: '_p~iF~ps|U_ulLnnqC_mqNvxq`@',
|
|
17
|
-
summary: { length: 100, time: 12 },
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
summary: { length: 100, time: 12 },
|
|
21
|
-
status: 0,
|
|
22
|
-
},
|
|
23
|
-
}),
|
|
24
|
-
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
25
|
-
),
|
|
26
|
-
);
|
|
27
|
-
vi.stubGlobal('fetch', fetchMock);
|
|
28
|
-
|
|
29
|
-
const result = await new ValhallaExecutor().request({
|
|
30
|
-
url: 'https://example.test/route',
|
|
31
|
-
mode: 'default',
|
|
32
|
-
requestLocations: [
|
|
33
|
-
{ lat: 38.5, lon: -120.2, type: 'break' },
|
|
34
|
-
{ lat: 40.7, lon: -120.95, type: 'break' },
|
|
35
|
-
],
|
|
36
|
-
costing: 'auto',
|
|
37
|
-
units: 'kilometers',
|
|
38
|
-
shapeFormat: 'polyline5',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
42
|
-
'https://example.test/route',
|
|
43
|
-
expect.objectContaining({
|
|
44
|
-
method: 'POST',
|
|
45
|
-
body: expect.stringContaining('"locations"'),
|
|
46
|
-
}),
|
|
47
|
-
);
|
|
48
|
-
expect(result.routes[0]?.distance).toBe(100);
|
|
49
|
-
expect(result.routes[0]?.path).toEqual([
|
|
50
|
-
[38.5, -120.2],
|
|
51
|
-
[40.7, -120.95],
|
|
52
|
-
[43.252, -126.453],
|
|
53
|
-
]);
|
|
54
|
-
expect(result.routesShapeGeojson.features[0]?.geometry.coordinates[0]).toEqual([-120.2, 38.5]);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import bbox from '@turf/bbox';
|
|
2
|
-
import { featureCollection, lineString } from '@turf/helpers';
|
|
3
|
-
import type { BBox, Feature, LineString } from 'geojson';
|
|
4
|
-
import { Requester, UnauthorizedError } from '@any-routing/core';
|
|
5
|
-
import {
|
|
6
|
-
ValhallaAPIError,
|
|
7
|
-
type Options,
|
|
8
|
-
type ValhallaApiResponse,
|
|
9
|
-
type ValhallaRawLeg,
|
|
10
|
-
type ValhallaRawTrip,
|
|
11
|
-
type ValhallaRouteSummary,
|
|
12
|
-
type ValhallaRoutingData,
|
|
13
|
-
} from './valhalla-provider.types';
|
|
14
|
-
|
|
15
|
-
export type ExecutorRequestOptions = {
|
|
16
|
-
url: string;
|
|
17
|
-
requestLocations: Array<{ lat: number; lon: number; type: string }>;
|
|
18
|
-
} & Options;
|
|
19
|
-
|
|
20
|
-
type ShapeProperties = { waypoint: number; routeId: number };
|
|
21
|
-
|
|
22
|
-
export class ValhallaExecutor {
|
|
23
|
-
private readonly requester = new Requester();
|
|
24
|
-
|
|
25
|
-
async request(opts: ExecutorRequestOptions): Promise<ValhallaRoutingData> {
|
|
26
|
-
const body = {
|
|
27
|
-
locations: opts.requestLocations,
|
|
28
|
-
costing: opts.costing,
|
|
29
|
-
costing_options: opts.costingOptions,
|
|
30
|
-
units: opts.units,
|
|
31
|
-
language: opts.language,
|
|
32
|
-
alternates: opts.alternatives ?? 0,
|
|
33
|
-
shape_format: opts.shapeFormat,
|
|
34
|
-
directions_options: opts.directionsOptions,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const response = (await this.requester.request(opts.url, {
|
|
39
|
-
...opts.requestParams,
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: {
|
|
42
|
-
...opts.requestParams?.headers,
|
|
43
|
-
Accept: 'application/json',
|
|
44
|
-
'Content-Type': 'application/json',
|
|
45
|
-
},
|
|
46
|
-
body: JSON.stringify(body),
|
|
47
|
-
})) as ValhallaApiResponse;
|
|
48
|
-
|
|
49
|
-
if (!response.trip || (response.trip.status && response.trip.status !== 0)) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
response.error ||
|
|
52
|
-
response.status_message ||
|
|
53
|
-
response.trip?.status_message ||
|
|
54
|
-
'Valhalla returned no route',
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const trips = [response.trip, ...(response.alternates ?? []).map(({ trip }) => trip)];
|
|
59
|
-
const routes = trips.map((trip, routeId) => summarizeRoute(trip, routeId, opts.shapeFormat));
|
|
60
|
-
const features = routes.flatMap((route, fid) =>
|
|
61
|
-
route.shape.features.map((feature) => ({
|
|
62
|
-
id: fid,
|
|
63
|
-
...feature,
|
|
64
|
-
properties: { ...feature.properties, routeId: route.id, id: fid },
|
|
65
|
-
})),
|
|
66
|
-
);
|
|
67
|
-
const routesShapeGeojson = featureCollection(features);
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
routesShapeBounds: bbox(routesShapeGeojson) as BBox,
|
|
71
|
-
rawResponse: response,
|
|
72
|
-
routes,
|
|
73
|
-
selectedRouteId: routes.length ? 0 : null,
|
|
74
|
-
routesShapeGeojson,
|
|
75
|
-
version: performance.now(),
|
|
76
|
-
latest: !this.requester.hasPendingRequests,
|
|
77
|
-
requestOptions: opts,
|
|
78
|
-
};
|
|
79
|
-
} catch (error: unknown) {
|
|
80
|
-
if (error instanceof Response) {
|
|
81
|
-
const body = await error.json();
|
|
82
|
-
|
|
83
|
-
if (error.status === 401) {
|
|
84
|
-
throw new UnauthorizedError(body);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
throw new ValhallaAPIError(
|
|
88
|
-
body.error || body.status_message || 'Valhalla returned an error',
|
|
89
|
-
error.status,
|
|
90
|
-
body
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
hasPendingRequests(): boolean {
|
|
99
|
-
return this.requester.hasPendingRequests;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
abortAllRequests(): void {
|
|
103
|
-
this.requester.abortAllRequests();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function summarizeRoute(
|
|
108
|
-
trip: ValhallaRawTrip,
|
|
109
|
-
routeId: number,
|
|
110
|
-
shapeFormat: Options['shapeFormat'],
|
|
111
|
-
): ValhallaRouteSummary {
|
|
112
|
-
const now = new Date();
|
|
113
|
-
const precision = shapeFormat === 'polyline5' ? 5 : 6;
|
|
114
|
-
const legPaths = trip.legs.map((leg) => decodePolyline(leg.shape, precision));
|
|
115
|
-
const path = legPaths.flatMap((legPath, index) => (index ? legPath.slice(1) : legPath));
|
|
116
|
-
const shapeFeatures: Feature<LineString, ShapeProperties>[] = legPaths
|
|
117
|
-
.filter((legPath) => legPath.length > 1)
|
|
118
|
-
.map((legPath, waypoint) =>
|
|
119
|
-
lineString(
|
|
120
|
-
legPath.map(([lat, lon]) => [lon, lat]),
|
|
121
|
-
{ waypoint, routeId },
|
|
122
|
-
),
|
|
123
|
-
);
|
|
124
|
-
const summary = trip.summary ?? {};
|
|
125
|
-
const duration =
|
|
126
|
-
summary.time ?? trip.legs.reduce((total, leg) => total + (leg.summary?.time ?? 0), 0);
|
|
127
|
-
const distance =
|
|
128
|
-
summary.length ?? trip.legs.reduce((total, leg) => total + (leg.summary?.length ?? 0), 0);
|
|
129
|
-
const locations = trip.locations.map(({ lat, lon }) => ({ lat, lng: lon }));
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
id: routeId,
|
|
133
|
-
durationTime: duration,
|
|
134
|
-
distance,
|
|
135
|
-
arriveTime: new Date(now.getTime() + duration * 1000),
|
|
136
|
-
departureTime: now,
|
|
137
|
-
path,
|
|
138
|
-
waypoints: locations,
|
|
139
|
-
shape: featureCollection(shapeFeatures),
|
|
140
|
-
rawRoute: trip,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function decodePolyline(encoded: string, precision: number): Array<[number, number]> {
|
|
145
|
-
const coordinates: Array<[number, number]> = [];
|
|
146
|
-
const factor = 10 ** precision;
|
|
147
|
-
let index = 0;
|
|
148
|
-
let latitude = 0;
|
|
149
|
-
let longitude = 0;
|
|
150
|
-
|
|
151
|
-
while (index < encoded.length) {
|
|
152
|
-
const latitudeResult = decodeValue(encoded, index);
|
|
153
|
-
index = latitudeResult.index;
|
|
154
|
-
latitude += latitudeResult.value;
|
|
155
|
-
const longitudeResult = decodeValue(encoded, index);
|
|
156
|
-
index = longitudeResult.index;
|
|
157
|
-
longitude += longitudeResult.value;
|
|
158
|
-
coordinates.push([latitude / factor, longitude / factor]);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return coordinates;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function decodeValue(encoded: string, start: number): { value: number; index: number } {
|
|
165
|
-
let result = 0;
|
|
166
|
-
let shift = 0;
|
|
167
|
-
let index = start;
|
|
168
|
-
let byte: number;
|
|
169
|
-
|
|
170
|
-
do {
|
|
171
|
-
if (index >= encoded.length) {
|
|
172
|
-
throw new Error('Invalid Valhalla polyline geometry');
|
|
173
|
-
}
|
|
174
|
-
byte = encoded.charCodeAt(index++) - 63;
|
|
175
|
-
result |= (byte & 0x1f) << shift;
|
|
176
|
-
shift += 5;
|
|
177
|
-
} while (byte >= 0x20);
|
|
178
|
-
|
|
179
|
-
return { value: (result & 1) !== 0 ? ~(result >> 1) : result >> 1, index };
|
|
180
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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": [{ "path": "./tsconfig.lib.json" }, { "path": "./tsconfig.spec.json" }]
|
|
16
|
-
}
|
package/tsconfig.lib.json
DELETED
package/tsconfig.spec.json
DELETED
package/vite.config.mts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
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/valhalla-data-provider',
|
|
11
|
-
plugins: [
|
|
12
|
-
nxViteTsPaths(),
|
|
13
|
-
nxCopyAssetsPlugin(['*.md']),
|
|
14
|
-
dts({
|
|
15
|
-
entryRoot: 'src',
|
|
16
|
-
tsconfigPath: path.join(import.meta.dirname, 'tsconfig.lib.json'),
|
|
17
|
-
pathsToAliases: false,
|
|
18
|
-
}),
|
|
19
|
-
],
|
|
20
|
-
build: {
|
|
21
|
-
outDir: '../../dist/libs/valhalla-data-provider',
|
|
22
|
-
emptyOutDir: true,
|
|
23
|
-
reportCompressedSize: true,
|
|
24
|
-
commonjsOptions: { transformMixedEsModules: true },
|
|
25
|
-
lib: {
|
|
26
|
-
entry: 'src/index.ts',
|
|
27
|
-
name: 'valhalla-data-provider',
|
|
28
|
-
fileName: 'index',
|
|
29
|
-
formats: ['es' as const],
|
|
30
|
-
},
|
|
31
|
-
rolldownOptions: { external: [] },
|
|
32
|
-
},
|
|
33
|
-
test: {
|
|
34
|
-
name: 'valhalla-data-provider',
|
|
35
|
-
watch: false,
|
|
36
|
-
globals: true,
|
|
37
|
-
environment: 'node',
|
|
38
|
-
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
39
|
-
reporters: ['default'],
|
|
40
|
-
},
|
|
41
|
-
}));
|
|
File without changes
|