@furkot/directions 1.5.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 +34 -0
- package/index.js +1 -0
- package/lib/directions.js +137 -0
- package/lib/model.js +64 -0
- package/lib/service/graphhopper/index.js +164 -0
- package/lib/service/index.js +228 -0
- package/lib/service/mapquest/index.js +199 -0
- package/lib/service/openroute/index.js +197 -0
- package/lib/service/osrm/index.js +144 -0
- package/lib/service/partition.js +79 -0
- package/lib/service/simplify.js +96 -0
- package/lib/service/status.js +6 -0
- package/lib/service/util.js +101 -0
- package/lib/service/valhalla/index.js +230 -0
- package/package.json +42 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const LatLon = require('geodesy/latlon-spherical');
|
|
2
|
+
const polyline = require('@pirxpilot/google-polyline');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
concat,
|
|
6
|
+
decode,
|
|
7
|
+
defaults,
|
|
8
|
+
distance,
|
|
9
|
+
indexAt,
|
|
10
|
+
isFuture,
|
|
11
|
+
join,
|
|
12
|
+
last,
|
|
13
|
+
metersInKm: 1000,
|
|
14
|
+
metersInMile: 1609.34,
|
|
15
|
+
split2object,
|
|
16
|
+
splitPoints
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function concat(result, path) {
|
|
20
|
+
if (path) {
|
|
21
|
+
Array.prototype.push.apply(result, path);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function decode(poly, factor) {
|
|
27
|
+
return poly && polyline.decode(poly, factor);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function defaults(obj, source) {
|
|
31
|
+
return Object.assign({}, source, obj);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toLatLon(p) {
|
|
35
|
+
return new LatLon(p[1], p[0]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function distance(p1, p2) {
|
|
39
|
+
return toLatLon(p1).distanceTo(toLatLon(p2));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function indexAt(path, distance) {
|
|
43
|
+
let index = 1;
|
|
44
|
+
let p1 = toLatLon(path[0]);
|
|
45
|
+
let d = 0;
|
|
46
|
+
let p2;
|
|
47
|
+
while (index < path.length) {
|
|
48
|
+
p2 = toLatLon(path[index]);
|
|
49
|
+
d += p1.distanceTo(p2);
|
|
50
|
+
if (d > distance) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
p1 = p2;
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
return index;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isFuture(time) {
|
|
60
|
+
time = time && Date.parse(time);
|
|
61
|
+
return time && time >= Date.now();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// like normal join but with optional filter fn
|
|
65
|
+
function join(arr, conn, fn) {
|
|
66
|
+
fn = fn || function (it) { return it; };
|
|
67
|
+
return arr.filter(fn).join(conn);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function last(arr) {
|
|
71
|
+
return arr[arr.length - 1];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function split2object(str, conn, obj) {
|
|
75
|
+
return str.split(conn || '-').reduce(function (result, word) {
|
|
76
|
+
result[word] = word;
|
|
77
|
+
return result;
|
|
78
|
+
}, obj || {});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function splitPoints(query, maxPoints) {
|
|
82
|
+
let i;
|
|
83
|
+
let segments;
|
|
84
|
+
if (!(query.points && query.points.length > 1)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (query.points.length <= maxPoints) {
|
|
88
|
+
return query;
|
|
89
|
+
}
|
|
90
|
+
segments = [];
|
|
91
|
+
for (i = 0; i < query.points.length - 1; i += maxPoints - 1) {
|
|
92
|
+
segments.push(defaults({
|
|
93
|
+
points: query.points.slice(i, i + maxPoints),
|
|
94
|
+
stats: []
|
|
95
|
+
}, query));
|
|
96
|
+
}
|
|
97
|
+
if (last(segments).points.length === 1) {
|
|
98
|
+
last(segments).points.unshift(segments[segments.length - 2].points.pop());
|
|
99
|
+
}
|
|
100
|
+
return segments;
|
|
101
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// https://github.com/valhalla/valhalla-docs
|
|
2
|
+
|
|
3
|
+
const LatLon = require('geodesy/latlon-spherical');
|
|
4
|
+
const { pathType, travelMode } = require("../../model");
|
|
5
|
+
const status = require('../status');
|
|
6
|
+
const util = require('../util');
|
|
7
|
+
|
|
8
|
+
module.exports = init;
|
|
9
|
+
|
|
10
|
+
const units = {
|
|
11
|
+
km: 'kilometers',
|
|
12
|
+
m: 'miles'
|
|
13
|
+
};
|
|
14
|
+
const costing = {
|
|
15
|
+
1: 'bicycle',
|
|
16
|
+
2: 'pedestrian',
|
|
17
|
+
5: 'truck'
|
|
18
|
+
};
|
|
19
|
+
const defaultCosting = 'auto';
|
|
20
|
+
const costingOptions = {
|
|
21
|
+
5: {
|
|
22
|
+
// default restriction for trucks/RVs
|
|
23
|
+
hazmat: true
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const minSpeed = 15 * 1000 / 3600; // bump speed when it is absurdly low
|
|
27
|
+
const turnDistance = 100; // don't adjust speed of turns
|
|
28
|
+
|
|
29
|
+
const maneuver = {
|
|
30
|
+
ferryEnter: 28,
|
|
31
|
+
ferryExit: 29
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function prepareWaypoint(p) {
|
|
35
|
+
// waypoint format is { lat, lon, type } where type is either 'break' (default) or 'through'
|
|
36
|
+
return {
|
|
37
|
+
lon: p[0],
|
|
38
|
+
lat: p[1],
|
|
39
|
+
type: 'break'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function extractSegment(result, {
|
|
44
|
+
type,
|
|
45
|
+
begin_shape_index,
|
|
46
|
+
end_shape_index,
|
|
47
|
+
instruction,
|
|
48
|
+
length,
|
|
49
|
+
time,
|
|
50
|
+
rough,
|
|
51
|
+
travel_mode
|
|
52
|
+
}) {
|
|
53
|
+
const { directions: { segments, routes }, unitMultiplier, path } = result;
|
|
54
|
+
const distance = Math.round((length || 0) * unitMultiplier);
|
|
55
|
+
let duration = time;
|
|
56
|
+
// for some reason Valhalla calculates absurdly low speed on some dirt roads
|
|
57
|
+
if (rough && duration && travel_mode === 'drive' && distance > turnDistance && distance / duration < minSpeed) {
|
|
58
|
+
duration = Math.round(distance / minSpeed);
|
|
59
|
+
}
|
|
60
|
+
result.legDuration += duration;
|
|
61
|
+
const seg = {
|
|
62
|
+
duration,
|
|
63
|
+
distance,
|
|
64
|
+
path: path && path.slice(begin_shape_index, end_shape_index),
|
|
65
|
+
instructions: instruction
|
|
66
|
+
};
|
|
67
|
+
if (type === maneuver.ferryExit) {
|
|
68
|
+
if (result.ferry) {
|
|
69
|
+
delete result.ferry;
|
|
70
|
+
} else if (segments.length) {
|
|
71
|
+
// route started on the ferry
|
|
72
|
+
util.last(segments).mode = travelMode.ferry;
|
|
73
|
+
util.last(routes).ferry = true;
|
|
74
|
+
}
|
|
75
|
+
} else if (type === maneuver.ferryEnter || result.ferry) {
|
|
76
|
+
seg.mode = travelMode.ferry;
|
|
77
|
+
result.ferry = util.last(routes).ferry = true;
|
|
78
|
+
}
|
|
79
|
+
segments.push(seg);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function extractDirections(result, leg) {
|
|
84
|
+
const { directions } = result;
|
|
85
|
+
result.path = util.decode(leg.shape, { factor: 1e6 });
|
|
86
|
+
const route = {
|
|
87
|
+
duration: (leg.summary && leg.summary.time) || 0,
|
|
88
|
+
distance: Math.round(((leg.summary && leg.summary.length) || 0) * result.unitMultiplier)
|
|
89
|
+
};
|
|
90
|
+
if (result.fullPath) {
|
|
91
|
+
route.path = result.path;
|
|
92
|
+
}
|
|
93
|
+
if (directions.segments) {
|
|
94
|
+
route.segmentIndex = directions.segments.length;
|
|
95
|
+
}
|
|
96
|
+
directions.routes.push(route);
|
|
97
|
+
if (directions.segments && leg.maneuvers) {
|
|
98
|
+
result.legDuration = 0;
|
|
99
|
+
leg.maneuvers.reduce(extractSegment, result);
|
|
100
|
+
route.duration = result.legDuration;
|
|
101
|
+
if (directions.segments.length) {
|
|
102
|
+
util.last(directions.segments).path.push(util.last(result.path));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function toLatLon(p) {
|
|
109
|
+
return new LatLon(p.lat, p.lon);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function minDistance(locations, units) {
|
|
113
|
+
units = units === 'miles' ? util.metersInMile : util.metersInKm;
|
|
114
|
+
return 0.9 * toLatLon(locations[0]).distanceTo(toLatLon(util.last(locations))) / units;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getStatus(err, response) {
|
|
118
|
+
let st = response && response.status_code;
|
|
119
|
+
if (!response) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// 403 Forbidden, 429 Too Many Requests
|
|
123
|
+
if (st === 403 || st === 429) {
|
|
124
|
+
// we exceeded the limit
|
|
125
|
+
return status.failure;
|
|
126
|
+
}
|
|
127
|
+
if (st === 400) {
|
|
128
|
+
// no route
|
|
129
|
+
return status.empty;
|
|
130
|
+
}
|
|
131
|
+
st = response.trip && response.trip.status;
|
|
132
|
+
if (st === 0) {
|
|
133
|
+
if (response.trip.legs && response.trip.legs.length &&
|
|
134
|
+
// make sure points are not too far from roads
|
|
135
|
+
response.trip.summary.length > minDistance(response.trip.locations, response.trip.units)) {
|
|
136
|
+
return status.success;
|
|
137
|
+
}
|
|
138
|
+
delete response.trip.legs;
|
|
139
|
+
return status.empty;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function vehicleSize(query, options) {
|
|
144
|
+
const { mode, vehicle } = query;
|
|
145
|
+
if (!(vehicle && mode === 5)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
Object.assign(options, vehicle);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function init(options) {
|
|
152
|
+
|
|
153
|
+
function prepareRequest(query) {
|
|
154
|
+
let req = {
|
|
155
|
+
locations: query.points.map(prepareWaypoint),
|
|
156
|
+
costing: costing[query.mode] || defaultCosting,
|
|
157
|
+
costing_options: {},
|
|
158
|
+
directions_options: {
|
|
159
|
+
units: units[query.units] || units.m
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
req.costing_options[req.costing] = Object.assign({}, costingOptions[query.mode]);
|
|
163
|
+
if (query.avoidTolls) {
|
|
164
|
+
req.costing_options[req.costing].toll_booth_penalty = 1000.0;
|
|
165
|
+
req.costing_options[req.costing].use_tolls = 0;
|
|
166
|
+
} else {
|
|
167
|
+
req.costing_options[req.costing].use_tolls = 1;
|
|
168
|
+
}
|
|
169
|
+
if (query.avoidHighways) {
|
|
170
|
+
req.costing_options[req.costing].use_highways = 0;
|
|
171
|
+
} else {
|
|
172
|
+
req.costing_options[req.costing].use_highways = 1;
|
|
173
|
+
}
|
|
174
|
+
vehicleSize(query, req.costing_options[req.costing]);
|
|
175
|
+
|
|
176
|
+
if (query.begin) {
|
|
177
|
+
req.date_time = {
|
|
178
|
+
type: 1,
|
|
179
|
+
value: query.begin
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
req = {
|
|
184
|
+
json: JSON.stringify(req)
|
|
185
|
+
};
|
|
186
|
+
if (options.valhalla_key) {
|
|
187
|
+
req.api_key = options.valhalla_key;
|
|
188
|
+
}
|
|
189
|
+
return req;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function processResponse(response, query) {
|
|
193
|
+
const trip = response && response.trip;
|
|
194
|
+
if (trip && trip.legs && trip.legs.length) {
|
|
195
|
+
const directions = {
|
|
196
|
+
query,
|
|
197
|
+
provider: options.name,
|
|
198
|
+
routes: []
|
|
199
|
+
};
|
|
200
|
+
if (query.turnbyturn || query.path === pathType.smooth || query.path === pathType.coarse) {
|
|
201
|
+
directions.segments = [];
|
|
202
|
+
}
|
|
203
|
+
const fullPath = query.path === pathType.full;
|
|
204
|
+
trip.legs.reduce(extractDirections, {
|
|
205
|
+
directions,
|
|
206
|
+
unitMultiplier: query.units !== 'km' ? util.metersInMile : util.metersInKm,
|
|
207
|
+
fullPath
|
|
208
|
+
});
|
|
209
|
+
if (fullPath) {
|
|
210
|
+
// path is already prepared - no need to do it again from segments
|
|
211
|
+
directions.pathReady = true;
|
|
212
|
+
}
|
|
213
|
+
return directions;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
options = util.defaults(options, {
|
|
218
|
+
// Mapzen says 2 request per second: https://mapzen.com/documentation/overview/#mapzen-turn-by-turn
|
|
219
|
+
// but if we get below 560 millis it starts returning 429 (Too Many Requests)
|
|
220
|
+
interval: 560,
|
|
221
|
+
// max 20 points for automobile and 50 for bicycle and pedestrian
|
|
222
|
+
maxPoints: options.valhalla_max_points || 2,
|
|
223
|
+
url: options.valhalla_url,
|
|
224
|
+
body: true,
|
|
225
|
+
status: getStatus,
|
|
226
|
+
prepareRequest,
|
|
227
|
+
processResponse
|
|
228
|
+
});
|
|
229
|
+
return require('..')(options);
|
|
230
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@furkot/directions",
|
|
3
|
+
"version": "1.5.1",
|
|
4
|
+
"description": "Directions service for Furkot",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Damian Krzeminski",
|
|
7
|
+
"email": "pirxpilot@furkot.com",
|
|
8
|
+
"url": "https://pirxpilot.me"
|
|
9
|
+
},
|
|
10
|
+
"repository": "furkot/directions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"furkot-directions",
|
|
14
|
+
"furkot",
|
|
15
|
+
"directions"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@pirxpilot/google-polyline": "^3.0.0",
|
|
19
|
+
"debug": "~2 || ~3 || ~4",
|
|
20
|
+
"fetchagent": "~2",
|
|
21
|
+
"geodesy": "^1.1.1",
|
|
22
|
+
"limiter-component": "^1.0.0",
|
|
23
|
+
"run-series": "^1.1.4",
|
|
24
|
+
"run-waterfall-until": "~1",
|
|
25
|
+
"vis-why": "^1.2.2"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"jshint": "~2",
|
|
29
|
+
"lodash.clonedeep": "^4.5.0",
|
|
30
|
+
"lodash.clonedeepwith": "^4.5.0",
|
|
31
|
+
"mocha": "~10",
|
|
32
|
+
"should": "~13",
|
|
33
|
+
"sinon": "~14"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "make check"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"lib"
|
|
41
|
+
]
|
|
42
|
+
}
|