@furkot/directions 2.0.2 → 2.1.0

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/lib/model.js CHANGED
@@ -12,7 +12,7 @@ const travelMode = {
12
12
  car: 0,
13
13
  bicycle: 1,
14
14
  walk: 2,
15
- other: 3,
15
+ rv: 5,
16
16
  ferry: 6
17
17
  };
18
18
 
@@ -1,5 +1,9 @@
1
- const { pathType } = require("../../model");
1
+ // https://docs.graphhopper.com/#tag/Routing-API
2
+ // https://github.com/boldtrn/kurviger-api-documentation
3
+
4
+ const { pathType, travelMode } = require("../../model");
2
5
  const status = require('../status');
6
+ const tagRoute = require('../tag-route');
3
7
  const util = require('../util');
4
8
 
5
9
  module.exports = init;
@@ -18,24 +22,48 @@ const weighting = {
18
22
  2: 'curvaturefastest'
19
23
  };
20
24
 
21
- function prepareWaypoint(qs, p) {
22
- // waypoint format is lat,lon
23
- qs.push('point=' + encodeURIComponent(p[1] + ',' + p[0]));
24
- return qs;
25
- }
25
+ const ferryTypes = {
26
+ ferry: true
27
+ };
28
+ const roughTypes = {
29
+ compacted: true,
30
+ dirt: true,
31
+ fine_gravel: true,
32
+ grass: true,
33
+ gravel: true,
34
+ ground: true,
35
+ sand: true,
36
+ unpaved: true
37
+ };
38
+ const tollTypes = {
39
+ all: true
40
+ };
26
41
 
27
42
  function extractSegment(result, { distance, interval, text, time }) {
28
43
  const { directions: { segments }, path } = result;
44
+ const [from, to] = interval;
29
45
  segments.push({
30
46
  duration: Math.round((time || 0) / 1000),
31
47
  distance: Math.round(distance || 0),
32
- path: path && path.slice(interval[0], interval[1]),
48
+ path: path?.slice(from, to + 1),
33
49
  instructions: text
34
50
  });
35
51
  return result;
36
52
  }
37
53
 
38
- function extractDirections(result, { distance, instructions, points, time }) {
54
+ function setFerryMode(seg) {
55
+ seg.mode = travelMode.ferry;
56
+ }
57
+
58
+ function setRough(seg) {
59
+ seg.rough = true;
60
+ }
61
+
62
+ function setTolls(seg) {
63
+ seg.tolls = true;
64
+ }
65
+
66
+ function extractDirections(result, { details, distance, instructions, points, time }) {
39
67
  const { directions: { routes, segments }, fullPath } = result;
40
68
  result.path = util.decode(points);
41
69
  const route = {
@@ -54,6 +82,30 @@ function extractDirections(result, { distance, instructions, points, time }) {
54
82
  if (segments.length) {
55
83
  util.last(segments).path.push(util.last(result.path));
56
84
  }
85
+ const ferry = tagRoute(details?.road_environment, {
86
+ segments,
87
+ types: ferryTypes,
88
+ updateSegment: setFerryMode
89
+ });
90
+ if (ferry?.foundType) {
91
+ route.ferry = true;
92
+ }
93
+ const rough = tagRoute(details?.surface, {
94
+ segments,
95
+ types: roughTypes,
96
+ updateSegment: setRough
97
+ });
98
+ if (rough?.foundType) {
99
+ route.rough = true;
100
+ }
101
+ const tolls = tagRoute(details?.toll, {
102
+ segments,
103
+ types: tollTypes,
104
+ updateSegment: setTolls
105
+ });
106
+ if (tolls?.foundType) {
107
+ route.tolls = true;
108
+ }
57
109
  }
58
110
  return result;
59
111
  }
@@ -78,48 +130,92 @@ function getStatus(st, response) {
78
130
  }
79
131
  }
80
132
 
133
+ function vehicleSize(query, options) {
134
+ const { mode, vehicle } = query;
135
+ if (!(vehicle && mode === travelMode.rv)) {
136
+ return;
137
+ }
138
+ const { hazmat } = vehicle;
139
+ if (hazmat) {
140
+ options.push({
141
+ if: 'hazmat == NO',
142
+ multiply_by: '0.0'
143
+ });
144
+ }
145
+ ['height', 'width', 'length', 'weight', 'axle_load'].forEach(p => {
146
+ if (vehicle[p]) {
147
+ options.push({
148
+ if: `max_${p} < ${vehicle[p]}`,
149
+ multiply_by: '0.0'
150
+ });
151
+ }
152
+ });
153
+ }
154
+
81
155
  function init(options) {
82
156
 
83
- function prepareUrl(url, { avoidHighways, avoidTolls, avoidUnpaved, curvy, mode, path, points, turnbyturn }) {
157
+ function prepareUrl(url) {
158
+ return `${url}?key=${options.graphhopper_key}`;
159
+ }
160
+
161
+ function prepareRequest(query) {
162
+ const { avoidFerry, avoidHighways, avoidTolls, avoidUnpaved, curvy, mode, path, points, turnbyturn } = query;
163
+ if (options.parameters.max_curvy_distance && curvy && mode === -1 && points.length === 2 &&
164
+ util.distance(points[0], points[1]) > options.parameters.max_curvy_distance) {
165
+ return;
166
+ }
84
167
  let req = {
85
168
  vehicle: vehicle[mode] || vehicle[0],
86
- key: options.graphhopper_key
169
+ points,
170
+ details: ['road_environment', 'toll']
87
171
  };
172
+ if (!turnbyturn && path !== pathType.smooth && path !== pathType.coarse) {
173
+ req.instructions = false;
174
+ }
88
175
  if (curvy && mode === -1) {
89
176
  req.vehicle = 'motorcycle.kurviger.de';
90
177
  req.weighting = weighting[curvy];
91
178
  if (options.parameters.app_type) {
92
179
  req['app.type'] = options.parameters.app_type;
93
180
  }
181
+ if (avoidHighways) {
182
+ req.avoid_motorways = true;
183
+ }
184
+ if (avoidTolls) {
185
+ req.avoid_toll_roads = true;
186
+ }
187
+ if (avoidFerry) {
188
+ req.avoid_ferries = true;
189
+ }
94
190
  if (avoidUnpaved) {
95
191
  req.avoid_unpaved_roads = true;
96
192
  }
97
193
  }
98
- if (!turnbyturn && path !== pathType.smooth && path !== pathType.coarse) {
99
- req.instructions = false;
100
- }
101
- if (options.parameters.flexible) {
102
- if (avoidTolls) {
103
- req['ch.disable'] = true;
104
- req.avoid = ['toll'];
105
- }
106
- if (avoidHighways) {
107
- req['ch.disable'] = true;
108
- req.avoid = req.avoid || [];
109
- req.avoid.push('motorway');
194
+ else {
195
+ req.details.push('surface');
196
+ req['ch.disable'] = true;
197
+ req.custom_model = {
198
+ priority: [{
199
+ if: 'road_class == MOTORWAY',
200
+ multiply_by: avoidHighways ? '0.1' : '1.0'
201
+ }, {
202
+ if: 'toll == ALL',
203
+ multiply_by: avoidTolls ? '0.1' : '1.0'
204
+ }, {
205
+ if: 'road_environment == FERRY',
206
+ multiply_by: avoidFerry ? '0.1' : '1.0'
207
+ }]
208
+ };
209
+ if (avoidUnpaved) {
210
+ req.custom_model.priority.push({
211
+ if: 'surface == UNPAVED',
212
+ multiply_by: '0.0'
213
+ });
110
214
  }
215
+ vehicleSize(query, req.custom_model.priority);
111
216
  }
112
217
 
113
- req = points.reduce(prepareWaypoint, Object.keys(req).map(function (name) {
114
- return name + '=' + encodeURIComponent(req[name]);
115
- }));
116
-
117
- return url + '?' + req.join('&');
118
- }
119
-
120
- function prepareRequest({ mode, points, curvy }) {
121
- return !(options.parameters.max_curvy_distance && curvy && mode === -1 && points.length === 2 &&
122
- util.distance(points[0], points[1]) > options.parameters.max_curvy_distance);
218
+ return req;
123
219
  }
124
220
 
125
221
  function processResponse(response, query) {
@@ -154,6 +250,7 @@ function init(options) {
154
250
 
155
251
  options = util.defaults(options, {
156
252
  maxPoints: options.graphhopper_max_points || 5, // max 5 points for free and 30-150 for paid plan
253
+ post: true,
157
254
  url: prepareUrl.bind(undefined, options.graphhopper_url),
158
255
  status: getStatus,
159
256
  prepareRequest,
@@ -3,6 +3,7 @@
3
3
  const LatLon = require('geodesy/latlon-spherical');
4
4
  const { pathType, travelMode } = require("../../model");
5
5
  const status = require('../status');
6
+ const tagRoute = require('../tag-route');
6
7
  const util = require('../util');
7
8
 
8
9
  module.exports = init;
@@ -19,55 +20,63 @@ const profileRestrictions = {
19
20
  hazmat: true
20
21
  }
21
22
  };
22
- const ferryType = 9;
23
+ const avoidFeatures = {
24
+ avoidHighways: 'highways',
25
+ avoidTolls: 'tollways',
26
+ avoidFerry: 'ferries'
27
+ };
28
+ const ferryTypes = {
29
+ '9': true
30
+ };
31
+ const roughTypes = {
32
+ '2': true, // Unpaved
33
+ '8': true, // Compacted Gravel
34
+ '9': true, // Fine Gravel
35
+ '10': true, // Gravel
36
+ '11': true, // Dirt
37
+ '12': true, // Ground
38
+ '15': true, // Sand
39
+ '17': true, // Grass
40
+ '18': true // Grass Paver
41
+ };
42
+ const tollTypes = {
43
+ '1': true
44
+ };
23
45
  const maxRoundabout = 12 * 60 * 60; // 12 hours maximum for route that is 10 times longer than direct distance
24
46
 
25
- function nextFerry(result, points = [-1, -1]) {
26
- let { waytypes } = result;
27
- if (!waytypes) {
28
- return;
29
- }
30
- for (; result.way < waytypes.length; result.way += 1) {
31
- const waytype = waytypes[result.way];
32
- if (waytype[0] > points[1]) {
33
- return;
34
- }
35
- if (waytype[2] === ferryType && (
36
- (waytype[0] <= points[0] && waytype[1] >= points[1]) ||
37
- (waytype[0] > points[0] && waytype[1] < points[1]) ||
38
- (waytype[0] >= points[0] && waytype[0] <= points[1]) ||
39
- (waytype[1] >= points[0] && waytype[1] <= points[1])
40
- )) {
41
- return waytype;
42
- }
43
- }
44
- }
45
47
 
46
48
  function extractStep(result, { distance, duration, instruction, way_points }) {
47
- const { directions: { segments, routes }, path } = result;
49
+ const { directions: { segments }, path } = result;
48
50
  const seg = {
49
51
  duration: Math.round(duration || 0),
50
52
  distance: Math.round(distance || 0),
51
- path: path && path.slice(way_points[0], way_points[1]),
53
+ path: path?.slice(way_points[0], way_points[1] + 1),
52
54
  instructions: instruction
53
55
  };
54
56
  segments.push(seg);
55
- const ferrySeg = nextFerry(result, way_points);
56
- if (ferrySeg) {
57
- seg.mode = travelMode.ferry;
58
- util.last(routes).ferry = true;
59
- }
60
57
  return result;
61
58
  }
62
59
 
60
+ function setFerryMode(seg) {
61
+ seg.mode = travelMode.ferry;
62
+ }
63
+
64
+ function setRough(seg) {
65
+ seg.rough = true;
66
+ }
67
+
68
+ function setTolls(seg) {
69
+ seg.tolls = true;
70
+ }
71
+
63
72
  function extractDirections(result, { distance, duration, steps }, i) {
64
- const { directions: { routes, segments }, path, waypoints } = result;
73
+ const { directions: { routes, segments }, path, surface, waypoints, waytypes, tollways } = result;
65
74
  const route = {
66
75
  duration: Math.round(duration || 0),
67
76
  distance: Math.round(distance || 0),
68
- path: path && path.slice(waypoints[i], waypoints[i + 1] + 1)
77
+ path: path?.slice(waypoints[i], waypoints[i + 1] + 1)
69
78
  };
70
- if (!(route.duration || route.distance || (route.path && route.path.length > 1))) {
79
+ if (!(route.duration || route.distance || (route.path?.length > 1))) {
71
80
  route.path = [];
72
81
  }
73
82
  if (segments) {
@@ -75,18 +84,40 @@ function extractDirections(result, { distance, duration, steps }, i) {
75
84
  }
76
85
  routes.push(route);
77
86
  if (segments && steps) {
78
- result.way = 0;
79
- nextFerry(result);
80
87
  steps.reduce(extractStep, result);
81
88
  if (segments.length === 1) {
82
89
  const seg = segments[0];
83
- if (!(seg.duration || seg.distance || (seg.path && seg.path.length > 1))) {
90
+ if (!(seg.duration || seg.distance || (seg.path?.length > 1))) {
84
91
  seg.path = [];
85
92
  }
86
93
  }
87
94
  if (segments.length && route.path.length) {
88
95
  util.last(segments).path.push(util.last(route.path));
89
96
  }
97
+ const ferry = tagRoute(waytypes, {
98
+ segments,
99
+ types: ferryTypes,
100
+ updateSegment: setFerryMode
101
+ });
102
+ if (ferry?.foundType) {
103
+ route.ferry = true;
104
+ }
105
+ const rough = tagRoute(surface, {
106
+ segments,
107
+ types: roughTypes,
108
+ updateSegment: setRough
109
+ });
110
+ if (rough?.foundType) {
111
+ route.rough = true;
112
+ }
113
+ const tolls = tagRoute(tollways, {
114
+ segments,
115
+ types: tollTypes,
116
+ updateSegment: setTolls
117
+ });
118
+ if (tolls?.foundType) {
119
+ route.tolls = true;
120
+ }
90
121
  }
91
122
  return result;
92
123
  }
@@ -108,7 +139,7 @@ function directDistance(locations) {
108
139
  }
109
140
 
110
141
  function getStatus(st, response) {
111
- if (!st && response && response.routes && response.routes.length) {
142
+ if (!st && response?.routes?.length) {
112
143
  const { distance, duration } = response.routes.reduce((result, { summary}) => {
113
144
  result.distance += summary.distance;
114
145
  result.duration += summary.duration;
@@ -126,7 +157,7 @@ function getStatus(st, response) {
126
157
 
127
158
  function vehicleSize(query, restrictions) {
128
159
  const { mode, vehicle } = query;
129
- if (!(vehicle && mode === 5)) {
160
+ if (!(vehicle && mode === travelMode.rv)) {
130
161
  return restrictions;
131
162
  }
132
163
  restrictions = Object.assign({}, restrictions, vehicle);
@@ -147,14 +178,25 @@ function init(options) {
147
178
  ].join('/');
148
179
  }
149
180
 
181
+ function avoidFeature(result, flag) {
182
+ const { query, req } = result;
183
+ if (query[flag]) {
184
+ req.options = req.options || {};
185
+ req.options.avoid_features = req.options.avoid_features || [];
186
+ req.options.avoid_features.push(avoidFeatures[flag]);
187
+ }
188
+ return result;
189
+ }
190
+
150
191
  function prepareRequest(query) {
151
192
  const req = {
152
- coordinates: query.points
193
+ coordinates: query.points,
194
+ extra_info: ['surface', 'tollways']
153
195
  };
154
196
  if (!query.turnbyturn && query.path !== pathType.smooth && query.path !== pathType.coarse) {
155
197
  req.instructions = false;
156
198
  } else {
157
- req.extra_info = ['waytype'];
199
+ req.extra_info.push('waytype');
158
200
  }
159
201
  const restrictions = vehicleSize(query, profileRestrictions[query.mode]);
160
202
  if (restrictions) {
@@ -164,25 +206,18 @@ function init(options) {
164
206
  }
165
207
  };
166
208
  }
209
+ const param = { query, req };
167
210
  if ((profile[query.mode] || profile[0]) === 'driving-car') {
168
- if (query.avoidHighways) {
169
- req.options = req.options || {
170
- avoid_features: ['highways']
171
- };
172
- }
173
- if (query.avoidTolls) {
174
- req.options = req.options || {};
175
- req.options.avoid_features = req.options.avoid_features || [];
176
- req.options.avoid_features.push('tollways');
177
- }
211
+ ['avoidHighways', 'avoidTolls'].reduce(avoidFeature, param);
178
212
  }
213
+ avoidFeature(param, 'avoidFerry');
179
214
 
180
215
  return req;
181
216
  }
182
217
 
183
218
  function processResponse(response, query) {
184
219
 
185
- if (!(response && response.routes && response.routes.length)) {
220
+ if (!response?.routes?.length) {
186
221
  // let it cascade to the next service
187
222
  return;
188
223
  }
@@ -203,8 +238,10 @@ function init(options) {
203
238
  paths.reduce(extractDirections, {
204
239
  directions,
205
240
  path: util.decode(geometry),
241
+ surface: extras?.surface?.values,
242
+ tollways: extras?.tollways?.values,
206
243
  waypoints,
207
- waytypes: extras && extras.waytypes && extras.waytypes.values
244
+ waytypes: extras?.waytypes?.values
208
245
  });
209
246
  if (fullPath) {
210
247
  // path is already prepared - no need to do it again from segments
@@ -0,0 +1,54 @@
1
+ module.exports = tagRoute;
2
+
3
+ function splitSegment(result, length) {
4
+ const { segments, seg } = result;
5
+ const { distance, duration, path } = segments[seg];
6
+ if (length <= 0 || length + 1 >= path.length) {
7
+ return;
8
+ }
9
+ const factor = length / path.length;
10
+ segments.splice(seg, 0, {
11
+ distance: distance * factor,
12
+ duration: duration * factor,
13
+ path: path?.slice(0, length + 1)
14
+ });
15
+ segments[seg + 1].distance -= segments[seg].distance;
16
+ segments[seg + 1].duration -= segments[seg].duration;
17
+ segments[seg + 1].path = path?.slice(length);
18
+ result.running += length;
19
+ result.seg += 1;
20
+ }
21
+
22
+ function extractRouteType(result, [from, to, type]) {
23
+ const { segments, types, updateSegment } = result;
24
+ if (!types[type]) {
25
+ return result;
26
+ }
27
+ while (result.running + segments[result.seg].path.length - 1 <= from) {
28
+ result.running += segments[result.seg].path.length - 1;
29
+ result.seg += 1;
30
+ }
31
+ const { running } = result;
32
+ splitSegment(result, from - running);
33
+ let { seg } = result;
34
+ splitSegment(result, to - from);
35
+ updateSegment(segments[seg]);
36
+ result.foundType = true;
37
+ if (result.seg === seg) {
38
+ while (result.running + segments[seg].path.length < to) {
39
+ const { length } = segments[seg].path;
40
+ from += length - 1;
41
+ result.running += length - 1;
42
+ result.seg += 1;
43
+ seg = result.seg;
44
+ splitSegment(result, to - from);
45
+ updateSegment(segments[seg]);
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+
51
+ function tagRoute(tagSegments, params) {
52
+ params.seg = params.running = 0;
53
+ return tagSegments?.reduce(extractRouteType, params);
54
+ }
@@ -49,14 +49,21 @@ function extractSegment(result, {
49
49
  length,
50
50
  time,
51
51
  rough,
52
+ toll,
52
53
  travel_mode
53
54
  }) {
54
55
  const { directions: { segments, routes }, unitMultiplier, path } = result;
55
56
  const distance = Math.round((length || 0) * unitMultiplier);
56
57
  let duration = time;
57
- // for some reason Valhalla calculates absurdly low speed on some dirt roads
58
- if (rough && duration && travel_mode === 'drive' && distance > turnDistance && distance / duration < minSpeed) {
59
- duration = Math.round(distance / minSpeed);
58
+ if (rough) {
59
+ util.last(routes).rough = true;
60
+ // for some reason Valhalla calculates absurdly low speed on some dirt roads
61
+ if (duration && travel_mode === 'drive' && distance > turnDistance && distance / duration < minSpeed) {
62
+ duration = Math.round(distance / minSpeed);
63
+ }
64
+ }
65
+ if (toll) {
66
+ util.last(routes).tolls = true;
60
67
  }
61
68
  result.legDuration += duration;
62
69
  const seg = {
@@ -65,6 +72,12 @@ function extractSegment(result, {
65
72
  path: path && path.slice(begin_shape_index, end_shape_index),
66
73
  instructions: instruction
67
74
  };
75
+ if (rough) {
76
+ seg.rough = true;
77
+ }
78
+ if (toll) {
79
+ seg.tolls = true;
80
+ }
68
81
  if (type === maneuver.ferryExit || result.ferry) {
69
82
  if (result.ferry) {
70
83
  delete result.ferry;
@@ -155,7 +168,7 @@ function getStatus(err, response) {
155
168
 
156
169
  function vehicleSize(query, options) {
157
170
  const { mode, vehicle } = query;
158
- if (!(vehicle && mode === 5)) {
171
+ if (!(vehicle && mode === travelMode.rv)) {
159
172
  return;
160
173
  }
161
174
  Object.assign(options, vehicle);
@@ -184,6 +197,14 @@ function init(options) {
184
197
  } else {
185
198
  req.costing_options[req.costing].use_highways = 1.1;
186
199
  }
200
+ if (query.avoidFerry) {
201
+ req.costing_options[req.costing].use_ferry = 0;
202
+ } else {
203
+ req.costing_options[req.costing].use_ferry = 1;
204
+ }
205
+ if (query.avoidUnpaved) {
206
+ req.costing_options[req.costing].exclude_unpaved = true;
207
+ }
187
208
  vehicleSize(query, req.costing_options[req.costing]);
188
209
 
189
210
  if (query.begin) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furkot/directions",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Directions service for Furkot",
5
5
  "author": {
6
6
  "name": "Damian Krzeminski",