├── .eslintrc
├── .gitignore
├── .travis.yml
├── API.md
├── CHANGELOG.md
├── LICENSE.md
├── Makefile
├── README.md
├── index.js
├── package-lock.json
├── package.json
├── test
├── data
│ ├── Makefile
│ └── data.md5sum
└── index.js
└── utils.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb-base", "prettier"],
3 | "plugins": ["import"],
4 | "rules": {
5 | "arrow-parens": ["warn", "as-needed", { "requireForBlockBody": true }],
6 | "comma-dangle": ["error", "never"],
7 | "max-len": ["error", { "code": 100, "ignoreUrls": true }],
8 | "no-param-reassign": "off",
9 | "quotes": ["error", "single"]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | yarn-error.log
5 |
6 | test/data/*
7 | !test/data/Makefile
8 | !test/data/data.md5sum
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | dist: trusty
4 |
5 | sudo: required
6 |
7 | cache:
8 | apt: true
9 | npm: true
10 | ccache: true
11 |
12 | addons:
13 | apt:
14 | sources: ["ubuntu-toolchain-r-test"]
15 | packages:
16 | [
17 | "g++-6",
18 | "gcc-6",
19 | "build-essential",
20 | "git",
21 | "wget",
22 | "cmake3",
23 | "pkg-config",
24 | "libbz2-dev",
25 | "libstxxl-dev",
26 | "libstxxl1",
27 | "libxml2-dev",
28 | "libzip-dev",
29 | "libboost-all-dev",
30 | "lua5.2",
31 | "liblua5.2-dev",
32 | "libtbb-dev",
33 | ]
34 |
35 | env: CPP=cpp-6 CC=gcc-6 CXX=g++-6
36 |
37 | branches:
38 | except:
39 | - /^v[0-9]/
40 |
41 | node_js:
42 | - "10"
43 | - "8"
44 |
45 | install: npm install
46 |
47 | script:
48 | - npm run lint
49 | - make test
50 |
--------------------------------------------------------------------------------
/API.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## isochrone
4 |
5 | [index.js:91-119][1]
6 |
7 | Build isochrone using start point and options
8 |
9 | ### Parameters
10 |
11 | - `startPoint` **[Array][2]<float>** start point [lng, lat]
12 | - `options` **[Object][3]** object
13 | - `options.osrm` **[Object][3]** [OSRM][4] instance
14 | - `options.radius` **[number][5]** distance to draw the buffer as in
15 | [@turf/buffer][6]
16 | - `options.cellSize` **[number][5]** the distance across each cell as in
17 | [@turf/point-grid][7]
18 | - `options.intervals` **[Array][2]<[number][5]>** intervals for isochrones in minutes
19 | - `options.deintersect` **[boolean][8]** whether or not to deintersect the isochrones.
20 | If this is true, then the isochrones will be mutually exclusive
21 | - `options.concavity` **[number][5]** relative measure of concavity as in
22 | [concaveman][9] (optional, default `2`)
23 | - `options.lengthThreshold` **[number][5]** length threshold as in
24 | [concaveman][9] (optional, default `0`)
25 | - `options.units` **[string][10]** any of the options supported by turf units (optional, default `'kilometers'`)
26 |
27 | ### Examples
28 |
29 | ```javascript
30 | const OSRM = require('osrm');
31 | const isochrone = require('isochrone');
32 |
33 | const osrm = new OSRM({ path: './monaco.osrm' });
34 | const startPoint = [7.41337, 43.72956];
35 |
36 | const options = {
37 | osrm,
38 | radius: 2,
39 | cellSize: 0.1,
40 | intervals: [5, 10, 15],
41 | deintersect: true
42 | };
43 |
44 | isochrone(startPoint, options)
45 | .then((geojson) => {
46 | console.log(JSON.stringify(geojson));
47 | });
48 | ```
49 |
50 | Returns **[Promise][11]** GeoJSON FeatureCollection of Polygons when resolved
51 |
52 | [1]: https://github.com/stepankuzmin/node-isochrone/blob/3dc588a2f3a3b21db10e751150d41e318866fa27/index.js#L91-L119 "Source code on GitHub"
53 |
54 | [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
55 |
56 | [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
57 |
58 | [4]: https://github.com/Project-OSRM/osrm-backend
59 |
60 | [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
61 |
62 | [6]: https://github.com/Turfjs/turf/tree/master/packages/turf-buffer
63 |
64 | [7]: https://github.com/Turfjs/turf/tree/master/packages/turf-point-grid
65 |
66 | [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
67 |
68 | [9]: https://github.com/mapbox/concaveman
69 |
70 | [10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
71 |
72 | [11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
73 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 | ## [3.0.2](https://github.com/stepankuzmin/node-isochrone/compare/v3.0.1...v3.0.2) (2018-09-20)
7 |
8 |
9 | ### Bug Fixes
10 |
11 | * fix polygon deintersection ([900e59c](https://github.com/stepankuzmin/node-isochrone/commit/900e59c))
12 |
13 |
14 |
15 |
16 | ## [3.0.1](https://github.com/stepankuzmin/node-isochrone/compare/v3.0.0...v3.0.1) (2018-05-25)
17 |
18 |
19 | ### Bug Fixes
20 |
21 | * Add extra catch for empty polygons ([1f4e521](https://github.com/stepankuzmin/node-isochrone/commit/1f4e521))
22 |
23 |
24 |
25 |
26 | # [3.0.0](https://github.com/stepankuzmin/node-isochrone/compare/v2.0.2...v3.0.0) (2018-04-02)
27 |
28 |
29 | ### Bug Fixes
30 |
31 | * **package:** update [@turf](https://github.com/turf)/helpers to version 6.0.1 ([907a4b4](https://github.com/stepankuzmin/node-isochrone/commit/907a4b4))
32 | * **package:** update [@turf](https://github.com/turf)/helpers to version 6.1.0 ([30d51c2](https://github.com/stepankuzmin/node-isochrone/commit/30d51c2))
33 | * **package:** update geojson-rewind to version 0.3.1 ([67efc27](https://github.com/stepankuzmin/node-isochrone/commit/67efc27))
34 |
35 |
36 | ### Features
37 |
38 | * update OSRM, drop node v6 support ([d3d05c3](https://github.com/stepankuzmin/node-isochrone/commit/d3d05c3))
39 |
40 |
41 | ### BREAKING CHANGES
42 |
43 | * drop node v6 support
44 |
45 |
46 |
47 |
48 | ## [2.0.2](https://github.com/stepankuzmin/node-isochrone/compare/v2.0.1...v2.0.2) (2018-02-05)
49 |
50 |
51 | ### Bug Fixes
52 |
53 | * **package:** update [@turf](https://github.com/turf)/bbox to version 4.6.0 ([12efd8c](https://github.com/stepankuzmin/node-isochrone/commit/12efd8c))
54 | * **package:** update [@turf](https://github.com/turf)/bbox to version 4.7.3 ([3365751](https://github.com/stepankuzmin/node-isochrone/commit/3365751))
55 | * **package:** update [@turf](https://github.com/turf)/bbox to version 5.0.4 ([b9171aa](https://github.com/stepankuzmin/node-isochrone/commit/b9171aa))
56 | * **package:** update [@turf](https://github.com/turf)/bbox to version 5.1.0 ([b53faf6](https://github.com/stepankuzmin/node-isochrone/commit/b53faf6))
57 | * **package:** update [@turf](https://github.com/turf)/bbox to version 5.1.5 ([04fed0b](https://github.com/stepankuzmin/node-isochrone/commit/04fed0b))
58 | * **package:** update [@turf](https://github.com/turf)/destination to version 4.6.0 ([caf0fed](https://github.com/stepankuzmin/node-isochrone/commit/caf0fed))
59 | * **package:** update [@turf](https://github.com/turf)/destination to version 4.7.3 ([40c8186](https://github.com/stepankuzmin/node-isochrone/commit/40c8186))
60 | * **package:** update [@turf](https://github.com/turf)/destination to version 5.0.4 ([c0c1ee8](https://github.com/stepankuzmin/node-isochrone/commit/c0c1ee8))
61 | * **package:** update [@turf](https://github.com/turf)/destination to version 5.1.0 ([db65b78](https://github.com/stepankuzmin/node-isochrone/commit/db65b78))
62 | * **package:** update [@turf](https://github.com/turf)/destination to version 5.1.5 ([a866ce1](https://github.com/stepankuzmin/node-isochrone/commit/a866ce1))
63 | * **package:** update [@turf](https://github.com/turf)/helpers to version 4.6.0 ([099e56d](https://github.com/stepankuzmin/node-isochrone/commit/099e56d))
64 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 5.1.0 ([9da0702](https://github.com/stepankuzmin/node-isochrone/commit/9da0702))
65 | * properly handle errors ([43f8532](https://github.com/stepankuzmin/node-isochrone/commit/43f8532))
66 | * **package:** update [@turf](https://github.com/turf)/helpers to version 4.7.3 ([4e27f8c](https://github.com/stepankuzmin/node-isochrone/commit/4e27f8c))
67 | * **package:** update [@turf](https://github.com/turf)/helpers to version 5.0.4 ([38dc2ec](https://github.com/stepankuzmin/node-isochrone/commit/38dc2ec))
68 | * **package:** update [@turf](https://github.com/turf)/helpers to version 5.1.0 ([0f2674f](https://github.com/stepankuzmin/node-isochrone/commit/0f2674f))
69 | * **package:** update [@turf](https://github.com/turf)/helpers to version 5.1.4 ([14def13](https://github.com/stepankuzmin/node-isochrone/commit/14def13))
70 | * **package:** update [@turf](https://github.com/turf)/helpers to version 6.0.0-beta.1 ([2f47fa8](https://github.com/stepankuzmin/node-isochrone/commit/2f47fa8))
71 | * **package:** update [@turf](https://github.com/turf)/helpers to version 6.0.0-beta.4 ([fc1139c](https://github.com/stepankuzmin/node-isochrone/commit/fc1139c))
72 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 4.6.0 ([197ad3f](https://github.com/stepankuzmin/node-isochrone/commit/197ad3f))
73 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 4.7.3 ([9d8c894](https://github.com/stepankuzmin/node-isochrone/commit/9d8c894))
74 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 5.0.0 ([0505a3d](https://github.com/stepankuzmin/node-isochrone/commit/0505a3d))
75 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 5.0.4 ([39407a0](https://github.com/stepankuzmin/node-isochrone/commit/39407a0))
76 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 5.1.5 ([aca7b85](https://github.com/stepankuzmin/node-isochrone/commit/aca7b85))
77 |
78 |
79 |
80 |
81 | ## [2.0.1](https://github.com/stepankuzmin/node-isochrone/compare/v2.0.0...v2.0.1) (2017-07-17)
82 |
83 |
84 |
85 |
86 | # [2.0.0](https://github.com/stepankuzmin/node-isochrone/compare/v1.0.2...v2.0.0) (2017-07-12)
87 |
88 |
89 | ### Bug Fixes
90 |
91 | * **package:** update [@turf](https://github.com/turf)/bbox to version 4.5.1 ([0318cce](https://github.com/stepankuzmin/node-isochrone/commit/0318cce))
92 | * **package:** update [@turf](https://github.com/turf)/destination to version 4.5.1 ([8518f8b](https://github.com/stepankuzmin/node-isochrone/commit/8518f8b))
93 | * **package:** update [@turf](https://github.com/turf)/helpers to version 4.5.1 ([b5fe981](https://github.com/stepankuzmin/node-isochrone/commit/b5fe981))
94 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 4.5.1 ([d22e253](https://github.com/stepankuzmin/node-isochrone/commit/d22e253))
95 |
96 |
97 | ### build
98 |
99 | * add node v8 to travis ([3a79eab](https://github.com/stepankuzmin/node-isochrone/commit/3a79eab))
100 |
101 |
102 | ### Features
103 |
104 | * implement deintersect as a configurable option (by [@glifchits](https://github.com/glifchits)) ([21739f1](https://github.com/stepankuzmin/node-isochrone/commit/21739f1))
105 |
106 |
107 | ### BREAKING CHANGES
108 |
109 | * drop node v4 support
110 |
111 |
112 |
113 |
114 | ## [1.0.2](https://github.com/stepankuzmin/node-isochrone/compare/v1.0.1...v1.0.2) (2017-06-11)
115 |
116 |
117 | ### Bug Fixes
118 |
119 | * **package:** update [@turf](https://github.com/turf)/bbox to version 4.4.0 ([74976ac](https://github.com/stepankuzmin/node-isochrone/commit/74976ac))
120 | * **package:** update [@turf](https://github.com/turf)/destination to version 4.4.0 ([44e4560](https://github.com/stepankuzmin/node-isochrone/commit/44e4560))
121 | * **package:** update [@turf](https://github.com/turf)/helpers to version 4.4.0 ([28295e4](https://github.com/stepankuzmin/node-isochrone/commit/28295e4))
122 | * **package:** update [@turf](https://github.com/turf)/point-grid to version 4.4.0 ([55b5cc4](https://github.com/stepankuzmin/node-isochrone/commit/55b5cc4))
123 |
124 |
125 |
126 |
127 | ## [1.0.1](https://github.com/stepankuzmin/node-isochrone/compare/v1.0.0...v1.0.1) (2017-05-30)
128 |
129 |
130 |
131 |
132 | # 1.0.0 (2017-05-29)
133 |
134 |
135 | ### Bug Fixes
136 |
137 | * deintersect isochrones with different time ([01b415a](https://github.com/stepankuzmin/node-isochrone/commit/01b415a))
138 | * fix turf-deintersect version ([d25c565](https://github.com/stepankuzmin/node-isochrone/commit/d25c565))
139 |
140 |
141 | ### Features
142 |
143 | * initial commit ([47d8903](https://github.com/stepankuzmin/node-isochrone/commit/47d8903))
144 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Stepan Kuzmin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | npm i
3 |
4 | clean:
5 | rm -rf node_modules
6 |
7 | shm:
8 | $(MAKE) all -i -C ./test/data
9 |
10 | test: shm
11 | npm test
12 |
13 | .PHONY: test clean shm
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Isochrone
2 |
3 | [](https://www.npmjs.com/package/isochrone)
4 | [](https://www.npmjs.com/package/isochrone)
5 | [](https://travis-ci.com/stepankuzmin/node-isochrone)
6 |
7 | Isochrone maps are commonly used to depict areas of equal travel time.
8 | Build isochrones using [OSRM](http://project-osrm.org/), [Turf](http://turfjs.org/) and [concaveman](https://github.com/mapbox/concaveman).
9 |
10 | 
11 |
12 | ## Installation
13 |
14 | ```sh
15 | npm install osrm isochrone
16 | ```
17 |
18 | ## Usage
19 |
20 | ### Building OSRM graph
21 |
22 | This package consumes preprocessed [OSRM](http://project-osrm.org/) graph as an input. To build such a graph you have to extract it from your OSM file with one of [profiles](https://github.com/Project-OSRM/osrm-backend/wiki/Profiles) and build it using one of the algorithms (Contraction Hierarchies or Multi-Level Dijkstra).
23 |
24 | To build OSRM graph using `isochrone` package, you can clone the source code and install dependencies
25 |
26 | ```sh
27 | git clone https://github.com/stepankuzmin/node-isochrone.git
28 | cd node-isochrone
29 | npm i
30 | ```
31 |
32 | Here is an example of how to extract graph using `foot` profile and build it using contraction hierarchies algorithm.
33 |
34 | ```sh
35 | wget https://s3.amazonaws.com/mapbox/osrm/testing/monaco.osm.pbf
36 | ./node_modules/osrm/lib/binding/osrm-extract -p ./node_modules/osrm/profiles/foot.lua monaco.osm.pbf
37 | ./node_modules/osrm/lib/binding/osrm-contract monaco.osrm
38 | ```
39 |
40 | ### Example
41 |
42 | See [API](https://github.com/stepankuzmin/node-isochrone/blob/master/API.md) for more info.
43 |
44 | ```js
45 | const OSRM = require("osrm");
46 | const isochrone = require("isochrone");
47 |
48 | const osrm = new OSRM({ path: "./monaco.osrm" });
49 |
50 | const startPoint = [7.42063, 43.73104];
51 |
52 | const options = {
53 | osrm,
54 | radius: 2,
55 | cellSize: 0.1,
56 | intervals: [5, 10, 15]
57 | };
58 |
59 | isochrone(startPoint, options).then(geojson => {
60 | console.log(JSON.stringify(geojson, null, 2));
61 | });
62 | ```
63 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const deintersect = require('turf-deintersect');
2 | const helpers = require('@turf/helpers');
3 | const rewind = require('geojson-rewind');
4 | const { makeGrid, groupByInterval, makePolygons } = require('./utils');
5 |
6 | /**
7 | * Build isochrone using start point and options
8 | *
9 | * @param {Array.} startPoint start point [lng, lat]
10 | * @param {Object} options object
11 | * @param {Object} options.osrm - [OSRM](https://github.com/Project-OSRM/osrm-backend) instance
12 | * @param {number} options.radius - distance to draw the buffer as in [@turf/buffer](https://github.com/Turfjs/turf/tree/master/packages/turf-buffer)
13 | * @param {number} options.cellSize - the distance across each cell as in [@turf/point-grid](https://github.com/Turfjs/turf/tree/master/packages/turf-point-grid)
14 | * @param {Array.} options.intervals - intervals for isochrones in minutes
15 | * @param {boolean} options.deintersect - whether or not to deintersect the isochrones.
16 | * If this is true, then the isochrones will be mutually exclusive
17 | * @param {number} [options.concavity=2] - relative measure of concavity as in [concaveman](https://github.com/mapbox/concaveman)
18 | * @param {number} [options.lengthThreshold=0] - length threshold as in [concaveman](https://github.com/mapbox/concaveman)
19 | * @param {string} [options.units='kilometers'] - any of the options supported by turf units
20 | * @returns {Promise} GeoJSON FeatureCollection of Polygons when resolved
21 | *
22 | * @example
23 | * const OSRM = require('osrm');
24 | * const isochrone = require('isochrone');
25 | *
26 | * const osrm = new OSRM({ path: './monaco.osrm' });
27 | * const startPoint = [7.41337, 43.72956];
28 | *
29 | * const options = {
30 | * osrm,
31 | * radius: 2,
32 | * cellSize: 0.1,
33 | * intervals: [5, 10, 15]
34 | * };
35 | *
36 | * isochrone(startPoint, options)
37 | * .then((geojson) => {
38 | * console.log(JSON.stringify(geojson));
39 | * });
40 | */
41 | const isochrone = (startPoint, options) =>
42 | new Promise((resolve, reject) => {
43 | try {
44 | const endPoints = makeGrid(startPoint, options);
45 | const coordinates = [startPoint].concat(endPoints);
46 | options.osrm.table({ sources: [0], coordinates }, (error, table) => {
47 | if (error) {
48 | reject(error);
49 | }
50 |
51 | const travelTime = table.durations[0] || [];
52 |
53 | const pointsByInterval = groupByInterval(table.destinations, options.intervals, travelTime);
54 |
55 | const polygons = makePolygons(pointsByInterval, options);
56 |
57 | const features = options.deintersect ? deintersect(polygons) : polygons;
58 | const featureCollection = rewind(helpers.featureCollection(features));
59 |
60 | resolve(featureCollection);
61 | });
62 | } catch (error) {
63 | reject(error);
64 | }
65 | });
66 |
67 | module.exports = isochrone;
68 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "isochrone",
3 | "version": "3.0.2",
4 | "description": "isochrone map library",
5 | "author": "Stepan Kuzmin (stepankuzmin.ru)",
6 | "homepage": "https://github.com/stepankuzmin/node-isochrone",
7 | "license": "MIT",
8 | "main": "index.js",
9 | "scripts": {
10 | "extract": "./node_modules/osrm/lib/binding/osrm-extract",
11 | "contract": "./node_modules/osrm/lib/binding/osrm-contract",
12 | "cz": "git-cz",
13 | "docs": "documentation build index.js -g --markdown-toc false -f md -o API.md",
14 | "lint": "eslint .",
15 | "release": "standard-version",
16 | "test": "node test/index.js"
17 | },
18 | "config": {
19 | "commitizen": {
20 | "path": "cz-conventional-changelog"
21 | }
22 | },
23 | "husky": {
24 | "hooks": {
25 | "pre-commit": "lint-staged"
26 | }
27 | },
28 | "lint-staged": {
29 | "*.js": [
30 | "prettier-eslint --write",
31 | "npm run lint",
32 | "git add"
33 | ]
34 | },
35 | "engine-strict": true,
36 | "engines": {
37 | "node": ">=8"
38 | },
39 | "keywords": [
40 | "isochrone",
41 | "drivetime",
42 | "walktime",
43 | "geojson",
44 | "polygon",
45 | "osrm"
46 | ],
47 | "repository": {
48 | "type": "git",
49 | "url": "git://github.com/stepankuzmin/node-isochrone.git"
50 | },
51 | "bugs": {
52 | "url": "https://github.com/stepankuzmin/node-isochrone/issues"
53 | },
54 | "dependencies": {
55 | "@turf/bbox": "6.0.1",
56 | "@turf/destination": "6.0.1",
57 | "@turf/helpers": "6.1.4",
58 | "@turf/point-grid": "6.0.1",
59 | "concaveman": "1.1.1",
60 | "geojson-rewind": "0.3.1",
61 | "turf-deintersect": "1.0.4"
62 | },
63 | "peerDependencies": {
64 | "osrm": "^5.16.4"
65 | },
66 | "devDependencies": {
67 | "@mapbox/geojsonhint": "^3.0.0",
68 | "@turf/area": "6.0.1",
69 | "@turf/intersect": "6.1.3",
70 | "commitizen": "^4.0.3",
71 | "cz-conventional-changelog": "^3.0.2",
72 | "documentation": "^12.1.4",
73 | "eslint": "^6.6.0",
74 | "eslint-config-airbnb-base": "^14.0.0",
75 | "eslint-config-prettier": "^6.5.0",
76 | "eslint-plugin-import": "^2.18.2",
77 | "husky": "^3.0.9",
78 | "lint-staged": "^9.4.3",
79 | "osrm": "^5.22.0",
80 | "prettier": "^1.19.1",
81 | "prettier-eslint": "^9.0.0",
82 | "prettier-eslint-cli": "^5.0.0",
83 | "standard-version": "^7.0.0",
84 | "tape": "^4.11.0"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/test/data/Makefile:
--------------------------------------------------------------------------------
1 | DATA_NAME:=monaco
2 | DATA_URL:=https://s3.amazonaws.com/mapbox/osrm/testing/$(DATA_NAME).osm.pbf
3 | DATA_POLY_URL:=https://s3.amazonaws.com/mapbox/osrm/testing/$(DATA_NAME).poly
4 | OSRM_BUILD_DIR=../../node_modules/osrm/lib/binding
5 | PROFILE_ROOT:=../../node_modules/osrm/profiles
6 | OSRM_EXTRACT:=$(OSRM_BUILD_DIR)/osrm-extract
7 | OSRM_CONTRACT:=$(OSRM_BUILD_DIR)/osrm-contract
8 | PROFILE:=$(PROFILE_ROOT)/car.lua
9 |
10 | all:
11 | wget $(DATA_URL) -O $(DATA_NAME).osm.pbf
12 | @echo "Running osrm-extract..."
13 | $(OSRM_EXTRACT) -p $(PROFILE) $(DATA_NAME).osm.pbf
14 | @echo "Running osrm-contract..."
15 | $(OSRM_CONTRACT) $(DATA_NAME).osrm
16 |
17 | clean:
18 | -rm -r $(DATA_NAME).*
19 |
20 | .PHONY: clean all
21 |
--------------------------------------------------------------------------------
/test/data/data.md5sum:
--------------------------------------------------------------------------------
1 | 2b8dd9343d5e615afc9c67bcc7028a63 monaco.osm.pbf
2 | b0788991ab3791d53c1c20b6281f81ad monaco.poly
3 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const test = require('tape');
3 | const OSRM = require('osrm');
4 | const area = require('@turf/area').default;
5 | const intersect = require('@turf/intersect').default;
6 | const geojsonhint = require('@mapbox/geojsonhint');
7 | const isochrone = require('../index');
8 |
9 | const points = [[7.41337, 43.72956], [7.41375, 43.73339], [7.41862, 43.73216]];
10 |
11 | const osrmPath = path.join(__dirname, './data/monaco.osrm');
12 | const osrm = new OSRM({ path: osrmPath });
13 |
14 | const options = {
15 | osrm,
16 | radius: 2,
17 | cellSize: 0.1,
18 | concavity: 2,
19 | intervals: [5, 10, 15],
20 | lengthThreshold: 0,
21 | units: 'kilometers'
22 | };
23 |
24 | const timeColors = {
25 | 5: '#0f0',
26 | 10: '#00f',
27 | 15: '#f00'
28 | };
29 |
30 | // For argument array intervals: [5, 10, 15, 20]
31 | // Gives: [ [5,10], [5,15], [5,20], [10,15], [10,20], [15,20] ]
32 | const testPairs = options.intervals
33 | .map(i => ({ head: i, tail: options.intervals.filter(x => x > i) }))
34 | .map(z => z.tail.map(t => [z.head, t]))
35 | .reduce((x, y) => x.concat(y), []);
36 |
37 | const getIntersectionArea = (a, b) => {
38 | const intersection = intersect(a, b);
39 | return intersection ? area(intersection) : 0;
40 | };
41 |
42 | test('deintersected isochrone', (t) => {
43 | t.plan(points.length * (1 + testPairs.length));
44 | points.forEach(point =>
45 | isochrone(point, { ...options, deintersect: true })
46 | .then((geojson) => {
47 | geojson.features.forEach((feature) => {
48 | // eslint-disable-next-line
49 | feature.properties.fill = timeColors[feature.properties.time];
50 | });
51 |
52 | const errors = geojsonhint.hint(geojson);
53 | if (errors.length > 0) {
54 | errors.forEach(error => t.comment(error.message));
55 | t.fail('Invalid GeoJSON');
56 | } else {
57 | t.pass('Valid GeoJSON');
58 | }
59 | // Gives { interval1: isochrone1, interval2: isochrone2, ... }
60 | const isochrones = options.intervals.reduce(
61 | (acc, interval) => ({
62 | ...acc,
63 | [interval]: geojson.features.find(iso => iso.properties.time === interval)
64 | }),
65 | {}
66 | );
67 | // test that every smaller isochrone is contained by a larger one
68 | testPairs.forEach((minutePair) => {
69 | const [minSmall, minLarge] = minutePair;
70 | const [small, large] = [isochrones[minSmall], isochrones[minLarge]];
71 | const areaIntersection = getIntersectionArea(small, large);
72 |
73 | // assert that there is no intersection between isochrones
74 | if (areaIntersection < 300) {
75 | t.pass(`Isochrone ${minSmall} does not intersect ${minLarge}`);
76 | } else {
77 | t.fail(
78 | `Isochrone ${minSmall} intersects with ${minLarge} with area ${areaIntersection}`
79 | );
80 | }
81 | });
82 | })
83 | .catch(error => t.error(error, 'No error'))
84 | );
85 | });
86 |
87 | test('isochrone', (t) => {
88 | t.plan(points.length * (1 + testPairs.length));
89 | points.forEach(point =>
90 | isochrone(point, { ...options, deintersect: false })
91 | .then((geojson) => {
92 | const errors = geojsonhint.hint(geojson);
93 | if (errors.length > 0) {
94 | errors.forEach(error => t.comment(error.message));
95 | t.fail('Invalid GeoJSON');
96 | } else {
97 | t.pass('Valid GeoJSON');
98 | }
99 | // Gives { interval1: isochrone1, interval2: isochrone2, ... }
100 | const isochrones = options.intervals.reduce(
101 | (acc, interval) => ({
102 | ...acc,
103 | [interval]: geojson.features.find(iso => iso.properties.time === interval)
104 | }),
105 | {}
106 | );
107 | // test that every smaller isochrone is contained by a larger one
108 | testPairs.forEach((minutePair) => {
109 | const [minSmall, minLarge] = minutePair;
110 | const [small, large] = [isochrones[minSmall], isochrones[minLarge]];
111 | const intersection = intersect(small, large);
112 | const areaIntersection = intersection ? area(intersection) : 0;
113 | // assert that the small isochrone overlaps over 90% with the large
114 | if (areaIntersection / area(small) > 0.9) {
115 | t.pass(`Isochrone ${minSmall} is contained in ${minLarge}`);
116 | } else {
117 | t.fail(`Isochrone ${minSmall} is not contained in ${minLarge}`);
118 | }
119 | });
120 | })
121 | .catch(error => t.error(error, 'No error'))
122 | );
123 | });
124 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | const bbox = require('@turf/bbox').default;
2 | const concaveman = require('concaveman');
3 | const destination = require('@turf/destination').default;
4 | const helpers = require('@turf/helpers');
5 | const pointGrid = require('@turf/point-grid').default;
6 |
7 | const makeGrid = (startPoint, options) => {
8 | const point = helpers.point(startPoint);
9 |
10 | const spokes = helpers.featureCollection([
11 | destination(point, options.radius, 180, options.unit),
12 | destination(point, options.radius, 0, options.unit),
13 | destination(point, options.radius, 90, options.unit),
14 | destination(point, options.radius, -90, options.unit)
15 | ]);
16 |
17 | const bboxGrid = bbox(spokes);
18 | const grid = pointGrid(bboxGrid, options.cellSize, { units: options.units });
19 |
20 | return grid.features.map(feature => feature.geometry.coordinates);
21 | };
22 |
23 | const groupByInterval = (destinations, intervals, travelTime) =>
24 | intervals.reduce((pointsByInterval, interval) => {
25 | const intervalInSeconds = interval * 60;
26 | pointsByInterval[interval] = destinations.reduce((acc, point, index) => {
27 | if (travelTime[index] !== null && travelTime[index] <= intervalInSeconds) {
28 | acc.push(point.location);
29 | }
30 | return acc;
31 | }, []);
32 |
33 | return pointsByInterval;
34 | }, {});
35 |
36 | const makePolygon = (points, interval, options) => {
37 | const concave = concaveman(points, options.concavity, options.lengthThreshold);
38 | return helpers.polygon([concave], { time: parseFloat(interval) });
39 | };
40 |
41 | const makePolygons = (pointsByInterval, options) =>
42 | Object.keys(pointsByInterval).reduce((acc, interval) => {
43 | const points = pointsByInterval[interval];
44 | if (points.length >= 3) {
45 | acc.push(makePolygon(points, interval, options));
46 | }
47 | return acc;
48 | }, []);
49 |
50 | module.exports = { makeGrid, groupByInterval, makePolygons };
51 |
--------------------------------------------------------------------------------