@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
package/Readme.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[![NPM version][npm-image]][npm-url]
|
|
2
|
+
[![Build Status][build-image]][build-url]
|
|
3
|
+
[![Dependency Status][deps-image]][deps-url]
|
|
4
|
+
|
|
5
|
+
# furkot-directions
|
|
6
|
+
|
|
7
|
+
Directions service for Furkot
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
$ npm install --save furkot-directions
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
var furkotDirections = require('furkot-directions');
|
|
19
|
+
|
|
20
|
+
furkotDirections('Rainbow');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
MIT © [Damian Krzeminski](https://pirxpilot.me)
|
|
26
|
+
|
|
27
|
+
[npm-image]: https://img.shields.io/npm/v/@furkot/directions
|
|
28
|
+
[npm-url]: https://npmjs.org/package/@furkot/directions
|
|
29
|
+
|
|
30
|
+
[build-url]: https://github.com/furkot/directions/actions/workflows/check.yaml
|
|
31
|
+
[build-image]: https://img.shields.io/github/workflow/status/furkot/directions/check
|
|
32
|
+
|
|
33
|
+
[deps-image]: https://img.shields.io/librariesio/release/npm/@furkot/directions
|
|
34
|
+
[deps-url]: https://libraries.io/npm/@furkot%2Fdirections
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./lib/directions');
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const strategy = require('run-waterfall-until');
|
|
2
|
+
const travelMode = require('./model').travelMode;
|
|
3
|
+
const util = require('./service/util');
|
|
4
|
+
|
|
5
|
+
module.exports = furkotDirections;
|
|
6
|
+
|
|
7
|
+
function skip(options, query, result) {
|
|
8
|
+
// some other service already calculated directions
|
|
9
|
+
// or service is disabled
|
|
10
|
+
return result || !options.enable(query, result);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// query cascades through services until one produces a result
|
|
14
|
+
// 'skip' function for a service is used to determine whether
|
|
15
|
+
// it should be skipped or applied to a given request
|
|
16
|
+
const services = {
|
|
17
|
+
graphhopper: {
|
|
18
|
+
service: require('./service/graphhopper'),
|
|
19
|
+
skip
|
|
20
|
+
},
|
|
21
|
+
mapquest: {
|
|
22
|
+
service: require('./service/mapquest'),
|
|
23
|
+
skip
|
|
24
|
+
},
|
|
25
|
+
openroute: {
|
|
26
|
+
service: require('./service/openroute'),
|
|
27
|
+
skip
|
|
28
|
+
},
|
|
29
|
+
valhalla: {
|
|
30
|
+
service: require('./service/valhalla'),
|
|
31
|
+
skip
|
|
32
|
+
},
|
|
33
|
+
osrm: {
|
|
34
|
+
service: require('./service/osrm'),
|
|
35
|
+
skip(options, query, result) {
|
|
36
|
+
// or asking for walking or biking directions (OSRM doesn't do it well)
|
|
37
|
+
return skip(options, query, result) || (query.mode !== travelMode.car && query.mode !== travelMode.motorcycle);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// default timeout to complete operation
|
|
43
|
+
const defaultTimeout = 20 * 1000;
|
|
44
|
+
|
|
45
|
+
let id = 0;
|
|
46
|
+
|
|
47
|
+
function furkotDirections(options) {
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Asynchronous directions service
|
|
51
|
+
* @param query directions query object
|
|
52
|
+
* @param fn function called with directions
|
|
53
|
+
*/
|
|
54
|
+
function directions(query, fn) {
|
|
55
|
+
if (!query) {
|
|
56
|
+
return fn();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
id += 1;
|
|
60
|
+
|
|
61
|
+
const result = new Array(query.length);
|
|
62
|
+
if (!query.length) {
|
|
63
|
+
return fn(query, result);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const queryId = id;
|
|
67
|
+
let timeoutId = setTimeout(function () {
|
|
68
|
+
timeoutId = undefined;
|
|
69
|
+
// cancel outstanding requests
|
|
70
|
+
options.services.forEach(function (service) {
|
|
71
|
+
service.abort(queryId);
|
|
72
|
+
});
|
|
73
|
+
}, options.timeout);
|
|
74
|
+
|
|
75
|
+
strategy(options.services, queryId, query, result, function (err, queryId, query, result) {
|
|
76
|
+
if (timeoutId) {
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
timeoutId = undefined;
|
|
79
|
+
}
|
|
80
|
+
if (err) {
|
|
81
|
+
return fn();
|
|
82
|
+
}
|
|
83
|
+
// if no results, mark first as empty
|
|
84
|
+
if (result.length > 0 && !result.some(function (r) {
|
|
85
|
+
return r;
|
|
86
|
+
})) {
|
|
87
|
+
result[0] = {
|
|
88
|
+
query: query[0],
|
|
89
|
+
routes: [{
|
|
90
|
+
distance: 0,
|
|
91
|
+
duration: 0
|
|
92
|
+
}]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
fn(query, result);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
options = util.defaults(options, {
|
|
100
|
+
timeout: defaultTimeout,
|
|
101
|
+
order: ['osrm', 'mapquest', 'valhalla', 'graphhopper', 'openroute']
|
|
102
|
+
});
|
|
103
|
+
if (!options.services) {
|
|
104
|
+
options.services = options.order.reduce(function (result, name) {
|
|
105
|
+
const service = services[options[name] || name];
|
|
106
|
+
let defaults;
|
|
107
|
+
if (service && options[(name + '_enable')]) {
|
|
108
|
+
defaults = {
|
|
109
|
+
name,
|
|
110
|
+
limiter: options[(name + '_limiter')],
|
|
111
|
+
enable: options[(name + '_enable')],
|
|
112
|
+
skip: service.skip
|
|
113
|
+
};
|
|
114
|
+
if (options[name]) {
|
|
115
|
+
Object.keys(options).reduce(mapOptions, {
|
|
116
|
+
options,
|
|
117
|
+
name,
|
|
118
|
+
optName: options[name],
|
|
119
|
+
defaults
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
result.push(service.service(util.defaults(defaults, options)));
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}, []);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
directions.options = options;
|
|
129
|
+
return directions;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function mapOptions(result, opt) {
|
|
133
|
+
if (opt.startsWith(result.name)) {
|
|
134
|
+
result.defaults[opt.replace(result.name, result.optName)] = result.options[opt];
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
package/lib/model.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// path simplification constants
|
|
2
|
+
const pathType = {
|
|
3
|
+
none: 'none', // don't include the path in route (default)
|
|
4
|
+
coarse: 'coarse', // include heavily simplified path
|
|
5
|
+
smooth: 'smooth', // include path that is somewhat simplified
|
|
6
|
+
full: 'full' // don't simplify the route path at all
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// travel mode constants
|
|
10
|
+
const travelMode = {
|
|
11
|
+
motorcycle: -1,
|
|
12
|
+
car: 0,
|
|
13
|
+
bicycle: 1,
|
|
14
|
+
walk: 2,
|
|
15
|
+
other: 3,
|
|
16
|
+
ferry: 6
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// template for directions query object
|
|
20
|
+
const directionsQuery = [{ // array of legs each for consecutive series of points
|
|
21
|
+
mode: travelMode.car, // numeric value of travel mode
|
|
22
|
+
avoidHighways: false, // true to avoid highways
|
|
23
|
+
avoidTolls: false, // true to avoid toll roads
|
|
24
|
+
units: 'm', // m - miles, km - kilometers
|
|
25
|
+
points: [
|
|
26
|
+
[0, 0]
|
|
27
|
+
], // array of consecutive series of points; each point is [lon, lat]
|
|
28
|
+
isInChina: false, // points are in China (some services need this information)
|
|
29
|
+
begin: '', // date/time for the begin of route as 'YYYY-MM-DDThh:mm'
|
|
30
|
+
turnbyturn: false, // provide detailed turn-by-turn instructions (segments in directionsResult)
|
|
31
|
+
seasonal: false, // include roads that are seasonally closed
|
|
32
|
+
path: pathType.none, // the degree of route path simplification
|
|
33
|
+
span: 0, // distance in meters for more detailed path simplification
|
|
34
|
+
alternate: false, // return alternatives to the default route
|
|
35
|
+
stats: [] // set on output - list of providers that requests have been sent to to obtain directions
|
|
36
|
+
}];
|
|
37
|
+
|
|
38
|
+
// template for directions results object
|
|
39
|
+
const directionsResult = [{ // array of directions legs, one for each consecutive series of points
|
|
40
|
+
query: directionsQuery, // query parameters
|
|
41
|
+
places: [], // addresses or place names corresponding to points (if directions service performs reverse geocoding)
|
|
42
|
+
name: '', // human-readable name of directions (if available)
|
|
43
|
+
routes: [{ // routes; one for each point with a successor in the query.points
|
|
44
|
+
duration: 0, // route duration in seconds
|
|
45
|
+
distance: 0, // route distance in meters
|
|
46
|
+
path: [], // simplified series of interim points; each point is [lon, lat]
|
|
47
|
+
seasonal: false, // indicates a road that is seasonally closed
|
|
48
|
+
segmentIndex: 0 // index of the turn-by-turn directions segment
|
|
49
|
+
}],
|
|
50
|
+
segments: [{ // turn-by-turn directions
|
|
51
|
+
duration: 0, // segment duration in seconds
|
|
52
|
+
distance: 0, // segment distance in meters
|
|
53
|
+
path: [], // series of interim points; each point is [lon, lat]
|
|
54
|
+
instructions: '' // textual instructions for this segment
|
|
55
|
+
}],
|
|
56
|
+
provider: '' // identifies service providing the directions
|
|
57
|
+
}];
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
directionsQuery,
|
|
61
|
+
directionsResult,
|
|
62
|
+
pathType,
|
|
63
|
+
travelMode
|
|
64
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const { pathType } = require("../../model");
|
|
2
|
+
const status = require('../status');
|
|
3
|
+
const util = require('../util');
|
|
4
|
+
|
|
5
|
+
module.exports = init;
|
|
6
|
+
|
|
7
|
+
const vehicle = {
|
|
8
|
+
'-1': 'car',
|
|
9
|
+
0: 'car',
|
|
10
|
+
1: 'bike',
|
|
11
|
+
2: 'foot',
|
|
12
|
+
5: 'truck'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const weighting = {
|
|
16
|
+
true: 'curvature',
|
|
17
|
+
1: 'curvature',
|
|
18
|
+
2: 'curvaturefastest'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function prepareWaypoint(qs, p) {
|
|
22
|
+
// waypoint format is lat,lon
|
|
23
|
+
qs.push('point=' + encodeURIComponent(p[1] + ',' + p[0]));
|
|
24
|
+
return qs;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractSegment(result, { distance, interval, text, time }) {
|
|
28
|
+
const { directions: { segments }, path } = result;
|
|
29
|
+
segments.push({
|
|
30
|
+
duration: Math.round((time || 0) / 1000),
|
|
31
|
+
distance: Math.round(distance || 0),
|
|
32
|
+
path: path && path.slice(interval[0], interval[1]),
|
|
33
|
+
instructions: text
|
|
34
|
+
});
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractDirections(result, { distance, instructions, points, time }) {
|
|
39
|
+
const { directions: { routes, segments }, fullPath } = result;
|
|
40
|
+
result.path = util.decode(points);
|
|
41
|
+
const route = {
|
|
42
|
+
duration: Math.round((time || 0) / 1000),
|
|
43
|
+
distance: Math.round(distance || 0)
|
|
44
|
+
};
|
|
45
|
+
if (fullPath) {
|
|
46
|
+
route.path = result.path;
|
|
47
|
+
}
|
|
48
|
+
if (segments) {
|
|
49
|
+
route.segmentIndex = segments.length;
|
|
50
|
+
}
|
|
51
|
+
routes.push(route);
|
|
52
|
+
if (segments && instructions) {
|
|
53
|
+
instructions.reduce(extractSegment, result);
|
|
54
|
+
if (segments.length) {
|
|
55
|
+
util.last(segments).path.push(util.last(result.path));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getStatus(st, response) {
|
|
62
|
+
st = st && st.status;
|
|
63
|
+
if (!(st || response)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
response = response || {};
|
|
67
|
+
response.status = response.status || st;
|
|
68
|
+
// 401 Unauthorized, 429 Too Many Requests
|
|
69
|
+
if (st === 401 || st === 429) {
|
|
70
|
+
// we exceeded the limit
|
|
71
|
+
return status.failure;
|
|
72
|
+
}
|
|
73
|
+
if (st === 400 && response.message) {
|
|
74
|
+
return status.empty;
|
|
75
|
+
}
|
|
76
|
+
if (!st) {
|
|
77
|
+
return status.success;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function init(options) {
|
|
82
|
+
|
|
83
|
+
function prepareUrl(url, { avoidHighways, avoidTolls, avoidUnpaved, curvy, mode, path, points, turnbyturn }) {
|
|
84
|
+
let req = {
|
|
85
|
+
vehicle: vehicle[mode] || vehicle[0],
|
|
86
|
+
key: options.graphhopper_key
|
|
87
|
+
};
|
|
88
|
+
if (curvy && mode === -1) {
|
|
89
|
+
req.vehicle = 'motorcycle.kurviger.de';
|
|
90
|
+
req.weighting = weighting[curvy];
|
|
91
|
+
if (options.parameters.app_type) {
|
|
92
|
+
req['app.type'] = options.parameters.app_type;
|
|
93
|
+
}
|
|
94
|
+
if (avoidUnpaved) {
|
|
95
|
+
req.avoid_unpaved_roads = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
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');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
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);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function processResponse(response, query) {
|
|
126
|
+
|
|
127
|
+
if (response && response.status >= 400) {
|
|
128
|
+
// let it cascade to the next service
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const directions = {
|
|
133
|
+
query,
|
|
134
|
+
provider: options.name
|
|
135
|
+
};
|
|
136
|
+
const paths = response && response.paths;
|
|
137
|
+
if (paths) {
|
|
138
|
+
directions.routes = [];
|
|
139
|
+
if (query.turnbyturn || query.path === pathType.smooth || query.path === pathType.coarse) {
|
|
140
|
+
directions.segments = [];
|
|
141
|
+
}
|
|
142
|
+
const fullPath = query.path === pathType.full;
|
|
143
|
+
paths.reduce(extractDirections, {
|
|
144
|
+
directions,
|
|
145
|
+
fullPath
|
|
146
|
+
});
|
|
147
|
+
if (fullPath) {
|
|
148
|
+
// path is already prepared - no need to do it again from segments
|
|
149
|
+
directions.pathReady = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return directions;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
options = util.defaults(options, {
|
|
156
|
+
maxPoints: options.graphhopper_max_points || 5, // max 5 points for free and 30-150 for paid plan
|
|
157
|
+
url: prepareUrl.bind(undefined, options.graphhopper_url),
|
|
158
|
+
status: getStatus,
|
|
159
|
+
prepareRequest,
|
|
160
|
+
processResponse
|
|
161
|
+
});
|
|
162
|
+
options.parameters = options.graphhopper_parameters || {};
|
|
163
|
+
return require('..')(options);
|
|
164
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const fetchagent = require('fetchagent');
|
|
2
|
+
const pathType = require("../model").pathType;
|
|
3
|
+
const series = require('run-series');
|
|
4
|
+
const status = require('./status');
|
|
5
|
+
const util = require('./util');
|
|
6
|
+
const debug = require('debug')('furkot:directions:service');
|
|
7
|
+
|
|
8
|
+
module.exports = init;
|
|
9
|
+
|
|
10
|
+
const limiters = {};
|
|
11
|
+
|
|
12
|
+
const ERROR = 'input error';
|
|
13
|
+
|
|
14
|
+
function eachOfSeries(items, task, fn) {
|
|
15
|
+
const tasks = items.map(function (item, i) {
|
|
16
|
+
return task.bind(null, item, i);
|
|
17
|
+
});
|
|
18
|
+
return series(tasks, fn);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function request(url, req, fn) {
|
|
22
|
+
const options = this;
|
|
23
|
+
let fa = fetchagent;
|
|
24
|
+
if (options.post) {
|
|
25
|
+
fa = fa.post(url).send(req);
|
|
26
|
+
} else {
|
|
27
|
+
fa = fa.get(url).query(req);
|
|
28
|
+
}
|
|
29
|
+
if (options.authorization) {
|
|
30
|
+
fa.set('authorization', options.authorization);
|
|
31
|
+
}
|
|
32
|
+
return fa
|
|
33
|
+
.set('accept', 'application/json')
|
|
34
|
+
.end(fn);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function initUrl(url) {
|
|
38
|
+
if (typeof url === 'function') {
|
|
39
|
+
return url;
|
|
40
|
+
}
|
|
41
|
+
return function () {
|
|
42
|
+
return url;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function init(options) {
|
|
47
|
+
let limiter;
|
|
48
|
+
let holdRequests;
|
|
49
|
+
let simplify;
|
|
50
|
+
const outstanding = {};
|
|
51
|
+
|
|
52
|
+
function abort(queryId) {
|
|
53
|
+
debug('abort', queryId);
|
|
54
|
+
if (!outstanding[queryId]) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// cancel later request if scheduled
|
|
58
|
+
if (outstanding[queryId].laterTimeoutId) {
|
|
59
|
+
clearTimeout(outstanding[queryId].laterTimeoutId);
|
|
60
|
+
}
|
|
61
|
+
// cancel request in progress
|
|
62
|
+
if (outstanding[queryId].reqInProgress) {
|
|
63
|
+
outstanding[queryId].reqInProgress.abort();
|
|
64
|
+
}
|
|
65
|
+
outstanding[queryId].callback(ERROR);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function directions(queryId, queryArray, result, fn) {
|
|
69
|
+
|
|
70
|
+
function spliceResults(idx, segments, segResult) {
|
|
71
|
+
Array.prototype.splice.apply(queryArray, [idx + queryArray.delta, 1].concat(segments));
|
|
72
|
+
Array.prototype.splice.apply(result, [idx + queryArray.delta, 1].concat(segResult));
|
|
73
|
+
queryArray.delta += segments.length - 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function queryDirections(query, idx, callback) {
|
|
77
|
+
let req;
|
|
78
|
+
let segments;
|
|
79
|
+
|
|
80
|
+
function requestLater() {
|
|
81
|
+
outstanding[queryId].laterTimeoutId = setTimeout(function () {
|
|
82
|
+
if (outstanding[queryId]) {
|
|
83
|
+
delete outstanding[queryId].laterTimeoutId;
|
|
84
|
+
}
|
|
85
|
+
queryDirections(query, idx, callback);
|
|
86
|
+
}, options.penaltyTimeout);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!outstanding[queryId]) {
|
|
90
|
+
// query has been aborted
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
outstanding[queryId].callback = callback;
|
|
94
|
+
|
|
95
|
+
if (options.skip(options, query, result[idx + queryArray.delta])) {
|
|
96
|
+
return callback();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (holdRequests) {
|
|
100
|
+
return callback();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
segments = util.splitPoints(query, queryArray.maxPoints || options.maxPoints);
|
|
104
|
+
if (!segments) {
|
|
105
|
+
return callback(ERROR);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (segments !== query) {
|
|
109
|
+
segments[0].stats = query.stats;
|
|
110
|
+
delete query.stats;
|
|
111
|
+
return directions(queryId, segments,
|
|
112
|
+
new Array(segments.length),
|
|
113
|
+
function (err, stop, id, query, result) {
|
|
114
|
+
if (query && result) {
|
|
115
|
+
spliceResults(idx, query, result);
|
|
116
|
+
}
|
|
117
|
+
callback(err);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
query.path = query.path || pathType.none;
|
|
122
|
+
req = options.prepareRequest(query);
|
|
123
|
+
if (!req) {
|
|
124
|
+
return callback();
|
|
125
|
+
}
|
|
126
|
+
if (req === true) {
|
|
127
|
+
req = undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
limiter.trigger(function () {
|
|
131
|
+
if (!outstanding[queryId]) {
|
|
132
|
+
// query has been aborted
|
|
133
|
+
limiter.skip(); // immediately process the next request in the queue
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
query.stats = query.stats || [];
|
|
137
|
+
query.stats.push(options.name);
|
|
138
|
+
outstanding[queryId].reqInProgress = options.request(options.url(query), req, function (err, response) {
|
|
139
|
+
let st;
|
|
140
|
+
let res;
|
|
141
|
+
if (!outstanding[queryId]) {
|
|
142
|
+
// query has been aborted
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
delete outstanding[queryId].reqInProgress;
|
|
146
|
+
st = options.status(err, response);
|
|
147
|
+
if (st === undefined) {
|
|
148
|
+
// shouldn't happen (bug or unexpected response format)
|
|
149
|
+
// treat it as no route
|
|
150
|
+
st = status.empty;
|
|
151
|
+
}
|
|
152
|
+
if (st === status.failure) {
|
|
153
|
+
// don't ever ask again
|
|
154
|
+
holdRequests = true;
|
|
155
|
+
return callback();
|
|
156
|
+
}
|
|
157
|
+
if (st === status.error) {
|
|
158
|
+
// try again later
|
|
159
|
+
limiter.penalty();
|
|
160
|
+
return requestLater();
|
|
161
|
+
}
|
|
162
|
+
if (st === status.empty && query.points.length > 2) {
|
|
163
|
+
query = [query];
|
|
164
|
+
query.maxPoints = 2;
|
|
165
|
+
|
|
166
|
+
return directions(queryId, query,
|
|
167
|
+
new Array(1),
|
|
168
|
+
function (err, stop, id, query, result) {
|
|
169
|
+
if (query && result) {
|
|
170
|
+
spliceResults(idx, query, result);
|
|
171
|
+
}
|
|
172
|
+
callback(err);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
res = options.processResponse(response, query);
|
|
177
|
+
if (res) {
|
|
178
|
+
if (!res.pathReady && res.routes && res.segments) {
|
|
179
|
+
simplify(query.path, query.span, res.routes, res.segments);
|
|
180
|
+
}
|
|
181
|
+
if (!query.turnbyturn) {
|
|
182
|
+
delete res.segments;
|
|
183
|
+
}
|
|
184
|
+
result[idx + queryArray.delta] = res;
|
|
185
|
+
}
|
|
186
|
+
callback();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
outstanding[queryId] = outstanding[queryId] || {
|
|
192
|
+
stack: 0,
|
|
193
|
+
hits: 0
|
|
194
|
+
};
|
|
195
|
+
outstanding[queryId].stack += 1;
|
|
196
|
+
outstanding[queryId].callback = function (err) {
|
|
197
|
+
fn(err, true, queryId, queryArray, result);
|
|
198
|
+
};
|
|
199
|
+
queryArray.delta = 0;
|
|
200
|
+
|
|
201
|
+
eachOfSeries(queryArray, queryDirections, function (err) {
|
|
202
|
+
if (outstanding[queryId]) {
|
|
203
|
+
outstanding[queryId].stack -= 1;
|
|
204
|
+
if (!outstanding[queryId].stack) {
|
|
205
|
+
delete outstanding[queryId];
|
|
206
|
+
}
|
|
207
|
+
if (err === ERROR) {
|
|
208
|
+
return fn(outstanding[queryId] ? err : undefined, true, queryId, queryArray, result);
|
|
209
|
+
}
|
|
210
|
+
fn(err, false, queryId, queryArray, result);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
options = util.defaults(options, {
|
|
216
|
+
interval: 340,
|
|
217
|
+
penaltyInterval: 2000,
|
|
218
|
+
limiter: limiters[options.name],
|
|
219
|
+
request,
|
|
220
|
+
abort
|
|
221
|
+
});
|
|
222
|
+
options.url = initUrl(options.url);
|
|
223
|
+
limiters[options.name] = options.limiter || require('limiter-component')(options.interval, options.penaltyInterval);
|
|
224
|
+
limiter = limiters[options.name];
|
|
225
|
+
simplify = require('./simplify')(options);
|
|
226
|
+
directions.abort = options.abort;
|
|
227
|
+
return directions;
|
|
228
|
+
}
|