@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,199 @@
1
+ const { pathType } = require("../../model");
2
+ const status = require('../status');
3
+ const util = require('../util');
4
+
5
+ module.exports = init;
6
+
7
+ const units = {
8
+ km: 'k',
9
+ m: 'm'
10
+ };
11
+ const routeType = {
12
+ '-1': 'shortest',
13
+ 0: 'shortest',
14
+ 1: 'bicycle',
15
+ 2: 'pedestrian',
16
+ 5: 'shortest'
17
+ };
18
+ const tolerance = {
19
+ '-1': 20,
20
+ 0: 20,
21
+ 1: 10,
22
+ 2: 2
23
+ };
24
+
25
+ function prepareWaypoint(p) {
26
+ return {
27
+ latLng: {
28
+ lng: p[0],
29
+ lat: p[1]
30
+ }
31
+ };
32
+ }
33
+
34
+ function addPoint(result, p, i, points) {
35
+ if (i % 2 === 0) {
36
+ result.push([points[i + 1], p]);
37
+ }
38
+ return result;
39
+ }
40
+
41
+ function extractSegment(result, { distance, index, narrative, time }) {
42
+ const { directions: { segments }, maneuverIndexes: indexes, path, unitMultiplier } = result;
43
+ segments.push({
44
+ duration: time,
45
+ distance: Math.round((distance || 0) * unitMultiplier),
46
+ path: indexes && path && path.slice(indexes[index], indexes[index + 1]),
47
+ instructions: narrative
48
+ });
49
+ return result;
50
+ }
51
+
52
+ function extractDirections(result, leg) {
53
+ const { directions: { routes, segments }, legIndexes: indexes, path } = result;
54
+ const route = {
55
+ duration: leg.time || 0,
56
+ distance: Math.round((leg.distance || 0) * result.unitMultiplier)
57
+ };
58
+ if (indexes && path && result.fullPath) {
59
+ route.path = path.slice(indexes[leg.index], indexes[leg.index + 1]);
60
+ }
61
+ if (leg.hasSeasonalClosure) {
62
+ route.seasonal = true;
63
+ }
64
+ if (segments) {
65
+ route.segmentIndex = segments.length;
66
+ }
67
+ routes.push(route);
68
+ if (segments && leg.maneuvers) {
69
+ leg.maneuvers.reduce(extractSegment, result);
70
+ }
71
+ return result;
72
+ }
73
+
74
+ function getStatus(err, response) {
75
+ if (!response) {
76
+ return;
77
+ }
78
+ const { info, route } = response;
79
+ const st = info && info.statuscode;
80
+ if (st === 403 || st === 500) {
81
+ // assume its because we exceeded the limit
82
+ return status.failure;
83
+ }
84
+ if (st === 402 || st > 600) {
85
+ // cannot find route
86
+ return status.empty;
87
+ }
88
+ if (st === 0) {
89
+ if (route) {
90
+ return status.success;
91
+ }
92
+ return status.empty;
93
+ }
94
+ }
95
+
96
+ function init(options) {
97
+
98
+ function getProvider(query) {
99
+ if (query.alternate) {
100
+ return options.name;
101
+ }
102
+ return 'open' + options.name;
103
+ }
104
+
105
+ function getUrl(query) {
106
+ return options[getProvider(query) + '_url'] + '/directions/v2/' +
107
+ (query.alternate && query.points.length <= 2 ? 'alternateroutes' : 'route');
108
+ }
109
+
110
+ function prepareRequest(query) {
111
+ const req = {
112
+ unit: units[query.units] || units.m,
113
+ manmaps: false,
114
+ destinationManeuverDisplay: false,
115
+ doReverseGeocode: false,
116
+ routeType: routeType[query.mode] || routeType[0],
117
+ generalize: tolerance[query.mode]
118
+ };
119
+ if (query.avoidHighways) {
120
+ req.avoids = ['Limited Access'];
121
+ }
122
+ if (query.avoidTolls) {
123
+ req.avoids = req.avoids || [];
124
+ req.avoids.push('Toll Road');
125
+ }
126
+ if (query.begin) {
127
+ req.timeType = 2;
128
+ req.dateType = 0;
129
+ req.isoLocal = query.begin;
130
+ } else if (!query.seasonal) {
131
+ req.disallows = ['Approximate Seasonal Closure'];
132
+ }
133
+ if (!query.turnbyturn) {
134
+ req.narrativeType = 'none';
135
+ }
136
+
137
+ return {
138
+ json: JSON.stringify({
139
+ options: req,
140
+ locations: query.points.map(prepareWaypoint)
141
+ }),
142
+ inFormat: 'json',
143
+ outFormat: 'json',
144
+ key: options.mapquest_key
145
+ };
146
+ }
147
+
148
+ function processResponse(response, query) {
149
+
150
+ if (response && response.info) {
151
+ const { statuscode: st } = response.info;
152
+ if (st === 402 || st > 600) {
153
+ // let it cascade to the next service
154
+ return;
155
+ }
156
+ }
157
+
158
+ const route = response && response.route;
159
+ if (!(route && route.legs && route.legs.length)) {
160
+ // shouldn't happen
161
+ return;
162
+ }
163
+ const directions = {
164
+ query,
165
+ provider: getProvider(query)
166
+ };
167
+ if (route.name) {
168
+ directions.name = route.name;
169
+ }
170
+ directions.routes = [];
171
+ if (query.turnbyturn || query.path === pathType.smooth || query.path === pathType.coarse) {
172
+ directions.segments = [];
173
+ }
174
+ const fullPath = query.path === pathType.full;
175
+ route.legs.reduce(extractDirections, {
176
+ directions,
177
+ unitMultiplier: query.units === 'km' ? util.metersInKm : util.metersInMile,
178
+ maneuverIndexes: route.shape && route.shape.maneuverIndexes,
179
+ legIndexes: route.shape && route.shape.legIndexes,
180
+ path: route.shape && route.shape.shapePoints.reduce(addPoint, []),
181
+ fullPath
182
+ });
183
+ if (fullPath) {
184
+ // path is already prepared - no need to do it again from segments
185
+ directions.pathReady = true;
186
+ }
187
+ return directions;
188
+ }
189
+
190
+ options = util.defaults(options, {
191
+ // MapQuest allows 50 but we code it in URL and may run into URL length limit of approx 2000
192
+ maxPoints: 30,
193
+ url: getUrl,
194
+ status: getStatus,
195
+ prepareRequest,
196
+ processResponse
197
+ });
198
+ return require('..')(options);
199
+ }
@@ -0,0 +1,197 @@
1
+ const { pathType, travelMode } = require("../../model");
2
+ const status = require('../status');
3
+ const util = require('../util');
4
+
5
+ module.exports = init;
6
+
7
+ const profile = {
8
+ '-1': 'driving-car',
9
+ 0: 'driving-car',
10
+ 1: 'cycling-regular',
11
+ 2: 'foot-hiking',
12
+ 5: 'driving-hgv'
13
+ };
14
+ const profileRestrictions = {
15
+ 5: {
16
+ hazmat: true
17
+ }
18
+ };
19
+ const ferryType = 9;
20
+
21
+ function nextFerry(result, points = [-1, -1]) {
22
+ let { waytypes } = result;
23
+ if (!waytypes) {
24
+ return;
25
+ }
26
+ for (; result.way < waytypes.length; result.way += 1) {
27
+ const waytype = waytypes[result.way];
28
+ if (waytype[0] > points[1]) {
29
+ return;
30
+ }
31
+ if (waytype[2] === ferryType && (
32
+ (waytype[0] <= points[0] && waytype[1] >= points[1]) ||
33
+ (waytype[0] > points[0] && waytype[1] < points[1]) ||
34
+ (waytype[0] >= points[0] && waytype[0] <= points[1]) ||
35
+ (waytype[1] >= points[0] && waytype[1] <= points[1])
36
+ )) {
37
+ return waytype;
38
+ }
39
+ }
40
+ }
41
+
42
+ function extractStep(result, { distance, duration, instruction, way_points }) {
43
+ const { directions: { segments, routes }, path } = result;
44
+ const seg = {
45
+ duration: Math.round(duration || 0),
46
+ distance: Math.round(distance || 0),
47
+ path: path && path.slice(way_points[0], way_points[1]),
48
+ instructions: instruction
49
+ };
50
+ segments.push(seg);
51
+ const ferrySeg = nextFerry(result, way_points);
52
+ if (ferrySeg) {
53
+ seg.mode = travelMode.ferry;
54
+ util.last(routes).ferry = true;
55
+ }
56
+ return result;
57
+ }
58
+
59
+ function extractDirections(result, { distance, duration, steps }, i) {
60
+ const { directions: { routes, segments }, path, waypoints } = result;
61
+ const route = {
62
+ duration: Math.round(duration || 0),
63
+ distance: Math.round(distance || 0),
64
+ path: path && path.slice(waypoints[i], waypoints[i + 1] + 1)
65
+ };
66
+ if (!(route.duration || route.distance || (route.path && route.path.length > 1))) {
67
+ route.path = [];
68
+ }
69
+ if (segments) {
70
+ route.segmentIndex = segments.length;
71
+ }
72
+ routes.push(route);
73
+ if (segments && steps) {
74
+ result.way = 0;
75
+ nextFerry(result);
76
+ steps.reduce(extractStep, result);
77
+ if (segments.length === 1) {
78
+ const seg = segments[0];
79
+ if (!(seg.duration || seg.distance || (seg.path && seg.path.length > 1))) {
80
+ seg.path = [];
81
+ }
82
+ }
83
+ if (segments.length && route.path.length) {
84
+ util.last(segments).path.push(util.last(route.path));
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+
90
+ function getStatus(st, response) {
91
+ if (!st && response && response.routes && response.routes.length) {
92
+ return status.success;
93
+ }
94
+ return status.empty;
95
+ }
96
+
97
+ function vehicleSize(query, restrictions) {
98
+ const { mode, vehicle } = query;
99
+ if (!(vehicle && mode === 5)) {
100
+ return restrictions;
101
+ }
102
+ restrictions = Object.assign({}, restrictions, vehicle);
103
+ if (restrictions.axle_load !== undefined) {
104
+ restrictions.axleload = restrictions.axle_load;
105
+ delete restrictions.axle_load;
106
+ }
107
+ return restrictions;
108
+ }
109
+
110
+ function init(options) {
111
+
112
+ function prepareUrl(query) {
113
+ return [
114
+ options.openroute_url,
115
+ profile[query.mode] || profile[0],
116
+ 'json'
117
+ ].join('/');
118
+ }
119
+
120
+ function prepareRequest(query) {
121
+ const req = {
122
+ coordinates: query.points
123
+ };
124
+ if (!query.turnbyturn && query.path !== pathType.smooth && query.path !== pathType.coarse) {
125
+ req.instructions = false;
126
+ } else {
127
+ req.extra_info = ['waytype'];
128
+ }
129
+ const restrictions = vehicleSize(query, profileRestrictions[query.mode]);
130
+ if (restrictions) {
131
+ req.options = {
132
+ profile_params: {
133
+ restrictions
134
+ }
135
+ };
136
+ }
137
+ if ((profile[query.mode] || profile[0]) === 'driving-car') {
138
+ if (query.avoidHighways) {
139
+ req.options = req.options || {
140
+ avoid_features: ['highways']
141
+ };
142
+ }
143
+ if (query.avoidTolls) {
144
+ req.options = req.options || {};
145
+ req.options.avoid_features = req.options.avoid_features || [];
146
+ req.options.avoid_features.push('tollways');
147
+ }
148
+ }
149
+
150
+ return req;
151
+ }
152
+
153
+ function processResponse(response, query) {
154
+
155
+ if (!(response && response.routes && response.routes.length)) {
156
+ // let it cascade to the next service
157
+ return;
158
+ }
159
+
160
+ const directions = {
161
+ query,
162
+ provider: options.name
163
+ };
164
+ const paths = response.routes[0].segments;
165
+ const geometry = response.routes[0].geometry;
166
+ if (paths) {
167
+ directions.routes = [];
168
+ if (query.turnbyturn || query.path === pathType.smooth || query.path === pathType.coarse) {
169
+ directions.segments = [];
170
+ }
171
+ const fullPath = query.path === pathType.full;
172
+ const { extras, way_points: waypoints } = response.routes[0];
173
+ paths.reduce(extractDirections, {
174
+ directions,
175
+ path: util.decode(geometry),
176
+ waypoints,
177
+ waytypes: extras && extras.waytypes && extras.waytypes.values
178
+ });
179
+ if (fullPath) {
180
+ // path is already prepared - no need to do it again from segments
181
+ directions.pathReady = true;
182
+ }
183
+ }
184
+ return directions;
185
+ }
186
+
187
+ options = util.defaults(options, {
188
+ maxPoints: 10, // unknown maximum
189
+ post: true,
190
+ authorization: options.openroute_key,
191
+ url: prepareUrl,
192
+ status: getStatus,
193
+ prepareRequest,
194
+ processResponse
195
+ });
196
+ return require('..')(options);
197
+ }
@@ -0,0 +1,144 @@
1
+ const { pathType } = require("../../model");
2
+ const status = require('../status');
3
+ const util = require('../util');
4
+
5
+ const code2status = {
6
+ Ok: status.success,
7
+ NoRoute: status.empty,
8
+ NoSegment: status.empty
9
+ };
10
+ const RADIUS = 1000; // search radius for nearby roads
11
+
12
+ module.exports = init;
13
+
14
+ function convertPlace(wpt) {
15
+ return wpt.name;
16
+ }
17
+
18
+ function convertStep(step) {
19
+ return {
20
+ duration: step.duration,
21
+ distance: step.distance,
22
+ path: util.decode(step.geometry),
23
+ instructions: step.maneuver.type + ' ' + step.maneuver.modifier
24
+ };
25
+ }
26
+
27
+ function convertPath(path, seg) {
28
+ return path.concat(seg.path.slice(1));
29
+ }
30
+
31
+ function convertLeg({ distance, duration, steps }) {
32
+ const r = {
33
+ duration,
34
+ distance,
35
+ segments: steps.map(convertStep)
36
+ };
37
+
38
+ r.path = r.segments.reduce(convertPath, []);
39
+
40
+ return r;
41
+ }
42
+
43
+ function convertRoute({ legs }) {
44
+ return legs.map(convertLeg);
45
+ }
46
+
47
+ // response codes: http://project-osrm.org/docs/v5.5.2/api/#responses
48
+ function getStatus(err, response) {
49
+ const code = response && response.code;
50
+ if (!response) {
51
+ return;
52
+ }
53
+ return code2status[code] || status.error;
54
+ }
55
+
56
+ function point2radius() {
57
+ return RADIUS;
58
+ }
59
+
60
+ function prepareRequest(query) {
61
+ return {
62
+ alternatives: Boolean(query.alternate),
63
+ steps: true, // always ask for steps since legs do not have overview
64
+ overview: false, // we'll get this from steps
65
+ radiuses: query.points.map(point2radius).join(';')
66
+ };
67
+ }
68
+
69
+ const profile = {
70
+ '-1': 'driving', // same as car
71
+ 0: 'car',
72
+ 1: 'bicycle',
73
+ 2: 'foot',
74
+ 5: 'driving'
75
+ };
76
+
77
+ function prepareUrl(baseUrl, query) {
78
+
79
+ function coords2string(c) {
80
+ return c[0].toFixed(5) + ',' + c[1].toFixed(5);
81
+ }
82
+
83
+
84
+ const path = query.points
85
+ .map(coords2string)
86
+ .join(';');
87
+
88
+ return [
89
+ baseUrl,
90
+ 'route/v1',
91
+ profile[query.mode],
92
+ path
93
+ ].join('/');
94
+ }
95
+
96
+ function init(options) {
97
+
98
+ function processResponse(response, query) {
99
+ const directions = {
100
+ query,
101
+ provider: options.name
102
+ };
103
+
104
+ if (response) {
105
+ if (response.waypoints) {
106
+ directions.places = response.waypoints.map(convertPlace);
107
+ }
108
+
109
+ if (response.routes) {
110
+ directions.routes = convertRoute(response.routes[0]);
111
+
112
+ if (query.turnbyturn || query.path === pathType.smooth || query.path === pathType.coarse) {
113
+ directions.segments = [];
114
+ // copy segments from route to its own table
115
+ directions.routes.forEach(function (route) {
116
+ route.segmentIndex = directions.segments.length;
117
+ directions.segments = directions.segments.concat(route.segments);
118
+ delete route.segments;
119
+ });
120
+ } else {
121
+ // delete segments
122
+ directions.routes.forEach(function (route) {
123
+ delete route.segments;
124
+ });
125
+ }
126
+
127
+ if (query.path === pathType.full) {
128
+ // path is already prepared - no need to do it again from segments
129
+ directions.pathReady = true;
130
+ }
131
+ }
132
+ }
133
+ return directions;
134
+ }
135
+
136
+ options = util.defaults(options, {
137
+ maxPoints: 20, // max 20 points for automobile and 50 for bicycle and pedestrian
138
+ url: prepareUrl.bind(null, options.osrm_url || 'https://router.project-osrm.org'),
139
+ status: getStatus,
140
+ prepareRequest,
141
+ processResponse
142
+ });
143
+ return require('..')(options);
144
+ }
@@ -0,0 +1,79 @@
1
+ const util = require('./util');
2
+
3
+ module.exports = partition;
4
+
5
+ function distanceSquare(p1, p2) {
6
+ return Math.pow((p1[0] - p2[0]), 2) + Math.pow((p1[1] - p2[1]), 2);
7
+ }
8
+
9
+ function pointOnLine(point, p1, p2) {
10
+ const d = distanceSquare(p1, p2);
11
+ let t;
12
+ let pol;
13
+ if (!d) {
14
+ pol = p1;
15
+ } else {
16
+ t = ((point[0] - p1[0]) * (p2[0] - p1[0]) + (point[1] - p1[1]) * (p2[1] - p1[1])) / d;
17
+ if (t < 0) {
18
+ pol = p1;
19
+ } else if (t > 1) {
20
+ pol = p2;
21
+ } else {
22
+ pol = [p1[0] + t * (p2[0] - p1[0]), p1[1] + t * (p2[1] - p1[1])];
23
+ }
24
+ }
25
+ return pol;
26
+ }
27
+
28
+ function minDistance(res, p, i, path) {
29
+ let dist = distanceSquare(res.point, p);
30
+ if (dist < res.dist || (dist < res.minDistance && res.pol)) {
31
+ res.dist = dist;
32
+ res.idx = i;
33
+ res.p = p;
34
+ res.pol = undefined;
35
+ }
36
+ if (i > 0) {
37
+ p = pointOnLine(res.point, path[i - 1], p);
38
+ dist = distanceSquare(res.point, p);
39
+ if (dist < res.dist && (res.dist >= res.minDistance || res.pol)) {
40
+ res.dist = dist;
41
+ res.idx = i - 1;
42
+ res.p = p;
43
+ res.pol = true;
44
+ }
45
+ }
46
+ return res;
47
+ }
48
+
49
+ function findPointOnPath(point, path) {
50
+ let result;
51
+ if (point && path) {
52
+ result = path.reduce(minDistance, {
53
+ dist: Number.MAX_VALUE,
54
+ point,
55
+ minDistance: 0.5
56
+ });
57
+ if (result.idx !== undefined) {
58
+ return result;
59
+ }
60
+ }
61
+ }
62
+
63
+ function cut(prev, next) {
64
+ const segments = prev.path.segments;
65
+ const point = findPointOnPath(segments[next.segmentIndex].path[0], prev.path);
66
+ if (point) {
67
+ next.path = prev.path.slice(point.idx + 1);
68
+ prev.path = prev.path.slice(0, point.idx + 1);
69
+ next.path.segments = segments;
70
+ }
71
+ }
72
+
73
+ // divide path into routes
74
+ function partition(path, routes, segments) {
75
+ path.segments = segments;
76
+ routes[0].path = path;
77
+ routes.reduce(cut);
78
+ delete util.last(routes).path.segments;
79
+ }
@@ -0,0 +1,96 @@
1
+ const pathType = require("../model").pathType;
2
+ const util = require('./util');
3
+
4
+ module.exports = init;
5
+
6
+ function init(options) {
7
+ options = options || {};
8
+
9
+ const algorithm = options.algorithm || require("vis-why");
10
+ const endPoints = options.endPoints || 25; // how many points keep at ends
11
+ const maxPoints = options.pathPoints || 100; // maximum number of points kept per path
12
+ const ed = maxPoints / endPoints;
13
+
14
+ function simplifyRoute(result, route) {
15
+ let i;
16
+ let seg;
17
+ let first = [];
18
+ let last = [];
19
+ const path = [];
20
+ const type = result.type;
21
+ const segments = result.segments;
22
+ let endDistance = result.endDistance;
23
+
24
+ if (endDistance > route.distance / ed) {
25
+ endDistance = 0;
26
+ }
27
+ last.distance = 0;
28
+ if (endDistance) {
29
+ for (i = result.segmentIndex - 1; i >= route.segmentIndex; i -= 1) {
30
+ seg = segments[i];
31
+ last.distance += seg.distance;
32
+ if (last.distance >= endDistance) {
33
+ last.split = util.indexAt(seg.path, last.distance - endDistance);
34
+ Array.prototype.unshift.apply(last, seg.path.slice(last.split));
35
+ break;
36
+ }
37
+ result.segmentIndex -= 1;
38
+ Array.prototype.unshift.apply(last, seg.path);
39
+ }
40
+ }
41
+ first.distance = 0;
42
+ i = route.segmentIndex;
43
+ if (endDistance) {
44
+ for (; i < result.segmentIndex; i += 1) {
45
+ seg = segments[i];
46
+ first.distance += seg.distance;
47
+ if (first.distance >= endDistance) {
48
+ first.split = util.indexAt(seg.path, endDistance - first.distance + seg.distance);
49
+ util.concat(first, seg.path.slice(0, first.split));
50
+ break;
51
+ }
52
+ util.concat(first, seg.path);
53
+ }
54
+ }
55
+ if (first.split) {
56
+ util.concat(path, segments[i].path.slice(first.split,
57
+ (i === result.segmentIndex - 1 && last.split) ? last.split : undefined));
58
+ i += 1;
59
+ }
60
+ for (; i < (last.split ? result.segmentIndex - 1 : result.segmentIndex); i += 1) {
61
+ util.concat(path, segments[i].path);
62
+ }
63
+ if (last.split && i < result.segmentIndex) {
64
+ util.concat(path, segments[i].path.slice(0, last.split));
65
+ }
66
+ result.segmentIndex = route.segmentIndex;
67
+ if (type === pathType.full || (first.length + path.length + last.length) <= maxPoints) {
68
+ if (endDistance) {
69
+ route.path = util.concat(util.concat(first, path), last);
70
+ } else {
71
+ route.path = path;
72
+ }
73
+ } else if (endDistance) {
74
+ first = algorithm(first, endPoints);
75
+ last = algorithm(last, endPoints);
76
+ route.path = util.concat(util.concat(first, algorithm(path, maxPoints - first.length - last.length)), last);
77
+ } else {
78
+ route.path = algorithm(path, maxPoints);
79
+ }
80
+ return result;
81
+ }
82
+
83
+ function simplify(type, endDistance, routes, segments) {
84
+ if (type === pathType.none) {
85
+ return;
86
+ }
87
+ routes.reduceRight(simplifyRoute, {
88
+ type,
89
+ segments,
90
+ segmentIndex: segments.length,
91
+ endDistance: endDistance || 2000
92
+ });
93
+ }
94
+
95
+ return simplify;
96
+ }