├── .github └── CODEOWNERS ├── test ├── fixtures │ ├── merge-ways │ │ ├── options-{} │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── options-undefined │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── merge-order │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 1-way-loop-with-exit │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── mixed-access-no-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── mixed-bridge-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── mixed-bridge-no-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── mixed-highway-merge │ │ │ ├── options │ │ │ ├── after │ │ │ ├── mixed-highway-merge │ │ │ │ └── after │ │ │ └── before │ │ ├── mixed-tunnel-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── mixed-tunnel-no-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 1-oneway-1-bidirectional │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 2-oneways-same-direction │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 3-straight-ways=>1-way │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 3-ways-backwards=>1-way │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 3-ways-opposing=>1-way │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── keep-highway-tags-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── keep-highway-tags-no-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── mixed-highway-no-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 2-oneways-different-direction │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 3-opposing-oneways=>unchanged │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 2-2-ways-different-directions=>1-way │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ ├── 2-mergable-oneways-with-1-reversed │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ │ └── mixed-access-merge │ │ │ ├── options │ │ │ ├── after │ │ │ └── before │ └── split-ways │ │ ├── 1-way-loop │ │ ├── before │ │ └── after │ │ ├── circle │ │ ├── before │ │ └── after │ │ ├── 2-way=>3-way-fork │ │ ├── before │ │ └── after │ │ ├── 2-way=>4-way-intersection │ │ ├── before │ │ └── after │ │ ├── loop-with-exit │ │ ├── before │ │ └── after │ │ ├── keep-tags │ │ ├── before │ │ └── after │ │ ├── loop-with-intersection │ │ ├── before │ │ └── after │ │ ├── 3-way=>4-way-intersection │ │ ├── before │ │ └── after │ │ └── 1-way-multi-intersection │ │ ├── before │ │ └── after ├── split-ways.test.js ├── merge-ways.test.js └── unidirectional-ways.test.js ├── .gitignore ├── .travis.yml ├── eslint.config.mjs ├── index.js ├── bench ├── merge-ways.bench.js ├── split-ways.bench.js └── fixtures │ └── small.json ├── lib ├── unidirectional-ways.js ├── split-ways.js └── merge-ways.js ├── package.json ├── LICENSE └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @mapbox/telemetry-traffic 2 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/options-{}/options: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/options-undefined/options: -------------------------------------------------------------------------------- 1 | "undefined" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | data 3 | test_output 4 | .nyc_output 5 | .tap -------------------------------------------------------------------------------- /test/fixtures/merge-ways/merge-order/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/1-way-loop-with-exit/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-access-no-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-bridge-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": true 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-bridge-no-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-highway-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": true, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-tunnel-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": true, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-tunnel-no-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/1-oneway-1-bidirectional/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-oneways-same-direction/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-straight-ways=>1-way/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-ways-backwards=>1-way/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-ways-opposing=>1-way/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/keep-highway-tags-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": true, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/keep-highway-tags-no-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-highway-no-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-oneways-different-direction/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-opposing-oneways=>unchanged/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-2-ways-different-directions=>1-way/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-mergable-oneways-with-1-reversed/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-access-merge/options: -------------------------------------------------------------------------------- 1 | { 2 | "mergeHighways": false, 3 | "mergeTunnels": false, 4 | "mergeBridges": false, 5 | "mergeAccess": true 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 14 4 | env: 5 | - PATH=$TRAVIS_BUILD_DIR:$PATH 6 | before_install: 7 | - curl -Ls https://mapbox-release-engineering.s3.amazonaws.com/mbx-ci/latest/mbx-ci-linux-amd64 > mbx-ci && chmod 755 mbx-ci 8 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import config from 'eslint-config-mourner'; 2 | 3 | export default [ 4 | ...config, 5 | { 6 | files: ['*.js', 'test/*.js'], 7 | rules: { 8 | indent: ["error", 4], 9 | }, 10 | } 11 | ]; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const mergeWays = require('./lib/merge-ways'); 4 | const splitWays = require('./lib/split-ways'); 5 | const unidirectionalWays = require('./lib/unidirectional-ways'); 6 | 7 | const graphNormalizer = { 8 | mergeWays, 9 | splitWays, 10 | unidirectionalWays 11 | }; 12 | 13 | module.exports = graphNormalizer; 14 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/merge-order/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ], 11 | "oneway": 0 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 10.121197700500488, 18 | 48.28707663414987 19 | ], 20 | [ 21 | 10.125274658203125, 22 | 48.28790474610689 23 | ], 24 | [ 25 | 10.12986660003662, 26 | 48.289218275462225 27 | ] 28 | ] 29 | } 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-oneways-same-direction/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ], 11 | "oneway": 1 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 10.121197700500488, 18 | 48.28707663414987 19 | ], 20 | [ 21 | 10.125274658203125, 22 | 48.28790474610689 23 | ], 24 | [ 25 | 10.12986660003662, 26 | 48.289218275462225 27 | ] 28 | ] 29 | } 30 | } 31 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-2-ways-different-directions=>1-way/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ], 11 | "oneway": 0 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 10.121197700500488, 18 | 48.28707663414987 19 | ], 20 | [ 21 | 10.125274658203125, 22 | 48.28790474610689 23 | ], 24 | [ 25 | 10.12986660003662, 26 | 48.289218275462225 27 | ] 28 | ] 29 | } 30 | } 31 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-mergable-oneways-with-1-reversed/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ], 11 | "oneway": 1 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 8.760030269622803, 18 | 11.87619136515638 19 | ], 20 | [ 21 | 8.766381740570068, 22 | 11.877913225380622 23 | ], 24 | [ 25 | 8.776638507843018, 26 | 11.878648162406192 27 | ] 28 | ] 29 | } 30 | } 31 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-straight-ways=>1-way/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "geometry": { 5 | "type": "LineString", 6 | "coordinates": [ 7 | [ 8 | 10.121197700500488, 9 | 48.28707663414987 10 | ], 11 | [ 12 | 10.125274658203125, 13 | 48.28790474610689 14 | ], 15 | [ 16 | 10.12986660003662, 17 | 48.289218275462225 18 | ], 19 | [ 20 | 10.132913589477539, 21 | 48.29067454025491 22 | ] 23 | ] 24 | }, 25 | "properties": { 26 | "id": "123,234,345", 27 | "refs": [ 28 | "1", 29 | "2", 30 | "3", 31 | "4" 32 | ] 33 | } 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-ways-backwards=>1-way/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ] 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 10.121197700500488, 18 | 48.28707663414987 19 | ], 20 | [ 21 | 10.125274658203125, 22 | 48.28790474610689 23 | ], 24 | [ 25 | 10.12986660003662, 26 | 48.289218275462225 27 | ], 28 | [ 29 | 10.132913589477539, 30 | 48.29067454025491 31 | ] 32 | ] 33 | } 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-ways-opposing=>1-way/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "oneway": 0 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 10.121197700500488, 19 | 48.28707663414987 20 | ], 21 | [ 22 | 10.125274658203125, 23 | 48.28790474610689 24 | ], 25 | [ 26 | 10.12986660003662, 27 | 48.289218275462225 28 | ], 29 | [ 30 | 10.132913589477539, 31 | 48.29067454025491 32 | ] 33 | ] 34 | } 35 | } 36 | ] 37 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-access-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "access": "yes" 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 10.121197700500488, 19 | 48.28707663414987 20 | ], 21 | [ 22 | 10.125274658203125, 23 | 48.28790474610689 24 | ], 25 | [ 26 | 10.12986660003662, 27 | 48.289218275462225 28 | ], 29 | [ 30 | 10.132913589477539, 31 | 48.29067454025491 32 | ] 33 | ] 34 | } 35 | } 36 | ] 37 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-highway-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "highway": "unclassified" 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 10.121197700500488, 19 | 48.28707663414987 20 | ], 21 | [ 22 | 10.125274658203125, 23 | 48.28790474610689 24 | ], 25 | [ 26 | 10.12986660003662, 27 | 48.289218275462225 28 | ], 29 | [ 30 | 10.132913589477539, 31 | 48.29067454025491 32 | ] 33 | ] 34 | } 35 | } 36 | ] 37 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-highway-merge/mixed-highway-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "highway": "primary" 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 10.121197700500488, 19 | 48.28707663414987 20 | ], 21 | [ 22 | 10.125274658203125, 23 | 48.28790474610689 24 | ], 25 | [ 26 | 10.12986660003662, 27 | 48.289218275462225 28 | ], 29 | [ 30 | 10.132913589477539, 31 | 48.29067454025491 32 | ] 33 | ] 34 | } 35 | } 36 | ] 37 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-bridge-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "highway": "primary", 13 | "bridge": "yes" 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | 10.121197700500488, 20 | 48.28707663414987 21 | ], 22 | [ 23 | 10.125274658203125, 24 | 48.28790474610689 25 | ], 26 | [ 27 | 10.12986660003662, 28 | 48.289218275462225 29 | ], 30 | [ 31 | 10.132913589477539, 32 | 48.29067454025491 33 | ] 34 | ] 35 | } 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-tunnel-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "highway": "primary", 13 | "tunnel": "yes" 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | 10.121197700500488, 20 | 48.28707663414987 21 | ], 22 | [ 23 | 10.125274658203125, 24 | 48.28790474610689 25 | ], 26 | [ 27 | 10.12986660003662, 28 | 48.289218275462225 29 | ], 30 | [ 31 | 10.132913589477539, 32 | 48.29067454025491 33 | ] 34 | ] 35 | } 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/keep-highway-tags-no-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "oneway": 0, 13 | "highway": "freeway" 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | 10.121197700500488, 20 | 48.28707663414987 21 | ], 22 | [ 23 | 10.125274658203125, 24 | 48.28790474610689 25 | ], 26 | [ 27 | 10.12986660003662, 28 | 48.289218275462225 29 | ], 30 | [ 31 | 10.132913589477539, 32 | 48.29067454025491 33 | ] 34 | ] 35 | } 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /test/split-ways.test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const test = require('tap').test; 4 | const normalizer = require('../'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const deepEqual = require('deep-equal'); 8 | 9 | test('split-ways', (t) => { 10 | const fixtures = fs.readdirSync(path.join(__dirname, './fixtures/split-ways/')); 11 | 12 | fixtures.forEach((fixture) => { 13 | const before = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/split-ways/', fixture, 'before'))); 14 | const after = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/split-ways/', fixture, 'after'))); 15 | 16 | const result = normalizer.splitWays(before); 17 | 18 | deepEqual(result, after, `${fixture } output matches expected result`); 19 | }); 20 | 21 | t.end(); 22 | }); 23 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/keep-highway-tags-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123,234,345", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ], 12 | "oneway": 0, 13 | "highway": "freeway", 14 | "access": "yes" 15 | }, 16 | "geometry": { 17 | "type": "LineString", 18 | "coordinates": [ 19 | [ 20 | 10.121197700500488, 21 | 48.28707663414987 22 | ], 23 | [ 24 | 10.125274658203125, 25 | 48.28790474610689 26 | ], 27 | [ 28 | 10.12986660003662, 29 | 48.289218275462225 30 | ], 31 | [ 32 | 10.132913589477539, 33 | 48.29067454025491 34 | ] 35 | ] 36 | } 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /bench/merge-ways.bench.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const Benchmark = require('benchmark'); 4 | const normalizer = require('../'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | const small = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/small.json'))); 9 | const medium = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/medium.json'))); 10 | const large = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/large.json'))); 11 | 12 | new Benchmark.Suite('merge-ways') 13 | .add('merge-ways # small', () => { 14 | normalizer.mergeWays(small); 15 | }) 16 | .add('merge-ways # medium', () => { 17 | normalizer.mergeWays(medium); 18 | }) 19 | .add('merge-ways # large', () => { 20 | normalizer.mergeWays(large); 21 | }) 22 | .on('cycle', (event) => { 23 | console.log(String(event.target)); 24 | }) 25 | .run(); 26 | -------------------------------------------------------------------------------- /bench/split-ways.bench.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const Benchmark = require('benchmark'); 4 | const normalizer = require('../'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | const small = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/small.json'))); 9 | const medium = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/medium.json'))); 10 | const large = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/large.json'))); 11 | 12 | new Benchmark.Suite('split-ways') 13 | .add('split-ways # small', () => { 14 | normalizer.splitWays(small); 15 | }) 16 | .add('split-ways # medium', () => { 17 | normalizer.splitWays(medium); 18 | }) 19 | .add('split-ways # large', () => { 20 | normalizer.splitWays(large); 21 | }) 22 | .on('cycle', (event) => { 23 | console.log(String(event.target)); 24 | }) 25 | .run(); 26 | -------------------------------------------------------------------------------- /test/fixtures/split-ways/1-way-loop/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4", 11 | "5", 12 | "2" 13 | ] 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | -76.97640538215637, 20 | 38.89013929623729 21 | ], 22 | [ 23 | -76.97659313678741, 24 | 38.89046497678951 25 | ], 26 | [ 27 | -76.9766253232956, 28 | 38.89106622927058 29 | ], 30 | [ 31 | -76.97428107261656, 32 | 38.891074579963636 33 | ], 34 | [ 35 | -76.97427034378052, 36 | 38.89046497678951 37 | ], 38 | [ 39 | -76.97659313678741, 40 | 38.89046497678951 41 | ] 42 | ] 43 | } 44 | } 45 | ] -------------------------------------------------------------------------------- /lib/unidirectional-ways.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Given ways with a oneway property, return ways with 'f' or 'r' 5 | * prepended to their ids, duplicating two directional ways. 6 | * @param {Object} ways an array of ways 7 | * @return {Object} ways another array of ways 8 | */ 9 | module.exports = function (ways) { 10 | const resultWays = []; 11 | for (let i = 0, n = ways.length; i < n; i++) { 12 | const way = ways[i]; 13 | if (way.properties.oneway === 0) { 14 | way.properties.oneway = 1; 15 | const reverseWay = JSON.parse(JSON.stringify(way)); 16 | reverseWay.geometry.coordinates.reverse(); 17 | reverseWay.properties.refs.reverse(); 18 | reverseWay.properties.id = `r${ reverseWay.properties.id}`; 19 | resultWays.push(reverseWay); 20 | } 21 | way.properties.id = `f${ way.properties.id}`; 22 | resultWays.push(way); 23 | } 24 | return resultWays; 25 | }; 26 | -------------------------------------------------------------------------------- /test/fixtures/split-ways/circle/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "502788725", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4", 11 | "5", 12 | "6", 13 | "1" 14 | ] 15 | }, 16 | "geometry": { 17 | "type": "LineString", 18 | "coordinates": [ 19 | [ 20 | 11.1374577, 21 | 49.4140842 22 | ], 23 | [ 24 | 11.1377995, 25 | 49.4138401 26 | ], 27 | [ 28 | 11.1376121, 29 | 49.413729 30 | ], 31 | [ 32 | 11.1374404, 33 | 49.4136273 34 | ], 35 | [ 36 | 11.1370987, 37 | 49.4138714 38 | ], 39 | [ 40 | 11.1372726, 41 | 49.4139744 42 | ], 43 | [ 44 | 11.1374577, 45 | 49.4140842 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/circle/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "502788725!0", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4", 11 | "5", 12 | "6", 13 | "1" 14 | ] 15 | }, 16 | "geometry": { 17 | "type": "LineString", 18 | "coordinates": [ 19 | [ 20 | 11.1374577, 21 | 49.4140842 22 | ], 23 | [ 24 | 11.1377995, 25 | 49.4138401 26 | ], 27 | [ 28 | 11.1376121, 29 | 49.413729 30 | ], 31 | [ 32 | 11.1374404, 33 | 49.4136273 34 | ], 35 | [ 36 | 11.1370987, 37 | 49.4138714 38 | ], 39 | [ 40 | 11.1372726, 41 | 49.4139744 42 | ], 43 | [ 44 | 11.1374577, 45 | 49.4140842 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/merge-ways.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const normalizer = require('../'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const deepEqual = require('deep-equal'); 6 | 7 | test('merge-ways', (t) => { 8 | const fixtures = fs.readdirSync(path.join(__dirname, './fixtures/merge-ways/')); 9 | 10 | fixtures.forEach((fixture) => { 11 | let options = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/merge-ways/', fixture, 'options'))); 12 | if (options === 'undefined') options = undefined; 13 | const before = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/merge-ways/', fixture, 'before'))); 14 | const after = JSON.parse(fs.readFileSync(path.join(__dirname, './fixtures/merge-ways/', fixture, 'after'))); 15 | 16 | const result = normalizer.mergeWays(before, options); 17 | deepEqual(result, after, `${fixture } output matches expected result`); 18 | }); 19 | 20 | t.end(); 21 | }); 22 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/options-{}/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 8.760030269622803, 17 | 11.87619136515638 18 | ], 19 | [ 20 | 8.766381740570068, 21 | 11.877913225380622 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 8.766381740570068, 41 | 11.877913225380622 42 | ], 43 | [ 44 | 8.776638507843018, 45 | 11.878648162406192 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/options-{}/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 8.760030269622803, 17 | 11.87619136515638 18 | ], 19 | [ 20 | 8.766381740570068, 21 | 11.877913225380622 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 8.766381740570068, 41 | 11.877913225380622 42 | ], 43 | [ 44 | 8.776638507843018, 45 | 11.878648162406192 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/options-undefined/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 8.760030269622803, 17 | 11.87619136515638 18 | ], 19 | [ 20 | 8.766381740570068, 21 | 11.877913225380622 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 8.766381740570068, 41 | 11.877913225380622 42 | ], 43 | [ 44 | 8.776638507843018, 45 | 11.878648162406192 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/options-undefined/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 8.760030269622803, 17 | 11.87619136515638 18 | ], 19 | [ 20 | 8.766381740570068, 21 | 11.877913225380622 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 8.766381740570068, 41 | 11.877913225380622 42 | ], 43 | [ 44 | 8.776638507843018, 45 | 11.878648162406192 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/merge-order/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "234", 6 | "refs": [ 7 | "2", 8 | "3" 9 | ], 10 | "oneway": 0 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.125274658203125, 17 | 48.28790474610689 18 | ], 19 | [ 20 | 10.12986660003662, 21 | 48.289218275462225 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "123", 30 | "refs": [ 31 | "1", 32 | "2" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.121197700500488, 41 | 48.28707663414987 42 | ], 43 | [ 44 | 10.125274658203125, 45 | 48.28790474610689 46 | ] 47 | ] 48 | } 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/1-oneway-1-bidirectional/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 8.760030269622803, 17 | 11.87619136515638 18 | ], 19 | [ 20 | 8.766381740570068, 21 | 11.877913225380622 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 8.766381740570068, 41 | 11.877913225380622 42 | ], 43 | [ 44 | 8.776638507843018, 45 | 11.878648162406192 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/1-oneway-1-bidirectional/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 8.760030269622803, 17 | 11.87619136515638 18 | ], 19 | [ 20 | 8.766381740570068, 21 | 11.877913225380622 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 8.766381740570068, 41 | 11.877913225380622 42 | ], 43 | [ 44 | 8.776638507843018, 45 | 11.878648162406192 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-oneways-same-direction/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 1 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-oneways-different-direction/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "3", 32 | "2" 33 | ], 34 | "oneway": 1 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.12986660003662, 41 | 48.289218275462225 42 | ], 43 | [ 44 | 10.125274658203125, 45 | 48.28790474610689 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-oneways-different-direction/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "3", 32 | "2" 33 | ], 34 | "oneway": 1 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.12986660003662, 41 | 48.289218275462225 42 | ], 43 | [ 44 | 10.125274658203125, 45 | 48.28790474610689 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-2-ways-different-directions=>1-way/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 0 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "3", 32 | "2" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.12986660003662, 41 | 48.289218275462225 42 | ], 43 | [ 44 | 10.125274658203125, 45 | 48.28790474610689 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/2-mergable-oneways-with-1-reversed/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 8.760030269622803, 17 | 11.87619136515638 18 | ], 19 | [ 20 | 8.766381740570068, 21 | 11.877913225380622 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "3", 32 | "2" 33 | ], 34 | "oneway": -1 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 8.776638507843018, 41 | 11.878648162406192 42 | ], 43 | [ 44 | 8.766381740570068, 45 | 11.877913225380622 46 | ] 47 | ] 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/2-way=>3-way-fork/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "geometry": { 5 | "type": "LineString", 6 | "coordinates": [ 7 | [ 8 | -80.04539966583252, 9 | 32.94613417084547 10 | ], 11 | [ 12 | -80.04416584968567, 13 | 32.94707502621693 14 | ], 15 | [ 16 | -80.04303395748138, 17 | 32.948033878087635 18 | ] 19 | ] 20 | }, 21 | "properties": { 22 | "id": "123", 23 | "refs": [ 24 | "1", 25 | "2", 26 | "3" 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "geometry": { 33 | "type": "LineString", 34 | "coordinates": [ 35 | [ 36 | -80.04416584968567, 37 | 32.94707502621693 38 | ], 39 | [ 40 | -80.04275500774384, 41 | 32.9476872520858 42 | ] 43 | ] 44 | }, 45 | "properties": { 46 | "id": "234", 47 | "refs": [ 48 | "2", 49 | "4" 50 | ] 51 | } 52 | } 53 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-highway-no-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "345", 6 | "refs": [ 7 | "3", 8 | "4" 9 | ], 10 | "highway": "secondary" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.12986660003662, 17 | 48.289218275462225 18 | ], 19 | [ 20 | 10.132913589477539, 21 | 48.29067454025491 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "123,234", 30 | "refs": [ 31 | "1", 32 | "2", 33 | "3" 34 | ], 35 | "highway": "primary" 36 | }, 37 | "geometry": { 38 | "type": "LineString", 39 | "coordinates": [ 40 | [ 41 | 10.121197700500488, 42 | 48.28707663414987 43 | ], 44 | [ 45 | 10.125274658203125, 46 | 48.28790474610689 47 | ], 48 | [ 49 | 10.12986660003662, 50 | 48.289218275462225 51 | ] 52 | ] 53 | } 54 | } 55 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-bridge-no-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "345", 6 | "refs": [ 7 | "3", 8 | "4" 9 | ], 10 | "highway": "primary", 11 | "tunnel": "yes" 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 10.12986660003662, 18 | 48.289218275462225 19 | ], 20 | [ 21 | 10.132913589477539, 22 | 48.29067454025491 23 | ] 24 | ] 25 | } 26 | }, 27 | { 28 | "type": "Feature", 29 | "properties": { 30 | "id": "123,234", 31 | "refs": [ 32 | "1", 33 | "2", 34 | "3" 35 | ], 36 | "highway": "primary" 37 | }, 38 | "geometry": { 39 | "type": "LineString", 40 | "coordinates": [ 41 | [ 42 | 10.121197700500488, 43 | 48.28707663414987 44 | ], 45 | [ 46 | 10.125274658203125, 47 | 48.28790474610689 48 | ], 49 | [ 50 | 10.12986660003662, 51 | 48.289218275462225 52 | ] 53 | ] 54 | } 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-tunnel-no-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "345", 6 | "refs": [ 7 | "3", 8 | "4" 9 | ], 10 | "highway": "primary", 11 | "bridge": "yes" 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 10.12986660003662, 18 | 48.289218275462225 19 | ], 20 | [ 21 | 10.132913589477539, 22 | 48.29067454025491 23 | ] 24 | ] 25 | } 26 | }, 27 | { 28 | "type": "Feature", 29 | "properties": { 30 | "id": "123,234", 31 | "refs": [ 32 | "1", 33 | "2", 34 | "3" 35 | ], 36 | "highway": "primary" 37 | }, 38 | "geometry": { 39 | "type": "LineString", 40 | "coordinates": [ 41 | [ 42 | 10.121197700500488, 43 | 48.28707663414987 44 | ], 45 | [ 46 | 10.125274658203125, 47 | 48.28790474610689 48 | ], 49 | [ 50 | 10.12986660003662, 51 | 48.289218275462225 52 | ] 53 | ] 54 | } 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /test/fixtures/split-ways/2-way=>4-way-intersection/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ] 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | -77.00896203517914, 17 | 38.903733101715716 18 | ], 19 | [ 20 | -77.00587749481201, 21 | 38.90373727631765 22 | ], 23 | [ 24 | -77.00355470180511, 25 | 38.903741450919384 26 | ] 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "properties": { 33 | "id": "234", 34 | "refs": [ 35 | "4", 36 | "2", 37 | "6" 38 | ] 39 | }, 40 | "geometry": { 41 | "type": "LineString", 42 | "coordinates": [ 43 | [ 44 | -77.00587749481201, 45 | 38.90561164780041 46 | ], 47 | [ 48 | -77.00587749481201, 49 | 38.90373727631765 50 | ], 51 | [ 52 | -77.00583457946777, 53 | 38.90247236069279 54 | ] 55 | ] 56 | } 57 | } 58 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/loop-with-exit/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "504179333", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "1", 11 | "4" 12 | ] 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | -121.0013996, 19 | 38.6593683 20 | ], 21 | [ 22 | -121.0016128, 23 | 38.6591379 24 | ], 25 | [ 26 | -121.0011488, 27 | 38.6590688 28 | ], 29 | [ 30 | -121.0013996, 31 | 38.6593683 32 | ], 33 | [ 34 | -121.0016243, 35 | 38.6595081 36 | ] 37 | ] 38 | } 39 | }, 40 | { 41 | "type": "Feature", 42 | "properties": { 43 | "id": "504179332", 44 | "refs": [ 45 | "3", 46 | "5" 47 | ] 48 | }, 49 | "geometry": { 50 | "type": "LineString", 51 | "coordinates": [ 52 | [ 53 | -121.0011488, 54 | 38.6590688 55 | ], 56 | [ 57 | -121.0008082, 58 | 38.6592395 59 | ] 60 | ] 61 | } 62 | } 63 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/1-way-loop/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123!0", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ] 10 | }, 11 | "geometry": { 12 | "type": "LineString", 13 | "coordinates": [ 14 | [ 15 | -76.97640538215637, 16 | 38.89013929623729 17 | ], 18 | [ 19 | -76.97659313678741, 20 | 38.89046497678951 21 | ] 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": { 28 | "id": "123!1", 29 | "refs": [ 30 | "2", 31 | "3", 32 | "4", 33 | "5", 34 | "2" 35 | ] 36 | }, 37 | "geometry": { 38 | "type": "LineString", 39 | "coordinates": [ 40 | [ 41 | -76.97659313678741, 42 | 38.89046497678951 43 | ], 44 | [ 45 | -76.9766253232956, 46 | 38.89106622927058 47 | ], 48 | [ 49 | -76.97428107261656, 50 | 38.891074579963636 51 | ], 52 | [ 53 | -76.97427034378052, 54 | 38.89046497678951 55 | ], 56 | [ 57 | -76.97659313678741, 58 | 38.89046497678951 59 | ] 60 | ] 61 | } 62 | } 63 | ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/graph-normalizer", 3 | "version": "4.0.0", 4 | "description": "Takes nodes and ways and turn them into a normalized graph of intersections and ways.", 5 | "main": "./index.js", 6 | "engines": { 7 | "node": "24.2.0" 8 | }, 9 | "scripts": { 10 | "test": "npm run lint && tap -R spec test/*.test.js", 11 | "lint": "eslint '*.js' './**/*.js'", 12 | "lint:fix": "eslint --fix '*.js' './**/*.js'", 13 | "bench": "for f in `ls ./bench/*.bench.js`; do node $f; done" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/mapbox/graph-normalizer.git" 18 | }, 19 | "keywords": [ 20 | "graph" 21 | ], 22 | "author": "benjamintd", 23 | "license": "ISC", 24 | "dependencies": { 25 | "byline": "^5.0.0", 26 | "concat-stream": "^2.0.0", 27 | "deep-equal": "^2.2.3", 28 | "graceful-fs": "^4.2.11", 29 | "minimist": "^1.2.6", 30 | "through2": "^4.0.2", 31 | "turf-linestring": "^1.0.2" 32 | }, 33 | "devDependencies": { 34 | "benchmark": "^2.1.4", 35 | "eslint": "^9.28.0", 36 | "eslint-config-mourner": "^4.0.2", 37 | "tap": "^21.1.0" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/mapbox/graph-normalizer/issues" 41 | }, 42 | "homepage": "https://github.com/mapbox/graph-normalizer#readme", 43 | "directories": { 44 | "test": "test" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2016-2020, Mapbox, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-straight-ways=>1-way/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ] 10 | }, 11 | "geometry": { 12 | "type": "LineString", 13 | "coordinates": [ 14 | [ 15 | 10.121197700500488, 16 | 48.28707663414987 17 | ], 18 | [ 19 | 10.125274658203125, 20 | 48.28790474610689 21 | ] 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": { 28 | "id": "234", 29 | "refs": [ 30 | "2", 31 | "3" 32 | ] 33 | }, 34 | "geometry": { 35 | "type": "LineString", 36 | "coordinates": [ 37 | [ 38 | 10.125274658203125, 39 | 48.28790474610689 40 | ], 41 | [ 42 | 10.12986660003662, 43 | 48.289218275462225 44 | ] 45 | ] 46 | } 47 | }, 48 | { 49 | "type": "Feature", 50 | "properties": { 51 | "id": "345", 52 | "refs": [ 53 | "3", 54 | "4" 55 | ] 56 | }, 57 | "geometry": { 58 | "type": "LineString", 59 | "coordinates": [ 60 | [ 61 | 10.12986660003662, 62 | 48.289218275462225 63 | ], 64 | [ 65 | 10.132913589477539, 66 | 48.29067454025491 67 | ] 68 | ] 69 | } 70 | } 71 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/2-way=>3-way-fork/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "geometry": { 5 | "type": "LineString", 6 | "coordinates": [ 7 | [ 8 | -80.04539966583252, 9 | 32.94613417084547 10 | ], 11 | [ 12 | -80.04416584968567, 13 | 32.94707502621693 14 | ] 15 | ] 16 | }, 17 | "properties": { 18 | "id": "123!0", 19 | "refs": [ 20 | "1", 21 | "2" 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "geometry": { 28 | "type": "LineString", 29 | "coordinates": [ 30 | [ 31 | -80.04416584968567, 32 | 32.94707502621693 33 | ], 34 | [ 35 | -80.04303395748138, 36 | 32.948033878087635 37 | ] 38 | ] 39 | }, 40 | "properties": { 41 | "id": "123!1", 42 | "refs": [ 43 | "2", 44 | "3" 45 | ] 46 | } 47 | }, 48 | { 49 | "type": "Feature", 50 | "geometry": { 51 | "type": "LineString", 52 | "coordinates": [ 53 | [ 54 | -80.04416584968567, 55 | 32.94707502621693 56 | ], 57 | [ 58 | -80.04275500774384, 59 | 32.9476872520858 60 | ] 61 | ] 62 | }, 63 | "properties": { 64 | "id": "234!0", 65 | "refs": [ 66 | "2", 67 | "4" 68 | ] 69 | } 70 | } 71 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-ways-backwards=>1-way/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "2", 8 | "1" 9 | ] 10 | }, 11 | "geometry": { 12 | "type": "LineString", 13 | "coordinates": [ 14 | [ 15 | 10.125274658203125, 16 | 48.28790474610689 17 | ], 18 | [ 19 | 10.121197700500488, 20 | 48.28707663414987 21 | ] 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": { 28 | "id": "234", 29 | "refs": [ 30 | "3", 31 | "2" 32 | ] 33 | }, 34 | "geometry": { 35 | "type": "LineString", 36 | "coordinates": [ 37 | [ 38 | 10.12986660003662, 39 | 48.289218275462225 40 | ], 41 | [ 42 | 10.125274658203125, 43 | 48.28790474610689 44 | ] 45 | ] 46 | } 47 | }, 48 | { 49 | "type": "Feature", 50 | "properties": { 51 | "id": "345", 52 | "refs": [ 53 | "4", 54 | "3" 55 | ] 56 | }, 57 | "geometry": { 58 | "type": "LineString", 59 | "coordinates": [ 60 | [ 61 | 10.132913589477539, 62 | 48.29067454025491 63 | ], 64 | [ 65 | 10.12986660003662, 66 | 48.289218275462225 67 | ] 68 | ] 69 | } 70 | } 71 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/keep-tags/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ], 11 | "oneway": 0, 12 | "highway": "primary", 13 | "access": "yes" 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | -77.00896203517914, 20 | 38.903733101715716 21 | ], 22 | [ 23 | -77.00587749481201, 24 | 38.90373727631765 25 | ], 26 | [ 27 | -77.00355470180511, 28 | 38.903741450919384 29 | ] 30 | ] 31 | } 32 | }, 33 | { 34 | "type": "Feature", 35 | "properties": { 36 | "id": "234", 37 | "refs": [ 38 | "4", 39 | "2", 40 | "6" 41 | ], 42 | "oneway": 1, 43 | "highway": "secondary", 44 | "name": "Market Street", 45 | "ref": "I-70", 46 | "surface": "paved", 47 | "access": "no" 48 | }, 49 | "geometry": { 50 | "type": "LineString", 51 | "coordinates": [ 52 | [ 53 | -77.00587749481201, 54 | 38.90561164780041 55 | ], 56 | [ 57 | -77.00587749481201, 58 | 38.90373727631765 59 | ], 60 | [ 61 | -77.00583457946777, 62 | 38.90247236069279 63 | ] 64 | ] 65 | } 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /bench/fixtures/small.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 0 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "4", 56 | "3" 57 | ], 58 | "oneway": 0 59 | }, 60 | "geometry": { 61 | "type": "LineString", 62 | "coordinates": [ 63 | [ 64 | 10.132913589477539, 65 | 48.29067454025491 66 | ], 67 | [ 68 | 10.12986660003662, 69 | 48.289218275462225 70 | ] 71 | ] 72 | } 73 | } 74 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-access-no-merge/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "access": "no" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "access": "yes" 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "3", 56 | "4" 57 | ] 58 | }, 59 | "geometry": { 60 | "type": "LineString", 61 | "coordinates": [ 62 | [ 63 | 10.12986660003662, 64 | 48.289218275462225 65 | ], 66 | [ 67 | 10.132913589477539, 68 | 48.29067454025491 69 | ] 70 | ] 71 | } 72 | } 73 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-access-no-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "access": "no" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "access": "yes" 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "3", 56 | "4" 57 | ] 58 | }, 59 | "geometry": { 60 | "type": "LineString", 61 | "coordinates": [ 62 | [ 63 | 10.12986660003662, 64 | 48.289218275462225 65 | ], 66 | [ 67 | 10.132913589477539, 68 | 48.29067454025491 69 | ] 70 | ] 71 | } 72 | } 73 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-access-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ] 10 | }, 11 | "geometry": { 12 | "type": "LineString", 13 | "coordinates": [ 14 | [ 15 | 10.121197700500488, 16 | 48.28707663414987 17 | ], 18 | [ 19 | 10.125274658203125, 20 | 48.28790474610689 21 | ] 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": { 28 | "id": "234", 29 | "refs": [ 30 | "2", 31 | "3" 32 | ], 33 | "access": "yes" 34 | }, 35 | "geometry": { 36 | "type": "LineString", 37 | "coordinates": [ 38 | [ 39 | 10.125274658203125, 40 | 48.28790474610689 41 | ], 42 | [ 43 | 10.12986660003662, 44 | 48.289218275462225 45 | ] 46 | ] 47 | } 48 | }, 49 | { 50 | "type": "Feature", 51 | "properties": { 52 | "id": "345", 53 | "refs": [ 54 | "3", 55 | "4" 56 | ], 57 | "access": "no" 58 | }, 59 | "geometry": { 60 | "type": "LineString", 61 | "coordinates": [ 62 | [ 63 | 10.12986660003662, 64 | 48.289218275462225 65 | ], 66 | [ 67 | 10.132913589477539, 68 | 48.29067454025491 69 | ] 70 | ] 71 | } 72 | } 73 | ] 74 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-ways-opposing=>1-way/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 0 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 0 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "4", 56 | "3" 57 | ], 58 | "oneway": 0 59 | }, 60 | "geometry": { 61 | "type": "LineString", 62 | "coordinates": [ 63 | [ 64 | 10.132913589477539, 65 | 48.29067454025491 66 | ], 67 | [ 68 | 10.12986660003662, 69 | 48.289218275462225 70 | ] 71 | ] 72 | } 73 | } 74 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-opposing-oneways=>unchanged/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "2", 8 | "1" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.125274658203125, 17 | 48.28790474610689 18 | ], 19 | [ 20 | 10.121197700500488, 21 | 48.28707663414987 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 1 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "4", 56 | "3" 57 | ], 58 | "oneway": 1 59 | }, 60 | "geometry": { 61 | "type": "LineString", 62 | "coordinates": [ 63 | [ 64 | 10.132913589477539, 65 | 48.29067454025491 66 | ], 67 | [ 68 | 10.12986660003662, 69 | 48.289218275462225 70 | ] 71 | ] 72 | } 73 | } 74 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/3-opposing-oneways=>unchanged/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "2", 8 | "1" 9 | ], 10 | "oneway": 1 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.125274658203125, 17 | 48.28790474610689 18 | ], 19 | [ 20 | 10.121197700500488, 21 | 48.28707663414987 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "oneway": 1 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "4", 56 | "3" 57 | ], 58 | "oneway": 1 59 | }, 60 | "geometry": { 61 | "type": "LineString", 62 | "coordinates": [ 63 | [ 64 | 10.132913589477539, 65 | 48.29067454025491 66 | ], 67 | [ 68 | 10.12986660003662, 69 | 48.289218275462225 70 | ] 71 | ] 72 | } 73 | } 74 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-highway-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "highway": "primary" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "highway": "primary" 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "3", 56 | "4" 57 | ], 58 | "highway": "secondary" 59 | }, 60 | "geometry": { 61 | "type": "LineString", 62 | "coordinates": [ 63 | [ 64 | 10.12986660003662, 65 | 48.289218275462225 66 | ], 67 | [ 68 | 10.132913589477539, 69 | 48.29067454025491 70 | ] 71 | ] 72 | } 73 | } 74 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-highway-no-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "highway": "primary" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "highway": "primary" 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "3", 56 | "4" 57 | ], 58 | "highway": "secondary" 59 | }, 60 | "geometry": { 61 | "type": "LineString", 62 | "coordinates": [ 63 | [ 64 | 10.12986660003662, 65 | 48.289218275462225 66 | ], 67 | [ 68 | 10.132913589477539, 69 | 48.29067454025491 70 | ] 71 | ] 72 | } 73 | } 74 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/loop-with-intersection/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "502788725", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4", 11 | "5", 12 | "6", 13 | "1", 14 | "7" 15 | ], 16 | "oneway": 0 17 | }, 18 | "geometry": { 19 | "type": "LineString", 20 | "coordinates": [ 21 | [ 22 | 11.1374577, 23 | 49.4140842 24 | ], 25 | [ 26 | 11.1377995, 27 | 49.4138401 28 | ], 29 | [ 30 | 11.1376121, 31 | 49.413729 32 | ], 33 | [ 34 | 11.1374404, 35 | 49.4136273 36 | ], 37 | [ 38 | 11.1370987, 39 | 49.4138714 40 | ], 41 | [ 42 | 11.1372726, 43 | 49.4139744 44 | ], 45 | [ 46 | 11.1374577, 47 | 49.4140842 48 | ], 49 | [ 50 | 11.1373071, 51 | 49.4141971 52 | ] 53 | ] 54 | } 55 | }, 56 | { 57 | "type": "Feature", 58 | "properties": { 59 | "id": "502788724", 60 | "refs": [ 61 | "6", 62 | "3" 63 | ] 64 | }, 65 | "geometry": { 66 | "type": "LineString", 67 | "coordinates": [ 68 | [ 69 | 11.1372726, 70 | 49.4139744 71 | ], 72 | [ 73 | 11.1376121, 74 | 49.413729 75 | ] 76 | ] 77 | } 78 | } 79 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/3-way=>4-way-intersection/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ] 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | -77.00896203517914, 17 | 38.903733101715716 18 | ], 19 | [ 20 | -77.00587749481201, 21 | 38.90373727631765 22 | ], 23 | [ 24 | -77.00355470180511, 25 | 38.903741450919384 26 | ] 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "properties": { 33 | "id": "234", 34 | "refs": [ 35 | "4", 36 | "2" 37 | ] 38 | }, 39 | "geometry": { 40 | "type": "LineString", 41 | "coordinates": [ 42 | [ 43 | -77.00587749481201, 44 | 38.90561164780041 45 | ], 46 | [ 47 | -77.00587749481201, 48 | 38.90373727631765 49 | ] 50 | ] 51 | } 52 | }, 53 | { 54 | "type": "Feature", 55 | "properties": { 56 | "id": "345", 57 | "refs": [ 58 | "2", 59 | "6" 60 | ] 61 | }, 62 | "geometry": { 63 | "type": "LineString", 64 | "coordinates": [ 65 | [ 66 | -77.00587749481201, 67 | 38.90373727631765 68 | ], 69 | [ 70 | -77.00583457946777, 71 | 38.90247236069279 72 | ] 73 | ] 74 | } 75 | } 76 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-bridge-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "highway": "primary" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "highway": "primary", 35 | "bridge": "yes" 36 | }, 37 | "geometry": { 38 | "type": "LineString", 39 | "coordinates": [ 40 | [ 41 | 10.125274658203125, 42 | 48.28790474610689 43 | ], 44 | [ 45 | 10.12986660003662, 46 | 48.289218275462225 47 | ] 48 | ] 49 | } 50 | }, 51 | { 52 | "type": "Feature", 53 | "properties": { 54 | "id": "345", 55 | "refs": [ 56 | "3", 57 | "4" 58 | ], 59 | "highway": "primary" 60 | }, 61 | "geometry": { 62 | "type": "LineString", 63 | "coordinates": [ 64 | [ 65 | 10.12986660003662, 66 | 48.289218275462225 67 | ], 68 | [ 69 | 10.132913589477539, 70 | 48.29067454025491 71 | ] 72 | ] 73 | } 74 | } 75 | ] 76 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-tunnel-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "highway": "primary" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "highway": "primary", 35 | "tunnel": "yes" 36 | }, 37 | "geometry": { 38 | "type": "LineString", 39 | "coordinates": [ 40 | [ 41 | 10.125274658203125, 42 | 48.28790474610689 43 | ], 44 | [ 45 | 10.12986660003662, 46 | 48.289218275462225 47 | ] 48 | ] 49 | } 50 | }, 51 | { 52 | "type": "Feature", 53 | "properties": { 54 | "id": "345", 55 | "refs": [ 56 | "3", 57 | "4" 58 | ], 59 | "highway": "primary" 60 | }, 61 | "geometry": { 62 | "type": "LineString", 63 | "coordinates": [ 64 | [ 65 | 10.12986660003662, 66 | 48.289218275462225 67 | ], 68 | [ 69 | 10.132913589477539, 70 | 48.29067454025491 71 | ] 72 | ] 73 | } 74 | } 75 | ] 76 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-bridge-no-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "highway": "primary" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "highway": "primary" 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "3", 56 | "4" 57 | ], 58 | "highway": "primary", 59 | "tunnel": "yes" 60 | }, 61 | "geometry": { 62 | "type": "LineString", 63 | "coordinates": [ 64 | [ 65 | 10.12986660003662, 66 | 48.289218275462225 67 | ], 68 | [ 69 | 10.132913589477539, 70 | 48.29067454025491 71 | ] 72 | ] 73 | } 74 | } 75 | ] 76 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/mixed-tunnel-no-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "highway": "primary" 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | 10.121197700500488, 17 | 48.28707663414987 18 | ], 19 | [ 20 | 10.125274658203125, 21 | 48.28790474610689 22 | ] 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "Feature", 28 | "properties": { 29 | "id": "234", 30 | "refs": [ 31 | "2", 32 | "3" 33 | ], 34 | "highway": "primary" 35 | }, 36 | "geometry": { 37 | "type": "LineString", 38 | "coordinates": [ 39 | [ 40 | 10.125274658203125, 41 | 48.28790474610689 42 | ], 43 | [ 44 | 10.12986660003662, 45 | 48.289218275462225 46 | ] 47 | ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "id": "345", 54 | "refs": [ 55 | "3", 56 | "4" 57 | ], 58 | "highway": "primary", 59 | "bridge": "yes" 60 | }, 61 | "geometry": { 62 | "type": "LineString", 63 | "coordinates": [ 64 | [ 65 | 10.12986660003662, 66 | 48.289218275462225 67 | ], 68 | [ 69 | 10.132913589477539, 70 | 48.29067454025491 71 | ] 72 | ] 73 | } 74 | } 75 | ] 76 | -------------------------------------------------------------------------------- /test/fixtures/merge-ways/keep-highway-tags-no-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 0, 11 | "highway": "freeway" 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | 10.121197700500488, 18 | 48.28707663414987 19 | ], 20 | [ 21 | 10.125274658203125, 22 | 48.28790474610689 23 | ] 24 | ] 25 | } 26 | }, 27 | { 28 | "type": "Feature", 29 | "properties": { 30 | "id": "234", 31 | "refs": [ 32 | "2", 33 | "3" 34 | ], 35 | "oneway": 0, 36 | "highway": "freeway" 37 | }, 38 | "geometry": { 39 | "type": "LineString", 40 | "coordinates": [ 41 | [ 42 | 10.125274658203125, 43 | 48.28790474610689 44 | ], 45 | [ 46 | 10.12986660003662, 47 | 48.289218275462225 48 | ] 49 | ] 50 | } 51 | }, 52 | { 53 | "type": "Feature", 54 | "properties": { 55 | "id": "345", 56 | "refs": [ 57 | "3", 58 | "4" 59 | ], 60 | "oneway": 0, 61 | "highway": "freeway" 62 | }, 63 | "geometry": { 64 | "type": "LineString", 65 | "coordinates": [ 66 | [ 67 | 10.12986660003662, 68 | 48.289218275462225 69 | ], 70 | [ 71 | 10.132913589477539, 72 | 48.29067454025491 73 | ] 74 | ] 75 | } 76 | } 77 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/1-way-loop-with-exit/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "504179333!0", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ], 11 | "oneway": 1 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | -121.0013996, 18 | 38.6593683 19 | ], 20 | [ 21 | -121.0016128, 22 | 38.6591379 23 | ], 24 | [ 25 | -121.0011488, 26 | 38.6590688 27 | ] 28 | ] 29 | } 30 | }, 31 | { 32 | "type": "Feature", 33 | "properties": { 34 | "id": "504179333!1", 35 | "refs": [ 36 | "3", 37 | "1", 38 | "4" 39 | ], 40 | "oneway": 1 41 | }, 42 | "geometry": { 43 | "type": "LineString", 44 | "coordinates": [ 45 | [ 46 | -121.0011488, 47 | 38.6590688 48 | ], 49 | [ 50 | -121.0013996, 51 | 38.6593683 52 | ], 53 | [ 54 | -121.0016243, 55 | 38.6595081 56 | ] 57 | ] 58 | } 59 | }, 60 | { 61 | "type": "Feature", 62 | "properties": { 63 | "id": "504179332!0", 64 | "refs": [ 65 | "3", 66 | "5" 67 | ] 68 | }, 69 | "geometry": { 70 | "type": "LineString", 71 | "coordinates": [ 72 | [ 73 | -121.0011488, 74 | 38.6590688 75 | ], 76 | [ 77 | -121.0008082, 78 | 38.6592395 79 | ] 80 | ] 81 | } 82 | } 83 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/1-way-loop-with-exit/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "504179333!0", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ], 11 | "oneway": 1 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | -121.0013996, 18 | 38.6593683 19 | ], 20 | [ 21 | -121.0016128, 22 | 38.6591379 23 | ], 24 | [ 25 | -121.0011488, 26 | 38.6590688 27 | ] 28 | ] 29 | } 30 | }, 31 | { 32 | "type": "Feature", 33 | "properties": { 34 | "id": "504179333!1", 35 | "refs": [ 36 | "3", 37 | "1", 38 | "4" 39 | ], 40 | "oneway": 1 41 | }, 42 | "geometry": { 43 | "type": "LineString", 44 | "coordinates": [ 45 | [ 46 | -121.0011488, 47 | 38.6590688 48 | ], 49 | [ 50 | -121.0013996, 51 | 38.6593683 52 | ], 53 | [ 54 | -121.0016243, 55 | 38.6595081 56 | ] 57 | ] 58 | } 59 | }, 60 | { 61 | "type": "Feature", 62 | "properties": { 63 | "id": "504179332!0", 64 | "refs": [ 65 | "3", 66 | "5" 67 | ] 68 | }, 69 | "geometry": { 70 | "type": "LineString", 71 | "coordinates": [ 72 | [ 73 | -121.0011488, 74 | 38.6590688 75 | ], 76 | [ 77 | -121.0008082, 78 | 38.6592395 79 | ] 80 | ] 81 | } 82 | } 83 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/1-way-multi-intersection/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3", 10 | "4" 11 | ] 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | -77.0035171508789, 18 | 38.90253915548077 19 | ], 20 | [ 21 | -77.00203657150269, 22 | 38.90253915548077 23 | ], 24 | [ 25 | -77.00051307678223, 26 | 38.902522456789654 27 | ], 28 | [ 29 | -76.99952602386475, 30 | 38.90253915548077 31 | ] 32 | ] 33 | } 34 | }, 35 | { 36 | "type": "Feature", 37 | "properties": { 38 | "id": "234", 39 | "refs": [ 40 | "5", 41 | "2" 42 | ] 43 | }, 44 | "geometry": { 45 | "type": "LineString", 46 | "coordinates": [ 47 | [ 48 | -77.00199365615845, 49 | 38.903758149323785 50 | ], 51 | [ 52 | -77.00203657150269, 53 | 38.90253915548077 54 | ] 55 | ] 56 | } 57 | }, 58 | { 59 | "type": "Feature", 60 | "properties": { 61 | "id": "345", 62 | "refs": [ 63 | "6", 64 | "3" 65 | ] 66 | }, 67 | "geometry": { 68 | "type": "LineString", 69 | "coordinates": [ 70 | [ 71 | -77.00055599212646, 72 | 38.901253344774105 73 | ], 74 | [ 75 | -77.00051307678223, 76 | 38.902522456789654 77 | ] 78 | ] 79 | } 80 | } 81 | ] -------------------------------------------------------------------------------- /test/fixtures/merge-ways/keep-highway-tags-merge/before: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 0, 11 | "highway": "freeway", 12 | "access": "yes" 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 10.121197700500488, 19 | 48.28707663414987 20 | ], 21 | [ 22 | 10.125274658203125, 23 | 48.28790474610689 24 | ] 25 | ] 26 | } 27 | }, 28 | { 29 | "type": "Feature", 30 | "properties": { 31 | "id": "234", 32 | "refs": [ 33 | "2", 34 | "3" 35 | ], 36 | "oneway": 0, 37 | "highway": "freeway", 38 | "access": "yes" 39 | }, 40 | "geometry": { 41 | "type": "LineString", 42 | "coordinates": [ 43 | [ 44 | 10.125274658203125, 45 | 48.28790474610689 46 | ], 47 | [ 48 | 10.12986660003662, 49 | 48.289218275462225 50 | ] 51 | ] 52 | } 53 | }, 54 | { 55 | "type": "Feature", 56 | "properties": { 57 | "id": "345", 58 | "refs": [ 59 | "3", 60 | "4" 61 | ], 62 | "oneway": 0, 63 | "highway": "freeway", 64 | "access": "yes" 65 | }, 66 | "geometry": { 67 | "type": "LineString", 68 | "coordinates": [ 69 | [ 70 | 10.12986660003662, 71 | 48.289218275462225 72 | ], 73 | [ 74 | 10.132913589477539, 75 | 48.29067454025491 76 | ] 77 | ] 78 | } 79 | } 80 | ] 81 | -------------------------------------------------------------------------------- /test/unidirectional-ways.test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const test = require('tap').test; 4 | const normalizer = require('../'); 5 | 6 | test('unidirectional-ways', (t) => { 7 | const ways = [ 8 | { 9 | 'type': 'Feature', 10 | 'geometry': {'type': 'LineString', 'coordinates': [[-121.640738, 36.658681], [-121.640598, 36.658655], [-121.640459, 36.658638]]}, 11 | 'properties': {'id': '1', 'refs': ['90004257', '90004259', '90004261'], 'oneway': 0, 'highway': 'secondary'} 12 | }, 13 | { 14 | 'type': 'Feature', 15 | 'geometry': {'type': 'LineString', 'coordinates': [[-121.640067, 36.660187], [-121.641553, 36.662188]]}, 16 | 'properties': {'id': '2', 'refs': ['89888084', '89888188'], 'oneway': 1, 'highway': 'primary'} 17 | } 18 | ]; 19 | 20 | const results = [ 21 | { 22 | 'type': 'Feature', 23 | 'geometry': {'type': 'LineString', 'coordinates': [[-121.640459, 36.658638], [-121.640598, 36.658655], [-121.640738, 36.658681]]}, 24 | 'properties': {'id': 'r1', 'refs': ['90004261', '90004259', '90004257'], 'oneway': 1, 'highway': 'secondary'} 25 | }, 26 | { 27 | 'type': 'Feature', 28 | 'geometry': {'type': 'LineString', 'coordinates': [[-121.640738, 36.658681], [-121.640598, 36.658655], [-121.640459, 36.658638]]}, 29 | 'properties': {'id': 'f1', 'refs': ['90004257', '90004259', '90004261'], 'oneway': 1, 'highway': 'secondary'} 30 | }, 31 | { 32 | 'type': 'Feature', 33 | 'geometry': {'type': 'LineString', 'coordinates': [[-121.640067, 36.660187], [-121.641553, 36.662188]]}, 34 | 'properties': {'id': 'f2', 'refs': ['89888084', '89888188'], 'oneway': 1, 'highway': 'primary'} 35 | } 36 | ]; 37 | 38 | t.same(normalizer.unidirectionalWays(ways), results); 39 | t.end(); 40 | }); 41 | -------------------------------------------------------------------------------- /test/fixtures/split-ways/2-way=>4-way-intersection/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123!0", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ] 10 | }, 11 | "geometry": { 12 | "type": "LineString", 13 | "coordinates": [ 14 | [ 15 | -77.00896203517914, 16 | 38.903733101715716 17 | ], 18 | [ 19 | -77.00587749481201, 20 | 38.90373727631765 21 | ] 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": { 28 | "id": "123!1", 29 | "refs": [ 30 | "2", 31 | "3" 32 | ] 33 | }, 34 | "geometry": { 35 | "type": "LineString", 36 | "coordinates": [ 37 | [ 38 | -77.00587749481201, 39 | 38.90373727631765 40 | ], 41 | [ 42 | -77.00355470180511, 43 | 38.903741450919384 44 | ] 45 | ] 46 | } 47 | }, 48 | { 49 | "type": "Feature", 50 | "properties": { 51 | "id": "234!0", 52 | "refs": [ 53 | "4", 54 | "2" 55 | ] 56 | }, 57 | "geometry": { 58 | "type": "LineString", 59 | "coordinates": [ 60 | [ 61 | -77.00587749481201, 62 | 38.90561164780041 63 | ], 64 | [ 65 | -77.00587749481201, 66 | 38.90373727631765 67 | ] 68 | ] 69 | } 70 | }, 71 | { 72 | "type": "Feature", 73 | "properties": { 74 | "id": "234!1", 75 | "refs": [ 76 | "2", 77 | "6" 78 | ] 79 | }, 80 | "geometry": { 81 | "type": "LineString", 82 | "coordinates": [ 83 | [ 84 | -77.00587749481201, 85 | 38.90373727631765 86 | ], 87 | [ 88 | -77.00583457946777, 89 | 38.90247236069279 90 | ] 91 | ] 92 | } 93 | } 94 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/3-way=>4-way-intersection/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123!0", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ] 10 | }, 11 | "geometry": { 12 | "type": "LineString", 13 | "coordinates": [ 14 | [ 15 | -77.00896203517914, 16 | 38.903733101715716 17 | ], 18 | [ 19 | -77.00587749481201, 20 | 38.90373727631765 21 | ] 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": { 28 | "id": "123!1", 29 | "refs": [ 30 | "2", 31 | "3" 32 | ] 33 | }, 34 | "geometry": { 35 | "type": "LineString", 36 | "coordinates": [ 37 | [ 38 | -77.00587749481201, 39 | 38.90373727631765 40 | ], 41 | [ 42 | -77.00355470180511, 43 | 38.903741450919384 44 | ] 45 | ] 46 | } 47 | }, 48 | { 49 | "type": "Feature", 50 | "properties": { 51 | "id": "234!0", 52 | "refs": [ 53 | "4", 54 | "2" 55 | ] 56 | }, 57 | "geometry": { 58 | "type": "LineString", 59 | "coordinates": [ 60 | [ 61 | -77.00587749481201, 62 | 38.90561164780041 63 | ], 64 | [ 65 | -77.00587749481201, 66 | 38.90373727631765 67 | ] 68 | ] 69 | } 70 | }, 71 | { 72 | "type": "Feature", 73 | "properties": { 74 | "id": "345!0", 75 | "refs": [ 76 | "2", 77 | "6" 78 | ] 79 | }, 80 | "geometry": { 81 | "type": "LineString", 82 | "coordinates": [ 83 | [ 84 | -77.00587749481201, 85 | 38.90373727631765 86 | ], 87 | [ 88 | -77.00583457946777, 89 | 38.90247236069279 90 | ] 91 | ] 92 | } 93 | } 94 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/loop-with-exit/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "504179333!0", 6 | "refs": [ 7 | "1", 8 | "2", 9 | "3" 10 | ] 11 | }, 12 | "geometry": { 13 | "type": "LineString", 14 | "coordinates": [ 15 | [ 16 | -121.0013996, 17 | 38.6593683 18 | ], 19 | [ 20 | -121.0016128, 21 | 38.6591379 22 | ], 23 | [ 24 | -121.0011488, 25 | 38.6590688 26 | ] 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "properties": { 33 | "id": "504179333!1", 34 | "refs": [ 35 | "3", 36 | "1" 37 | ] 38 | }, 39 | "geometry": { 40 | "type": "LineString", 41 | "coordinates": [ 42 | [ 43 | -121.0011488, 44 | 38.6590688 45 | ], 46 | [ 47 | -121.0013996, 48 | 38.6593683 49 | ] 50 | ] 51 | } 52 | }, 53 | { 54 | "type": "Feature", 55 | "properties": { 56 | "id": "504179333!2", 57 | "refs": [ 58 | "1", 59 | "4" 60 | ] 61 | }, 62 | "geometry": { 63 | "type": "LineString", 64 | "coordinates": [ 65 | [ 66 | -121.0013996, 67 | 38.6593683 68 | ], 69 | [ 70 | -121.0016243, 71 | 38.6595081 72 | ] 73 | ] 74 | } 75 | }, 76 | { 77 | "type": "Feature", 78 | "properties": { 79 | "id": "504179332!0", 80 | "refs": [ 81 | "3", 82 | "5" 83 | ] 84 | }, 85 | "geometry": { 86 | "type": "LineString", 87 | "coordinates": [ 88 | [ 89 | -121.0011488, 90 | 38.6590688 91 | ], 92 | [ 93 | -121.0008082, 94 | 38.6592395 95 | ] 96 | ] 97 | } 98 | } 99 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/keep-tags/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123!0", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ], 10 | "oneway": 0, 11 | "highway": "primary", 12 | "access": "yes" 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | -77.00896203517914, 19 | 38.903733101715716 20 | ], 21 | [ 22 | -77.00587749481201, 23 | 38.90373727631765 24 | ] 25 | ] 26 | } 27 | }, 28 | { 29 | "type": "Feature", 30 | "properties": { 31 | "id": "123!1", 32 | "refs": [ 33 | "2", 34 | "3" 35 | ], 36 | "oneway": 0, 37 | "highway": "primary", 38 | "access": "yes" 39 | }, 40 | "geometry": { 41 | "type": "LineString", 42 | "coordinates": [ 43 | [ 44 | -77.00587749481201, 45 | 38.90373727631765 46 | ], 47 | [ 48 | -77.00355470180511, 49 | 38.903741450919384 50 | ] 51 | ] 52 | } 53 | }, 54 | { 55 | "type": "Feature", 56 | "properties": { 57 | "id": "234!0", 58 | "refs": [ 59 | "4", 60 | "2" 61 | ], 62 | "oneway": 1, 63 | "highway": "secondary", 64 | "name": "Market Street", 65 | "ref": "I-70", 66 | "access": "no" 67 | }, 68 | "geometry": { 69 | "type": "LineString", 70 | "coordinates": [ 71 | [ 72 | -77.00587749481201, 73 | 38.90561164780041 74 | ], 75 | [ 76 | -77.00587749481201, 77 | 38.90373727631765 78 | ] 79 | ] 80 | } 81 | }, 82 | { 83 | "type": "Feature", 84 | "properties": { 85 | "id": "234!1", 86 | "refs": [ 87 | "2", 88 | "6" 89 | ], 90 | "oneway": 1, 91 | "highway": "secondary", 92 | "name": "Market Street", 93 | "ref": "I-70", 94 | "access": "no" 95 | }, 96 | "geometry": { 97 | "type": "LineString", 98 | "coordinates": [ 99 | [ 100 | -77.00587749481201, 101 | 38.90373727631765 102 | ], 103 | [ 104 | -77.00583457946777, 105 | 38.90247236069279 106 | ] 107 | ] 108 | } 109 | } 110 | ] 111 | -------------------------------------------------------------------------------- /test/fixtures/split-ways/1-way-multi-intersection/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "Feature", 4 | "properties": { 5 | "id": "123!0", 6 | "refs": [ 7 | "1", 8 | "2" 9 | ] 10 | }, 11 | "geometry": { 12 | "type": "LineString", 13 | "coordinates": [ 14 | [ 15 | -77.0035171508789, 16 | 38.90253915548077 17 | ], 18 | [ 19 | -77.00203657150269, 20 | 38.90253915548077 21 | ] 22 | ] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": { 28 | "id": "123!1", 29 | "refs": [ 30 | "2", 31 | "3" 32 | ] 33 | }, 34 | "geometry": { 35 | "type": "LineString", 36 | "coordinates": [ 37 | [ 38 | -77.00203657150269, 39 | 38.90253915548077 40 | ], 41 | [ 42 | -77.00051307678223, 43 | 38.902522456789654 44 | ] 45 | ] 46 | } 47 | }, 48 | { 49 | "type": "Feature", 50 | "properties": { 51 | "id": "123!2", 52 | "refs": [ 53 | "3", 54 | "4" 55 | ] 56 | }, 57 | "geometry": { 58 | "type": "LineString", 59 | "coordinates": [ 60 | [ 61 | -77.00051307678223, 62 | 38.902522456789654 63 | ], 64 | [ 65 | -76.99952602386475, 66 | 38.90253915548077 67 | ] 68 | ] 69 | } 70 | }, 71 | { 72 | "type": "Feature", 73 | "properties": { 74 | "id": "234!0", 75 | "refs": [ 76 | "5", 77 | "2" 78 | ] 79 | }, 80 | "geometry": { 81 | "type": "LineString", 82 | "coordinates": [ 83 | [ 84 | -77.00199365615845, 85 | 38.903758149323785 86 | ], 87 | [ 88 | -77.00203657150269, 89 | 38.90253915548077 90 | ] 91 | ] 92 | } 93 | }, 94 | { 95 | "type": "Feature", 96 | "properties": { 97 | "id": "345!0", 98 | "refs": [ 99 | "6", 100 | "3" 101 | ] 102 | }, 103 | "geometry": { 104 | "type": "LineString", 105 | "coordinates": [ 106 | [ 107 | -77.00055599212646, 108 | 38.901253344774105 109 | ], 110 | [ 111 | -77.00051307678223, 112 | 38.902522456789654 113 | ] 114 | ] 115 | } 116 | } 117 | ] -------------------------------------------------------------------------------- /test/fixtures/split-ways/loop-with-intersection/after: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type":"Feature", 4 | "geometry":{ 5 | "type":"LineString", 6 | "coordinates":[ 7 | [ 8 | 11.1374577, 9 | 49.4140842 10 | ], 11 | [ 12 | 11.1377995, 13 | 49.4138401 14 | ], 15 | [ 16 | 11.1376121, 17 | 49.413729 18 | ] 19 | ] 20 | }, 21 | "properties":{ 22 | "id":"502788725!0", 23 | "refs":[ 24 | "1", 25 | "2", 26 | "3" 27 | ], 28 | "oneway": 0 29 | } 30 | }, 31 | { 32 | "type":"Feature", 33 | "geometry":{ 34 | "type":"LineString", 35 | "coordinates":[ 36 | [ 37 | 11.1376121, 38 | 49.413729 39 | ], 40 | [ 41 | 11.1374404, 42 | 49.4136273 43 | ], 44 | [ 45 | 11.1370987, 46 | 49.4138714 47 | ], 48 | [ 49 | 11.1372726, 50 | 49.4139744 51 | ] 52 | ] 53 | }, 54 | "properties":{ 55 | "id":"502788725!1", 56 | "refs":[ 57 | "3", 58 | "4", 59 | "5", 60 | "6" 61 | ], 62 | "oneway": 0 63 | } 64 | }, 65 | { 66 | "type":"Feature", 67 | "geometry":{ 68 | "type":"LineString", 69 | "coordinates":[ 70 | [ 71 | 11.1372726, 72 | 49.4139744 73 | ], 74 | [ 75 | 11.1374577, 76 | 49.4140842 77 | ] 78 | ] 79 | }, 80 | "properties":{ 81 | "id":"502788725!2", 82 | "refs":[ 83 | "6", 84 | "1" 85 | ], 86 | "oneway": 0 87 | } 88 | }, 89 | { 90 | "type":"Feature", 91 | "geometry":{ 92 | "type":"LineString", 93 | "coordinates":[ 94 | [ 95 | 11.1374577, 96 | 49.4140842 97 | ], 98 | [ 99 | 11.1373071, 100 | 49.4141971 101 | ] 102 | ] 103 | }, 104 | "properties":{ 105 | "id":"502788725!3", 106 | "refs":[ 107 | "1", 108 | "7" 109 | ], 110 | "oneway": 0 111 | } 112 | }, 113 | { 114 | "type":"Feature", 115 | "geometry":{ 116 | "type":"LineString", 117 | "coordinates":[ 118 | [ 119 | 11.1372726, 120 | 49.4139744 121 | ], 122 | [ 123 | 11.1376121, 124 | 49.413729 125 | ] 126 | ] 127 | }, 128 | "properties":{ 129 | "id":"502788724!0", 130 | "refs":[ 131 | "6", 132 | "3" 133 | ] 134 | } 135 | } 136 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graph-normalizer 2 | 3 | [![Build Status](https://travis-ci.com/mapbox/graph-normalizer.svg?token=L2z9Dgm3tWM4E4xpoHDL&branch=master)](https://travis-ci.com/mapbox/graph-normalizer) 4 | 5 | `graph-normalizer` is a JavaScript module that performs operations on an array of GeoJSON LineStrings representing OpenStreetMap ways. The operations performed cover a standard set of graph normalization techniques such as merging edges between intersections and splitting edges that cross over intersections. Ids assigned to ways in the normalized graph are deterministic and reproducible. 6 | 7 | ### Installation 8 | 9 | ```sh 10 | npm install @mapbox/graph-normlizer 11 | ``` 12 | 13 | ### Test 14 | 15 | ```sh 16 | npm test 17 | ``` 18 | 19 | ### Benchmark 20 | 21 | ``` 22 | npm run bench 23 | ``` 24 | 25 | ### Use 26 | 27 | ```js 28 | var normalizer = require('@mapbox/graph-normalizer'); 29 | 30 | var ways = require('./ways.json'); 31 | 32 | var splitWays = normalizer.splitWays(ways); 33 | var mergedWays = normalizer.mergeWays(ways); 34 | 35 | console.log(JSON.stringify(ways)); 36 | ``` 37 | 38 | ### API 39 | 40 | #### splitWays(ways) 41 | 42 | Any ways that traverse an intersection are split in two. `!` is appended to the way id where `i` is the index of the split way in the original geometry. Note that `!0` is appended to the way id if there is no split. 43 | 44 | #### mergeWays(ways) 45 | 46 | Ways that share a node which is not an intersection (only 2 way owners) are merged together. The resulting id is `,`. 47 | 48 | ### Input format 49 | 50 | - An array of GeoJSON LineString Features 51 | - Each feature must have a `refs` array signifying node Ids of the coordinates that make up the way used for topology construction 52 | - Each feature must have an `id` property representing the OpenStreetMap id of the way 53 | - Each feature must have a `highway` property representing the OpenStreetMap highway tag of the way 54 | - Each feature must have a `oneway` property representing the OpenStreetMap oneway tag of the way 55 | - the `oneway` property must be normalized to `0`, `1`, or `-1` 56 | - `0` signifies a bidirectional way 57 | - `1` signifies a oneway way traveling in coordinate order 58 | - `-1` signifies a oneway way traveling in reverse coordinate order (this will be normalized to forward order `1`) 59 | - graph-normalizer will not work with raw OpenStreetMap oneway values such as `yes`, or `no` 60 | 61 | ### Misc 62 | 63 | - All way geometries in the original road network have an equivalent in the normalized graph. 64 | - No intersection ever lies within a normalized way, only at its ends. 65 | - Normalized way ids keep track of the history of transformations that led to it. 66 | - `highway`, `oneway`, `bridge`, `tunnel` and `junction` tags are conserved from the original graph by default. 67 | - `highway`, `bridge`, `tunnel` and `junction` tags can be merged using optional arguments. When merging different tags: 68 | - `highway` tag is set as `unclassified` 69 | - `tunnel` tag is set to `yes` i.e. we keep the info that there is a tunnel in the merged way 70 | - `bridge` tag is set to `yes` i.e. we keep the info that there is a bridge in the merged way 71 | - `junction` tag is set we keep the info about the junction in the merged way 72 | -------------------------------------------------------------------------------- /lib/split-ways.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const lineString = require('turf-linestring'); 4 | 5 | /** 6 | * Given ways, split any ways that cross over an intersections 7 | * @param {Object} ways an array of ways 8 | * @return {Object} ways another array of ways 9 | */ 10 | module.exports = function (ways) { 11 | // construct node hash 12 | // nodeHash is a hash of nodes => ways 13 | // each way represents a node "owner" 14 | const nodeHash = {}; 15 | ways.forEach((way) => { 16 | way.properties.refs.forEach((ref) => { 17 | if (!nodeHash[ref]) nodeHash[ref] = 0; 18 | 19 | nodeHash[ref] += 1; 20 | }); 21 | }); 22 | 23 | const splitWays = []; 24 | 25 | ways.forEach((way) => { 26 | let splits = 0; 27 | let last = 0; 28 | let current = 0; 29 | 30 | way.properties.refs.forEach((ref, i) => { 31 | current++; 32 | 33 | // ignore terminal nodes 34 | if (i > 0 && i < way.properties.refs.length - 1) { 35 | // find the number of ways that contain the node 36 | const ownerCount = nodeHash[ref]; 37 | 38 | // look for nodes with more than 1 owner 39 | if (ownerCount > 1) { 40 | // add front of split way to splitWays 41 | const waySlice = lineString( 42 | way.geometry.coordinates.slice(last, current), 43 | { 44 | id: `${way.properties.id }!${ splits}`, 45 | refs: way.properties.refs.slice(last, current) 46 | } 47 | ); 48 | 49 | // persist these tags if they are present: 50 | if (Object.hasOwn(way, 'oneway')) waySlice.properties.oneway = way.properties.oneway; 51 | if (Object.hasOwn(way, 'highway')) waySlice.properties.highway = way.properties.highway; 52 | if (Object.hasOwn(way, 'bridge')) waySlice.properties.bridge = way.properties.bridge; 53 | if (Object.hasOwn(way, 'tunnel')) waySlice.properties.tunnel = way.properties.tunnel; 54 | if (Object.hasOwn(way, 'name')) waySlice.properties.name = way.properties.name; 55 | if (Object.hasOwn(way, 'ref')) waySlice.properties.ref = way.properties.ref; 56 | if (Object.hasOwn(way, 'access')) waySlice.properties.access = way.properties.access; 57 | if (Object.hasOwn(way, 'junction')) waySlice.properties.junction = way.properties.junction; 58 | 59 | splitWays.push(waySlice); 60 | 61 | splits++; 62 | last = i; 63 | } 64 | } 65 | }); 66 | 67 | // add the remainder of the way 68 | if (last < current) { 69 | const waySlice = lineString( 70 | way.geometry.coordinates.slice(last, current), 71 | { 72 | id: `${way.properties.id }!${ splits}`, 73 | refs: way.properties.refs.slice(last, current) 74 | } 75 | ); 76 | 77 | // persist these tags if they are present: 78 | if (Object.hasOwn(way, 'oneway')) waySlice.properties.oneway = way.properties.oneway; 79 | if (Object.hasOwn(way, 'highway')) waySlice.properties.highway = way.properties.highway; 80 | if (Object.hasOwn(way, 'bridge')) waySlice.properties.bridge = way.properties.bridge; 81 | if (Object.hasOwn(way, 'tunnel')) waySlice.properties.tunnel = way.properties.tunnel; 82 | if (Object.hasOwn(way, 'name')) waySlice.properties.name = way.properties.name; 83 | if (Object.hasOwn(way, 'ref')) waySlice.properties.ref = way.properties.ref; 84 | if (Object.hasOwn(way, 'access')) waySlice.properties.access = way.properties.access; 85 | if (Object.hasOwn(way, 'junction')) waySlice.properties.junction = way.properties.junction; 86 | 87 | splitWays.push(waySlice); 88 | } 89 | }); 90 | 91 | return splitWays; 92 | }; 93 | -------------------------------------------------------------------------------- /lib/merge-ways.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const lineString = require('turf-linestring'); 4 | 5 | /** 6 | * Given ways, split any ways that cross over an intersections 7 | * @param {Object} ways an array of ways 8 | * @param {Object} options an options object defining mergeHighways, mergeTunnels, and mergeBridges (all default to false) 9 | * @return {Object} ways another array of ways 10 | */ 11 | module.exports = function (wayList, options) { 12 | // default options 13 | if (!options) options = {}; 14 | options.mergeHighways = (options.mergeHighways === undefined) ? false : options.mergeHighways; 15 | options.mergeTunnels = (options.mergeTunnels === undefined) ? false : options.mergeTunnels; 16 | options.mergeBridges = (options.mergeBridges === undefined) ? false : options.mergeBridges; 17 | options.mergeJunctions = (options.mergeJunctions === undefined) ? false : options.mergeJunctions; 18 | options.mergeAccess = (options.mergeAccess === undefined) ? false : options.mergeAccess; 19 | 20 | 21 | // build node and way hashes 22 | const nodes = new Map(); 23 | const ways = {}; 24 | 25 | wayList.forEach((way) => { 26 | // normalize oneways to always equal 0 (bidirectional) or 1 (oneway in direction of coords) 27 | if (way.properties.oneway === -1) { 28 | way.properties.oneway = 1; 29 | way.properties.refs = way.properties.refs.reverse(); 30 | way.geometry.coordinates = way.geometry.coordinates.reverse(); 31 | } 32 | 33 | ways[way.properties.id] = way; 34 | way.properties.refs.forEach((ref) => { 35 | if (!nodes.has(ref)) nodes.set(ref, new Set()); 36 | 37 | nodes.get(ref).add(way.properties.id); 38 | }); 39 | }); 40 | 41 | // build merge queue 42 | nodes.forEach((ownerIds, node) => { 43 | // delete nodes that do not have exactly 2 owners 44 | // nodes with < 2 owners are non terminal nodeHash 45 | // nodes with > 2 oweners are intersections 46 | if (ownerIds.size !== 2) nodes.delete(node); 47 | }); 48 | 49 | // filter merges with mismatched highway or oneway tags 50 | nodes.forEach((ownerIds, node) => { 51 | const owners = []; 52 | ownerIds.forEach((id) => { 53 | owners.push(ways[id]); 54 | }); 55 | 56 | if ( 57 | ( 58 | owners[0].properties.oneway !== 59 | owners[1].properties.oneway 60 | ) || 61 | ( 62 | (!options.mergeHighways) && 63 | ( 64 | owners[0].properties.highway !== 65 | owners[1].properties.highway 66 | ) 67 | ) || 68 | ( 69 | (!options.mergeBridges) && 70 | ( 71 | owners[0].properties.bridge !== 72 | owners[1].properties.bridge 73 | ) 74 | ) || 75 | ( 76 | (!options.mergeTunnels) && 77 | ( 78 | owners[0].properties.tunnel !== 79 | owners[1].properties.tunnel 80 | ) 81 | ) || 82 | ( 83 | (!options.mergeJunctions) && 84 | ( 85 | owners[0].properties.junction !== 86 | owners[1].properties.junction 87 | ) 88 | ) || 89 | ( 90 | (!options.mergeAccess) && 91 | ( 92 | owners[0].properties.access !== 93 | owners[1].properties.access 94 | ) 95 | ) 96 | ) nodes.delete(node); 97 | }); 98 | 99 | // keep merging until all merge nodes have been eliminated 100 | while (nodes.size) { 101 | const nodeIterator = nodes.keys(); 102 | const nodeId = nodeIterator.next().value; 103 | const node = nodes.get(nodeId); 104 | 105 | const owners = []; 106 | 107 | node.forEach((id) => { 108 | owners.push(ways[id]); 109 | }); 110 | 111 | let opening = null; 112 | let closing = null; 113 | let validMerge = true; 114 | 115 | // if owners < 2, this way cannot be merged due to an edge case 116 | if (owners.filter(owner => owner).length === 2) { 117 | if (owners[0].properties.oneway === 1) { 118 | // oneway merge 119 | // assign opening and closing way 120 | owners.forEach((owner) => { 121 | if (owner.properties.refs[owner.properties.refs.length - 1] === nodeId) { 122 | opening = owner; 123 | } else if (owner.properties.refs[0] === nodeId) { 124 | closing = owner; 125 | } 126 | }); 127 | // if an opening and closing way were not found, 128 | // the ways do not face the same direction 129 | if (!opening || !closing) validMerge = false; 130 | } else { 131 | // bidirectional merge 132 | 133 | // We order the ways in order of ids to ensure ID consistency. 134 | if (owners[0].properties.id < owners[1].properties.id) { 135 | opening = owners[0]; 136 | closing = owners[1]; 137 | } else { 138 | opening = owners[1]; 139 | closing = owners[0]; 140 | } 141 | 142 | // if opening and closing are not present for a bidirectional... 143 | // most likely one of the ways loops in on itself in an odd way 144 | if (!opening || !closing) validMerge = false; 145 | else { 146 | // flip bidirectional ways if they are not oriented correctly 147 | if (opening.properties.refs[opening.properties.refs.length - 1] !== nodeId) { 148 | opening.properties.refs = opening.properties.refs.reverse(); 149 | opening.geometry.coordinates = opening.geometry.coordinates.reverse(); 150 | } 151 | 152 | if (closing.properties.refs[0] !== nodeId) { 153 | closing.properties.refs = closing.properties.refs.reverse(); 154 | closing.geometry.coordinates = closing.geometry.coordinates.reverse(); 155 | } 156 | } 157 | } 158 | 159 | } else validMerge = false; 160 | 161 | if (validMerge) { 162 | // combine the opening way with the closing way 163 | // omit the first ref of the closing way to avoid repeating the shared node 164 | const combined = lineString( 165 | opening.geometry.coordinates.concat(closing.geometry.coordinates.slice(1, closing.geometry.coordinates.length)), 166 | { 167 | id: `${opening.properties.id },${ closing.properties.id}`, 168 | refs: opening.properties.refs.concat(closing.properties.refs.slice(1, closing.properties.refs.length)) 169 | } 170 | ); 171 | 172 | // persist oneway, highway, bridge, tunnel, junction and access tags if they are present 173 | if (Object.hasOwn(opening, 'oneway')) combined.properties.oneway = opening.properties.oneway; 174 | 175 | if (options.mergeHighways) { 176 | // if highway tags are the same, keep them, else set as unclassified 177 | if (Object.hasOwn(opening, 'highway') && Object.hasOwn(closing, 'highway') && (opening.properties.highway === closing.properties.highway)) { 178 | combined.properties.highway = opening.properties.highway; 179 | } else { 180 | combined.properties.highway = 'unclassified'; 181 | } 182 | } else if (Object.hasOwn(opening, 'highway')) combined.properties.highway = opening.properties.highway; 183 | 184 | if (options.mergeBridges) { 185 | if (Object.hasOwn(opening, 'bridge')) combined.properties.bridge = opening.properties.bridge; 186 | else if (Object.hasOwn(closing, 'bridge')) combined.properties.bridge = closing.properties.bridge; 187 | } else if (Object.hasOwn(opening, 'bridge')) combined.properties.bridge = opening.properties.bridge; 188 | 189 | if (options.mergeTunnels) { 190 | if (Object.hasOwn(opening, 'tunnel')) combined.properties.tunnel = opening.properties.tunnel; 191 | else if (Object.hasOwn(closing, 'tunnel')) combined.properties.tunnel = closing.properties.tunnel; 192 | } else if (Object.hasOwn(opening, 'tunnel')) combined.properties.tunnel = opening.properties.tunnel; 193 | 194 | if (options.mergeJunctions) { 195 | if (Object.hasOwn(opening, 'junction')) combined.properties.junction = opening.properties.junction; 196 | else if (Object.hasOwn(closing, 'junction')) combined.properties.junction = closing.properties.junction; 197 | } else if (Object.hasOwn(opening, 'junction')) combined.properties.junction = opening.properties.junction; 198 | 199 | if (options.mergeAccess) { 200 | if (Object.hasOwn(opening, 'access')) combined.properties.access = opening.properties.access; 201 | else if (Object.hasOwn(closing, 'access')) combined.properties.access = closing.properties.access; 202 | } else if (Object.hasOwn(opening, 'access')) combined.properties.access = opening.properties.access; 203 | 204 | // insert combined way into hash 205 | ways[combined.properties.id] = combined; 206 | 207 | // update terminal nodes of combined way 208 | // patch starting node 209 | if (nodes.has(combined.properties.refs[0])) { 210 | const starting = nodes.get(combined.properties.refs[0]); 211 | starting.delete(opening.properties.id); 212 | starting.delete(closing.properties.id); 213 | starting.add(combined.properties.id); 214 | } 215 | 216 | // patch ending node 217 | if (nodes.has(combined.properties.refs[combined.properties.refs.length - 1])) { 218 | const ending = nodes.get(combined.properties.refs[combined.properties.refs.length - 1]); 219 | ending.delete(opening.properties.id); 220 | ending.delete(closing.properties.id); 221 | ending.add(combined.properties.id); 222 | } 223 | 224 | // delete merged ways from hash 225 | delete ways[opening.properties.id]; 226 | delete ways[closing.properties.id]; 227 | } 228 | // delete merged node from heap 229 | nodes.delete(nodeId); 230 | } 231 | 232 | const merged = Object.keys(ways).map(id => ways[id]); 233 | 234 | return merged; 235 | }; 236 | --------------------------------------------------------------------------------