@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.
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ success: 'success', // success
3
+ failure: 'failure', // ultimate failure
4
+ error: 'error', // temporary error
5
+ empty: 'empty' // no result
6
+ };
@@ -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
+ }