├── .clang-format
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.txt
├── Makefile
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── asan-suppress.txt
├── binding.gyp
├── cloudformation
└── travis.template.js
├── codecov.yml
├── common.gypi
├── example-server.js
├── index.js
├── install_mason.sh
├── package-lock.json
├── package.json
├── scripts
├── build-with-docker.sh
├── coverage.sh
├── create-docker-image.sh
├── create_scheme.sh
├── fix_format.sh
├── format.sh
├── install_deps.sh
├── library.xcscheme
├── node.xcscheme
├── publish.sh
├── run-example-server.sh
└── setup.sh
├── src
├── annotator.cpp
├── annotator.hpp
├── database.cpp
├── database.hpp
├── extractor.cpp
├── extractor.hpp
├── main_bindings.cpp
├── nodejs_bindings.cpp
├── nodejs_bindings.hpp
├── segment_bindings.cpp
├── segment_bindings.hpp
├── segment_speed_map.cpp
├── segment_speed_map.hpp
├── types.hpp
├── way_bindings.cpp
├── way_bindings.hpp
├── way_speed_map.cpp
└── way_speed_map.hpp
└── test
├── basic-tests.cpp
├── basic
├── annotator.cpp
├── database.cpp
├── extractor.cpp
└── rtree.cpp
├── bench.cpp
├── congestion-tests.cpp
├── congestion
├── congestion.cpp
└── fixtures
│ ├── congestion.csv
│ ├── congestion2.csv
│ ├── congestion_speed_max.csv
│ ├── congestion_speed_max2.csv
│ ├── fewer_columns.csv
│ ├── fewer_columns2.csv
│ ├── header.csv
│ ├── header2.csv
│ ├── invalid_csv.csv
│ ├── invalid_csv2.csv
│ ├── more_columns.csv
│ ├── more_columns2.csv
│ ├── negative_number.csv
│ ├── negative_number2.csv
│ ├── not_a_number.csv
│ ├── not_a_number2.csv
│ ├── string_input.csv
│ └── string_input2.csv
├── data
├── baltimore.osm.pbf
├── monaco.extract.osm
├── multiple-tags
├── paris.extract.osm.pbf
├── tags
└── winthrop.osm
├── load.test.js
├── tags-filter.test.js
├── way-speed-tests.cpp
└── wayspeeds
├── fixtures
├── fewer_columns.csv
├── header.csv
├── invalid_csv.csv
├── more_columns.csv
├── negative_number.csv
├── not_a_number.csv
├── string_input.csv
├── way_speeds.csv
└── way_speeds2.csv
└── wayspeeds.cpp
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | # BasedOnStyle: LLVM
4 | AccessModifierOffset: -2
5 | ConstructorInitializerIndentWidth: 4
6 | AlignEscapedNewlinesLeft: false
7 | AlignTrailingComments: true
8 | AllowAllParametersOfDeclarationOnNextLine: true
9 | AllowShortIfStatementsOnASingleLine: false
10 | AllowShortLoopsOnASingleLine: false
11 | AllowShortFunctionsOnASingleLine: true
12 | AlwaysBreakTemplateDeclarations: false
13 | AlwaysBreakBeforeMultilineStrings: false
14 | BreakBeforeBinaryOperators: false
15 | BreakBeforeTernaryOperators: true
16 | BreakConstructorInitializersBeforeComma: false
17 | BinPackParameters: false
18 | ColumnLimit: 100
19 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
20 | DerivePointerBinding: false
21 | ExperimentalAutoDetectBinPacking: false
22 | IndentCaseLabels: false
23 | MaxEmptyLinesToKeep: 1
24 | KeepEmptyLinesAtTheStartOfBlocks: true
25 | NamespaceIndentation: None
26 | ObjCSpaceAfterProperty: false
27 | ObjCSpaceBeforeProtocolList: true
28 | PenaltyBreakBeforeFirstCallParameter: 19
29 | PenaltyBreakComment: 300
30 | PenaltyBreakString: 1000
31 | PenaltyBreakFirstLessLess: 120
32 | PenaltyExcessCharacter: 1000
33 | PenaltyReturnTypeOnItsOwnLine: 60
34 | PointerBindsToType: false
35 | SpacesBeforeTrailingComments: 1
36 | Cpp11BracedListStyle: true
37 | Standard: Cpp11
38 | IndentWidth: 4
39 | TabWidth: 8
40 | UseTab: Never
41 | BreakBeforeBraces: Allman
42 | IndentFunctionDeclarationAfterType: false
43 | SpacesInParentheses: false
44 | SpacesInAngles: false
45 | SpaceInEmptyParentheses: false
46 | SpacesInCStyleCastParentheses: false
47 | SpacesInContainerLiterals: true
48 | SpaceBeforeAssignmentOperators: true
49 | ContinuationIndentWidth: 4
50 | CommentPragmas: '^ IWYU pragma:'
51 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
52 | SpaceBeforeParens: ControlStatements
53 | ...
54 |
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | lib/binding
3 | node_modules
4 | npm-debug.log
5 | mason
6 | mason_packages
7 | nodes.cache
8 | *profraw
9 | *profdata
10 | .mason
11 | .toolchain
12 | local.env
13 | coverage/
14 | wayspeeds/
15 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /build
2 | lib/binding
3 | node_modules
4 | npm-debug.log
5 | /mason
6 | /mason_packages
7 | nodes.cache
8 | *profraw
9 | *profdata
10 | .mason
11 | .toolchain
12 | local.env
13 | /test
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 14.15.4
4 |
5 | sudo: false
6 |
7 | # enable c++11/14 builds
8 | addons:
9 | apt:
10 | sources: [ 'ubuntu-toolchain-r-test' ]
11 | packages: [ 'libstdc++-4.9-dev' ]
12 |
13 | install:
14 | # set up the environment by installing mason and clang++
15 | - ./scripts/setup.sh --config local.env
16 | # put mason and clang++ on PATH
17 | - source local.env
18 | - node -v
19 | - which node
20 | - clang++ -v
21 | - which clang++
22 | - npm ci --ignore-scripts
23 | - make ${BUILDTYPE}
24 |
25 | # run tests
26 | # We use before_script rather than script to ensure fast failure (the script section continues even after an error)
27 | # https://docs.travis-ci.com/user/customizing-the-build#Breaking-the-Build
28 | before_script:
29 | - npm test
30 | - make test-${BUILDTYPE}
31 | # after successful tests, publish binaries if specified in commit message
32 | - ./scripts/publish.sh --toolset=${TOOLSET:-} --debug=$([ "${BUILDTYPE}" == 'debug' ] && echo "true" || echo "false")
33 |
34 | # override script default (npm test) to do nothing (we test in before_script)
35 | script:
36 | - true
37 |
38 | # the matrix allows you to specify different operating systems and environments to
39 | # run your tests and build binaries
40 | matrix:
41 | include:
42 | # linux publishable node v14
43 | - os: linux
44 | env: BUILDTYPE=release
45 | # linux publishable node v14/debug
46 | - os: linux
47 | env: BUILDTYPE=debug
48 | # linux publishable node v12
49 | - os: linux
50 | env: BUILDTYPE=release
51 | node_js: 12
52 | # linux publishable node v12/debug
53 | - os: linux
54 | env: BUILDTYPE=debug
55 | node_js: 12
56 | # linux publishable node v10
57 | - os: linux
58 | env: BUILDTYPE=release
59 | node_js: 10
60 | # linux publishable node v10/debug
61 | - os: linux
62 | env: BUILDTYPE=debug
63 | node_js: 10
64 | # osx publishable node v14
65 | - os: osx
66 | osx_image: xcode12
67 | env: BUILDTYPE=release
68 | # Sanitizer build node v4/Debug
69 | - os: linux
70 | env: BUILDTYPE=debug TOOLSET=asan
71 | # Overrides `install` to set up custom asan flags
72 | install:
73 | - ./scripts/setup.sh --config local.env
74 | # put mason and clang++ on PATH
75 | - source local.env
76 | # Note: to build without stopping on errors remove the -fno-sanitize-recover=all flag
77 | # You might want to do this if there are multiple errors and you want to see them all before fixing
78 | - export CXXFLAGS="${MASON_SANITIZE_CXXFLAGS} -fno-sanitize-recover=all"
79 | - export LDFLAGS="${MASON_SANITIZE_LDFLAGS}"
80 | - npm ci --ignore-scripts
81 | - make ${BUILDTYPE}
82 | # Overrides `script` to disable asan LD_PRELOAD before publishing
83 | before_script:
84 | - export LD_PRELOAD=${MASON_LLVM_RT_PRELOAD}
85 | - export ASAN_OPTIONS=fast_unwind_on_malloc=0:${ASAN_OPTIONS}
86 | - export LSAN_OPTIONS=suppressions=asan-suppress.txt
87 | - npm test
88 | - unset LD_PRELOAD
89 | # g++ build (default builds all use clang++)
90 | - os: linux
91 | env: BUILDTYPE=debug CXX="g++-6" CC="gcc-6"
92 | addons:
93 | apt:
94 | sources:
95 | - ubuntu-toolchain-r-test
96 | packages:
97 | - libstdc++-6-dev
98 | - g++-6
99 | # Overrides `install` to avoid initializing clang toolchain
100 | install:
101 | - npm ci --ignore-scripts
102 | - make ${BUILDTYPE}
103 | - make test-${BUILDTYPE}
104 | # Overrides `script` to disable publishing
105 | before_script:
106 | - npm test
107 | - make test-${BUILDTYPE}
108 | # Coverage build
109 | #- os: linux
110 | # env: BUILDTYPE=debug CXXFLAGS="--coverage" LDFLAGS="--coverage"
111 | # # Overrides `script` to publish coverage data to codecov
112 | # before_script:
113 | # - ./scripts/format.sh
114 | # - make test-${BUILDTYPE}
115 | # - npm run coverage
116 | # - mason install llvm-cov ${MASON_LLVM_RELEASE}
117 | # - mason link llvm-cov ${MASON_LLVM_RELEASE}
118 | # - which llvm-cov
119 | # - curl -S -f https://codecov.io/bash -o codecov
120 | # - chmod +x codecov
121 | # - ./codecov -x "llvm-cov gcov" -Z
122 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Route Annotator releases
2 |
3 | ## Unreleased
4 | ## 0.4.1
5 | - Re-enable Node 10,12 builds that were mistakenly disabled in CI config
6 |
7 | ## 0.4.0
8 | - Upgrade bindings for Node 12, 14
9 |
10 | ## 0.3.0
11 | - Upgrade bindings for Node 10; dependencies.
12 |
13 | ## 0.2.3
14 | - Rounding was off for coverting mph to kph. For example, 55 mph was being converted to 88 and not 89. We now multiply by 1.609344f and round.
15 |
16 | ## 0.2.2
17 | - Added support units in SegmentSpeedMap. Data can be in the format of `NODE_A,NODE_B,UNIT,MAXSPEED` or `NODE_A,NODE_B,MAXSPEED`. If units are expected, then all speed is coverted to kph. If the unit is blank, it is assumed that the speed is in kph. Added a limit to speeds. We now will write out an error if > (invalid_speed - 1). Changed segment_speed_t to uint8_t. Added logic to convert mph to kph for maxspeed tag for OSM data.
18 |
19 | ## 0.2.1
20 | - If tag is not found, filter by certain highway types by default. (i.e., always add routable ways even though the requested tags don't exist.)
21 |
22 | ## 0.2.0
23 | - Implemented a key-value lookup service that can be used for fast in-memory lookup of speed data for wayids.
24 | - Refactored existing code. speed_lookup_bindings -> segment_bindings
25 |
26 | ## 0.1.1
27 | - Fixed code coverage environment and expanded test coverage
28 | - Add support for Node 8 pre-published binaries
29 |
30 | ## 0.1.0
31 | - Added filtering option based on a file of OSM tags
32 | * loadOSMExtract can optionally be given another path to a file containing OSM tags on which to filter data storage
33 | * loadOSMExtract now accepts an array of OSM data files in addition to a single string path
34 |
35 | ## 0.0.7
36 | - Performance improvements (faster load times)
37 | * Only parse and save node coordinates if we need to build the rtree
38 | * Use faster lookup table for checking valid tag names.
39 | * Only store node pairs once, flag whether storage is forward/backward.
40 | * Don't store external way IDs as strings - saves space, and faster to lookup
41 | * Don't use location index if there is no rtree being built.
42 | * Use simpler loop - zip_iterator came with a small performance hit.
43 |
44 | ## 0.0.6
45 | - Made RTree construction in the annotator database optional and exposed it in the node bindings as a configuration option in the Annotator object
46 |
47 | ## 0.0.5
48 | - Removed large unecessary test fixture from published npm module
49 |
50 | ## 0.0.4
51 | - Fixed bug of segfaulting if using the hashmap without loading CSVs first
52 |
53 | ## 0.0.3
54 | - Added SpeedLookup() to read CSV files and load a hashmap of NodeId pairs (key) and speeds (value)
55 | - Changed over build system to use mason
56 |
57 | ## 0.0.2
58 | - Added Annotator() for looking up OSM tag data from OSM node IDs or coordinates
59 |
60 | ## 0.0.1
61 | - First versioned release
62 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c), Mapbox
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification,
6 | are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice,
9 | this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 | * Neither the name of node-pre-gyp nor the names of its contributors
14 | may be used to endorse or promote products derived from this software
15 | without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | MODULE_NAME := $(shell node -e "console.log(require('./package.json').binary.module_name)")
2 |
3 | default: release
4 |
5 | node_modules:
6 | # install deps but for now ignore our own install script
7 | # so that we can run it directly in either debug or release
8 | npm install --ignore-scripts
9 |
10 | release: node_modules
11 | V=1 ./node_modules/.bin/node-pre-gyp configure build --loglevel=error
12 | @echo "run 'make clean' for full rebuild"
13 |
14 | debug: node_modules
15 | V=1 ./node_modules/.bin/node-pre-gyp configure build --loglevel=error --debug
16 | @echo "run 'make clean' for full rebuild"
17 |
18 | coverage:
19 | ./scripts/coverage.sh
20 |
21 | clean:
22 | rm -rf lib/binding
23 | rm -rf build
24 | @echo "run 'make distclean' to also clear node_modules, mason_packages, and .mason directories"
25 |
26 | distclean: clean
27 | rm -rf node_modules
28 | rm -rf mason_packages
29 | rm -rf .mason
30 |
31 | xcode: node_modules
32 | ./node_modules/.bin/node-pre-gyp configure -- -f xcode
33 |
34 | @# If you need more targets, e.g. to run other npm scripts, duplicate the last line and change NPM_ARGUMENT
35 | SCHEME_NAME="$(MODULE_NAME)" SCHEME_TYPE=library BLUEPRINT_NAME=$(MODULE_NAME) BUILDABLE_NAME=$(MODULE_NAME).node scripts/create_scheme.sh
36 | SCHEME_NAME="npm test" SCHEME_TYPE=node BLUEPRINT_NAME=$(MODULE_NAME) BUILDABLE_NAME=$(MODULE_NAME).node NODE_ARGUMENT="`npm bin tape`/tape test/*.test.js" scripts/create_scheme.sh
37 |
38 | open build/binding.xcodeproj
39 |
40 | docs:
41 | npm run docs
42 |
43 | unit-test-release:
44 | @echo "Running CXX tests..."
45 | ./build/Release/basic-tests -x
46 | ./build/Release/congestion-tests -x
47 |
48 | unit-test-debug:
49 | @echo "Running CXX tests..."
50 | ./build/Debug/basic-tests
51 | ./build/Debug/congestion-tests
52 |
53 | npmtest:
54 | @echo "Running nodejs tests..."
55 | npm test
56 |
57 | test-release: release unit-test-release npmtest
58 |
59 | test-debug: debug unit-test-debug npmtest
60 |
61 | test: test-release
62 |
63 | .PHONY: test docs
64 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Issue
2 |
3 | What issue is this PR targeting? In a few words, what does the PR add, change, or remove?
4 |
5 | ## Tasklist
6 |
7 | - [ ] Write code & tests
8 | - [ ] Update relevant CHANGELOG and/or the README
9 | - [ ] Get a review
10 | - [ ] Rebase and clean up commits
11 | - [ ] Merge & release (see README for release steps)
12 |
13 | ## Relevant parties and issues
14 |
15 | Tag people, and other issues here.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/mapbox/route-annotator) [](https://codecov.io/gh/mapbox/route-annotator/branch/master)
2 |
3 | # Route Annotator
4 |
5 | This is a NodeJS module that indexes connected node pairs in OSM data, and allows you to query for
6 | meta-data. It's useful for retrieving tag information when you have geometry from your basemap.
7 |
8 | ## Requires
9 |
10 | - Node >= 4
11 |
12 | ## Building
13 |
14 | To install via binaries:
15 |
16 | ```
17 | npm install
18 | ```
19 |
20 | To install from source, first install a C++14 capable compiler then run:
21 |
22 |
23 | ```
24 | make
25 | ```
26 |
27 | ## Testing
28 |
29 | To run the tests (which run both the JS tests and the C++ tests):
30 |
31 | ```
32 | make test
33 | ```
34 |
35 | To run just the JS tests:
36 |
37 | ```
38 | npm test
39 | ```
40 |
41 | To run the C++ tests:
42 |
43 | ```
44 | ./build/Release/basic-tests
45 | ```
46 |
47 | ## Usage
48 |
49 | This library contains three main modules: `Annotator`, `SegmentSpeedLookup`, and `WaySpeedLookup`
50 |
51 | ### Annotator
52 |
53 | The `Annotator()` object is for looking up OSM tag data from OSM node IDs or coordinates.
54 |
55 | **Example:**
56 | ```
57 | var taglookup = new (require('route_annotator')).Annotator();
58 |
59 | // Lookup some nodes and find out which ways they were on,
60 | // and what tags they had
61 | taglookup.loadOSMExtract(path.join(__dirname,'data/winthrop.osm'), (err) => {
62 | if (err) throw err;
63 | var nodes = [50253600,50253602,50137292];
64 | taglookup.annotateRouteFromNodeIds(nodes, (err, wayIds) => {
65 | if (err) throw err;
66 | annotator.getAllTagsForWayId(wayIds[0], (err, tags) => {
67 | if (err) throw err;
68 | console.log(tags);
69 | });
70 | });
71 | });
72 |
73 |
74 | // Do the same thing, but this time use coordinates instead
75 | // of node ids. Internally, a radius search finds the closest
76 | // node within 5m
77 | taglookup.loadOSMExtract(path.join(__dirname,'data/winthrop.osm'), (err) => {
78 | if (err) throw err;
79 | var coords = [[-120.1872774,48.4715898],[-120.1882910,48.4725110]];
80 | taglookup.annotateRouteFromLonLats(coords, (err, wayIds) => {
81 | if (err) throw err;
82 | annotator.getAllTagsForWayId(wayIds[0], (err, tags) => {
83 | if (err) throw err;
84 | console.log(tags);
85 | });
86 | });
87 | });
88 |
89 | ```
90 |
91 | ### SegmentSpeedLookup
92 |
93 | The `SegmentSpeedLookup()` object is for loading segment speed information from CSV files, then looking it up quickly from an in-memory hashtable.
94 |
95 | **Example:**
96 | ```
97 | var segmentspeedlookup = new (require('route_annotator')).SegmentSpeedLookup();
98 |
99 | // Loads example.csv, then looks up the pairs 123-124, 124-125, 125-126
100 | // and prints the speeds for those segments (3 values) as comma-separated
101 | // data
102 | segmentspeedlookup.loadCSV("example.csv", (err) => {
103 | if (err) throw err;
104 | segmentspeedlookup.getRouteSpeeds([123,124,125,126],(err,results) => {
105 | if (err) throw err;
106 | console.log(results.join(","));
107 | });
108 | });
109 | ```
110 |
111 | The `loadCSV` method can also be passed an array of filenames.
112 |
113 | ### WaySpeedLookup
114 |
115 | The `WaySpeedLookup()` object is for loading way speed information from CSV files, then looking it up quickly from an in-memory hashtable.
116 |
117 | **Example:**
118 | ```
119 | var wayspeedlookup = new (require('route_annotator')).WaySpeedLookup();
120 |
121 | // Loads example.csv, then looks up the ways 1111,2222,3333,4444
122 | // and prints the speeds for those ways as comma-separated
123 | // data
124 | wayspeedlookup.loadCSV("example.csv", (err) => {
125 | if (err) throw err;
126 | wayspeedlookup.getRouteSpeeds([1111,2222,3333,4444],(err,results) => {
127 | if (err) throw err;
128 | console.log(results.join(","));
129 | });
130 | });
131 | ```
132 |
133 | The `loadCSV` method can also be passed an array of filenames.
134 |
135 | ---
136 |
137 | ### Example HTTP server
138 | This will not use a tag file.
139 |
140 | ```
141 | npm install
142 | curl --remote-name http://download.geofabrik.de/europe/monaco-latest.osm.pbf
143 | node example-server.js monaco-latest.osm.pbf
144 | ```
145 |
146 | Then, in a new terminal, you should be able to do:
147 |
148 | ```
149 | curl "http://localhost:5000/coordlist/7.422155,43.7368838;7.4230139,43.7369751"
150 | ```
151 | ---
152 |
153 | ### Release
154 |
155 | _You must be a member of the Mapbox npm organization to do this!_
156 |
157 | - `git checkout master`
158 | - Update CHANGELOG.md
159 | - Bump version in package.json, package-lock.json
160 | - `git commit -am "vx.y.z [publish binary] | [republish binary]"` with Changelog list in commit message
161 | - Wait for Travis to finish publishing binaries so that all travis tests pass for the publish binary commit.
162 | - `git tag vx.y.z -a` with Changelog list in tag message
163 | - `git push origin master; git push origin --tags`
164 | - `npm publish --access=public`
165 |
--------------------------------------------------------------------------------
/asan-suppress.txt:
--------------------------------------------------------------------------------
1 | # Ignore ASAN problems coming from within nodejs itself
2 | leak:bin/node
3 |
--------------------------------------------------------------------------------
/binding.gyp:
--------------------------------------------------------------------------------
1 | {
2 | 'includes': [ 'common.gypi' ],
3 | 'variables': {
4 | 'error_on_warnings%':'true',
5 | # includes we don't want warnings for.
6 | # As a variable to make easy to pass to
7 | # cflags (linux) and xcode (mac)
8 | 'system_includes': [
9 | "-isystem <(module_root_dir)/ {
21 | console.log(`${req.method} ${req.url} ${req.headers['user-agent']}`);
22 | next();
23 | });
24 |
25 | const annotator = new bindings.Annotator();
26 |
27 | app.get('/coordlist/:coordlist', coordListHandler(annotator));
28 | app.get('/nodelist/:nodelist', nodeListHandler(annotator));
29 |
30 | annotator.loadOSMExtract(osmFile, tagFile, (err) => {
31 | if (err)
32 | return console.error(err);
33 |
34 | app.listen(port, () => {
35 | console.log(`Listening on 0.0.0.0:${port}`);
36 | });
37 | });
38 | }
39 |
40 |
41 | function nodeListHandler(annotator) {
42 | return (req, res) => {
43 | const nodes = req.params.nodelist
44 | .split(',')
45 | .map(x => parseInt(x, 10));
46 |
47 | const invalid = (x) => !isFinite(x) || x === null;
48 |
49 | if (nodes.some(invalid))
50 | return res.sendStatus(400);
51 |
52 | annotator.annotateRouteFromNodeIds(nodes, (err, wayIds) => {
53 | if (err)
54 | return res.sendStatus(400);
55 |
56 | var response = {"way_indexes": [], "ways_seen": []};
57 | var way_indexes = {};
58 |
59 | async.each(wayIds, (way_id, next) => {
60 | if (way_id === null) return next();
61 | annotator.getAllTagsForWayId(way_id, (err, tags) => {
62 | if (err) res.sendStatus(400);
63 | var wid = tags["_way_id"];
64 | if (!way_indexes.hasOwnProperty(wid)) {
65 | way_indexes[wid] = Object.keys(way_indexes).length;
66 | response.ways_seen.push(tags);
67 | }
68 | response.way_indexes.push(way_indexes[wid]);
69 | next();
70 | });
71 | }, (err, data) => {
72 | res.json(response);
73 | });
74 | });
75 | };
76 | }
77 |
78 |
79 | function coordListHandler(annotator) {
80 | return (req, res) => {
81 | const coordinates = req.params.coordlist
82 | .split(';')
83 | .map(lonLat => lonLat
84 | .split(',')
85 | .map(x => parseFloat(x)));
86 |
87 | const invalid = (x) => !isFinite(x) || x === null;
88 |
89 | if (coordinates.some(lonLat => lonLat.some(invalid)))
90 | return res.sendStatus(400);
91 |
92 | annotator.annotateRouteFromLonLats(coordinates, (err, wayIds) => {
93 | if (err) {
94 | console.error(err);
95 | return res.sendStatus(400);
96 | }
97 |
98 | var response = {"way_indexes": [], "ways_seen": []};
99 | var way_indexes = {};
100 |
101 | async.each(wayIds, (way_id, next) => {
102 | annotator.getAllTagsForWayId(way_id, (err, tags) => {
103 | var wid = tags["_way_id"];
104 | if (!way_indexes.hasOwnProperty(wid)) {
105 | way_indexes[wid] = Object.keys(way_indexes).length;
106 | response.ways_seen.push(tags);
107 | }
108 | response.way_indexes.push(way_indexes[wid]);
109 | next();
110 | });
111 | }, (err, data) => {
112 | res.json(response);
113 | });
114 | });
115 | };
116 | }
117 |
118 |
119 | if (require.main === module) { main(); }
120 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = require('./lib/binding/route_annotator.node');
3 |
--------------------------------------------------------------------------------
/install_mason.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eu
4 | set -o pipefail
5 |
6 | function install() {
7 | mason install $1 $2
8 | mason link $1 $2
9 | }
10 |
11 | # setup mason
12 | ./scripts/setup.sh --config local.env
13 | source local.env
14 |
15 | if [ ! -d mason_packages ] ; then
16 |
17 | install boost 1.63.0
18 | install boost_libtest 1.63.0
19 | install boost_libiostreams 1.63.0
20 | install libosmium 2.12.0
21 | install expat 2.2.0
22 | install bzip2 1.0.6
23 | install protozero 1.5.1
24 | install utfcpp 2.3.4
25 | install zlib 1.2.8
26 | install sparsepp 0.95
27 | install clang-format 3.8.1
28 |
29 | fi
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mapbox/route-annotator",
3 | "version": "0.4.1",
4 | "description": "Bindings for route-annotator",
5 | "keywords": [
6 | "addon",
7 | "native",
8 | "module"
9 | ],
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/mapbox/route-annotator.git"
13 | },
14 | "author": "Mapbox",
15 | "license": "See LICENSE.txt file",
16 | "main": "index.js",
17 | "scripts": {
18 | "test": "tape test/*.test.js",
19 | "coverage": "istanbul cover tape test/*.test.js && codecov",
20 | "install": "node-pre-gyp install --fallback-to-build"
21 | },
22 | "devDependencies": {
23 | "async": "^2.0.0-rc.3",
24 | "codecov": "^3.0.0",
25 | "express": "^4.13.4",
26 | "istanbul": "^0.4.5"
27 | },
28 | "dependencies": {
29 | "@mapbox/cloudfriend": "^4.2.1",
30 | "aws-sdk": "^2.739.0",
31 | "nan": "^2.14.0",
32 | "node-cmake": "^2.5.1",
33 | "node-gyp": "^7.1.0",
34 | "node-pre-gyp": "^0.15.0",
35 | "tape": "^4.6.3"
36 | },
37 | "binary": {
38 | "module_name": "route_annotator",
39 | "module_path": "./lib/binding/",
40 | "host": "https://mapbox-node-binary.s3.amazonaws.com",
41 | "remote_path": "./{name}/v{version}/{configuration}/",
42 | "package_name": "{node_abi}-{platform}-{arch}.tar.gz"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/scripts/build-with-docker.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | set -o pipefail
5 |
6 | if [ ! -d src ] ; then
7 | echo "You need to run this from the root directory of your repository"
8 | exit 1
9 | fi
10 |
11 | docker run \
12 | --interactive \
13 | --volume=`pwd`:/home/mapbox/route-annotator \
14 | --tty \
15 | --workdir=/home/mapbox/route-annotator \
16 | mapbox/route-annotator:linux \
17 | /bin/bash -lc "npm run cmake"
18 |
--------------------------------------------------------------------------------
/scripts/coverage.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 | set -o pipefail
5 |
6 | # http://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation
7 | # https://www.bignerdranch.com/blog/weve-got-you-covered/
8 |
9 | # automatically setup environment
10 |
11 | ./scripts/setup.sh --config local.env
12 | source local.env
13 |
14 | make clean
15 | export CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping"
16 | export LDFLAGS="-fprofile-instr-generate"
17 | mason install llvm-cov ${MASON_LLVM_RELEASE}
18 | mason link llvm-cov ${MASON_LLVM_RELEASE}
19 | make debug
20 | rm -f *profraw
21 | rm -f *gcov
22 | rm -f *profdata
23 | LLVM_PROFILE_FILE="code-%p.profraw" make test-debug
24 | CXX_MODULE=$(./node_modules/.bin/node-pre-gyp reveal module --silent)
25 | llvm-profdata merge -output=code.profdata code-*.profraw
26 | llvm-cov report ${CXX_MODULE} -instr-profile=code.profdata -use-color
27 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color
28 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color --format html > /tmp/coverage.html
29 | echo "open /tmp/coverage.html for HTML version of this report"
30 |
--------------------------------------------------------------------------------
/scripts/create-docker-image.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | set -o pipefail
5 |
6 | if [ ! -d src ] ; then
7 | echo "You need to run this from the root of your repository"
8 | exit 1
9 | fi
10 |
11 | docker build \
12 | -t mapbox/route-annotator:linux \
13 | .
14 |
15 |
--------------------------------------------------------------------------------
/scripts/create_scheme.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 | set -o pipefail
5 |
6 | CONTAINER=build/binding.xcodeproj
7 | OUTPUT="${CONTAINER}/xcshareddata/xcschemes/${SCHEME_NAME}.xcscheme"
8 |
9 | # Required ENV vars:
10 | # - SCHEME_TYPE: type of the scheme
11 | # - SCHEME_NAME: name of the scheme
12 |
13 | # Optional ENV vars:
14 | # - NODE_ARGUMENT (defaults to "")
15 | # - BUILDABLE_NAME (defaults ot SCHEME_NAME)
16 | # - BLUEPRINT_NAME (defaults ot SCHEME_NAME)
17 |
18 |
19 | # Try to reuse the existing Blueprint ID if the scheme already exists.
20 | if [ -f "${OUTPUT}" ]; then
21 | BLUEPRINT_ID=$(sed -n "s/[ \t]*BlueprintIdentifier *= *\"\([A-Z0-9]\{24\}\)\"/\\1/p" "${OUTPUT}" | head -1)
22 | fi
23 |
24 | NODE_ARGUMENT=${NODE_ARGUMENT:-}
25 | BLUEPRINT_ID=${BLUEPRINT_ID:-$(hexdump -n 12 -v -e '/1 "%02X"' /dev/urandom)}
26 | BUILDABLE_NAME=${BUILDABLE_NAME:-${SCHEME_NAME}}
27 | BLUEPRINT_NAME=${BLUEPRINT_NAME:-${SCHEME_NAME}}
28 |
29 | mkdir -p "${CONTAINER}/xcshareddata/xcschemes"
30 |
31 | sed "\
32 | s#{{BLUEPRINT_ID}}#${BLUEPRINT_ID}#;\
33 | s#{{BLUEPRINT_NAME}}#${BLUEPRINT_NAME}#;\
34 | s#{{BUILDABLE_NAME}}#${BUILDABLE_NAME}#;\
35 | s#{{CONTAINER}}#${CONTAINER}#;\
36 | s#{{WORKING_DIRECTORY}}#$(pwd)#;\
37 | s#{{NODE_PATH}}#$(dirname `which node`)#;\
38 | s#{{NODE_ARGUMENT}}#${NODE_ARGUMENT}#" \
39 | scripts/${SCHEME_TYPE}.xcscheme > "${OUTPUT}"
40 |
--------------------------------------------------------------------------------
/scripts/fix_format.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -o errexit
4 | set -o pipefail
5 | set -o nounset
6 |
7 | # Runs the Clang Formatter in parallel on the code base.
8 | # Return codes:
9 | # - 1 there are files to be formatted
10 | # - 0 everything looks fine
11 |
12 | # Get CPU count
13 | OS=$(uname)
14 | NPROC=1
15 | if [[ $OS = "Linux" ]] ; then
16 | NPROC=$(nproc)
17 | elif [[ ${OS} = "Darwin" ]] ; then
18 | NPROC=$(sysctl -n hw.physicalcpu)
19 | fi
20 |
21 | # Discover clang-format
22 | if type clang-format-7.0 2> /dev/null ; then
23 | CLANG_FORMAT=clang-format-7.0
24 | elif type clang-format 2> /dev/null ; then
25 | # Clang format found, but need to check version
26 | CLANG_FORMAT=clang-format
27 | V=$(clang-format --version)
28 | if [[ $V != *6.0* ]] ; then
29 | echo "Installed clang-format is not version 7.0"
30 | if [ ! -f $(pwd)/mason_packages/.link/bin/clang-format ] ; then
31 | echo "Installing clang-format 7.0 via mason"
32 | if [ ! -d mason ] ; then
33 | mkdir -p ./mason
34 | curl -sSfL https://github.com/mapbox/mason/archive/v0.18.0.tar.gz | tar --gunzip --extract --strip-components=1 --exclude="*md" --exclude="test*" --directory=./mason
35 | fi
36 | ./mason/mason install clang-format 7.0.0
37 | ./mason/mason link clang-format 7.0.0
38 | fi
39 | echo "Using clang-format 7.0.0 from $(pwd)/mason_packages/.link/bin"
40 | PATH="$(pwd)/mason_packages/.link/bin:$PATH"
41 | #exit 1
42 | fi
43 | else
44 | echo "No clang-format found"
45 | if [ ! -f $(pwd)/mason_packages/.link/bin/clang-format ] ; then
46 | echo "Installing clang-format 7.0.0 via mason"
47 | if [ ! -d mason ] ; then
48 | mkdir -p ./mason
49 | curl -sSfL https://github.com/mapbox/mason/archive/v0.18.0.tar.gz | tar --gunzip --extract --strip-components=1 --exclude="*md" --exclude="test*" --directory=./mason
50 | fi
51 | ./mason/mason install clang-format 7.0.0
52 | ./mason/mason link clang-format 7.0.0
53 | fi
54 | echo "Using clang-format 7.0.0 from $(pwd)/mason_packages/.link/bin"
55 | CLANG_FORMAT=clang-format
56 | PATH="$(pwd)/mason_packages/.link/bin:$PATH"
57 | fi
58 |
59 | find src 'test/basic' 'test/congestion' -type f -name '*.hpp' -o -name '*.cpp' \
60 | | xargs -I{} -P ${NPROC} ${CLANG_FORMAT} -i -style=file {}
61 |
62 |
--------------------------------------------------------------------------------
/scripts/format.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -o errexit
4 | set -o pipefail
5 | set -o nounset
6 |
7 | # Runs the Clang Formatter in parallel on the code base.
8 | # Return codes:
9 | # - 1 there are files to be formatted
10 | # - 0 everything looks fine
11 |
12 | # Get CPU count
13 | OS=$(uname)
14 | NPROC=1
15 | if [[ $OS = "Linux" ]] ; then
16 | NPROC=$(nproc)
17 | elif [[ ${OS} = "Darwin" ]] ; then
18 | NPROC=$(sysctl -n hw.physicalcpu)
19 | fi
20 |
21 | # Discover clang-format
22 | if type clang-format-3.8 2> /dev/null ; then
23 | CLANG_FORMAT=clang-format-3.8
24 | elif type clang-format 2> /dev/null ; then
25 | # Clang format found, but need to check version
26 | CLANG_FORMAT=clang-format
27 | V=$(clang-format --version)
28 | if [[ $V != *3.8* ]] ; then
29 | echo "clang-format is not 3.8 (returned ${V})"
30 | #exit 1
31 | fi
32 | else
33 | echo "No appropriate clang-format found (expected clang-format-3.8, or clang-format)"
34 | exit 1
35 | fi
36 |
37 | find src 'test/basic' 'test/congestion' -type f -name '*.hpp' -o -name '*.cpp' \
38 | | xargs -I{} -P ${NPROC} ${CLANG_FORMAT} -i -style=file {}
39 |
40 |
41 | dirty=$(git ls-files --modified)
42 |
43 | if [[ $dirty ]]; then
44 | echo "The following files do not adhere to the .clang-format style file:"
45 | echo $dirty
46 | exit 1
47 | else
48 | exit 0
49 | fi
50 |
--------------------------------------------------------------------------------
/scripts/install_deps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eu
4 | set -o pipefail
5 |
6 | function install() {
7 | mason install $1 $2
8 | mason link $1 $2
9 | }
10 |
11 | # setup mason
12 | ./scripts/setup.sh --config local.env
13 | source local.env
14 |
15 | if [ ! -d ./mason_packages/.link ]; then
16 | install boost 1.63.0
17 | install boost_libtest 1.63.0
18 | install libosmium 2.12.0
19 | install expat 2.2.0
20 | install bzip2 1.0.6
21 | install protozero 1.5.1
22 | install utfcpp 2.3.4
23 | install zlib 1.2.8
24 | fi
25 |
--------------------------------------------------------------------------------
/scripts/library.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/scripts/node.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
46 |
49 |
50 |
51 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
70 |
71 |
72 |
73 |
74 |
75 |
81 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 | set -o pipefail
5 |
6 | export COMMIT_MESSAGE=$(git log --format=%B --no-merges -n 1 | tr -d '\n')
7 |
8 | # `is_pr_merge` is designed to detect if a gitsha represents a normal
9 | # push commit (to any branch) or whether it represents travis attempting
10 | # to merge between the origin and the upstream branch.
11 | # For more details see: https://docs.travis-ci.com/user/pull-requests
12 | function is_pr_merge() {
13 | # Get the commit message via git log
14 | # This should always be the exactly the text the developer provided
15 | local COMMIT_LOG=${COMMIT_MESSAGE}
16 |
17 | # Get the commit message via git show
18 | # If the gitsha represents a merge then this will
19 | # look something like "Merge e3b1981 into 615d2a3"
20 | # Otherwise it will be the same as the "git log" output
21 | export COMMIT_SHOW=$(git show -s --format=%B | tr -d '\n')
22 |
23 | if [[ "${COMMIT_LOG}" != "${COMMIT_SHOW}" ]]; then
24 | echo true
25 | fi
26 | }
27 |
28 | # `publish` is used to publish binaries to s3 via commit messages if:
29 | # - the commit message includes [publish binary]
30 | # - the commit message includes [republish binary]
31 | # - the commit is not a pr_merge (checked with `is_pr_merge` function)
32 | function publish() {
33 | echo "dumping binary meta..."
34 | ./node_modules/.bin/node-pre-gyp reveal --loglevel=error $@
35 |
36 | echo "determining publishing status..."
37 |
38 | if [[ $(is_pr_merge) ]]; then
39 | echo "Skipping publishing because this is a PR merge commit"
40 | else
41 | echo "Commit message: ${COMMIT_MESSAGE}"
42 |
43 | if [[ ${COMMIT_MESSAGE} =~ "[publish binary]" ]]; then
44 | echo "Publishing"
45 | ./node_modules/.bin/node-pre-gyp package publish $@
46 | elif [[ ${COMMIT_MESSAGE} =~ "[republish binary]" ]]; then
47 | echo "Re-Publishing"
48 | ./node_modules/.bin/node-pre-gyp package unpublish publish $@
49 | else
50 | echo "Skipping publishing since we did not detect either [publish binary] or [republish binary] in commit message"
51 | fi
52 | fi
53 | }
54 |
55 | function usage() {
56 | >&2 echo "Usage"
57 | >&2 echo ""
58 | >&2 echo "$ ./scripts/publish.sh "
59 | >&2 echo ""
60 | >&2 echo "All args are forwarded to node-pre-gyp like --debug"
61 | >&2 echo ""
62 | exit 1
63 | }
64 |
65 | # https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
66 | for i in "$@"
67 | do
68 | case $i in
69 | -h | --help)
70 | usage
71 | shift
72 | ;;
73 | *)
74 | ;;
75 | esac
76 | done
77 |
78 | publish $@
79 |
--------------------------------------------------------------------------------
/scripts/run-example-server.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | set -o pipefail
5 |
6 | if [ ! -d src ] ; then
7 | echo "You need to run this from the root directory of your repository"
8 | exit 1
9 | fi
10 |
11 | docker run \
12 | --interactive \
13 | --volume=`pwd`:/home/mapbox/route-annotator \
14 | --tty \
15 | --publish=5000:5000 \
16 | --workdir=/home/mapbox/route-annotator \
17 | mapbox/route-annotator:linux \
18 | /bin/bash -lc "node ./example-server.js monaco-latest.osm.pbf"
19 |
--------------------------------------------------------------------------------
/scripts/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 | set -o pipefail
5 |
6 | export MASON_RELEASE="${MASON_RELEASE:-751b5c5d34d26aa1cba19700c2471d73d2740d09}"
7 | export MASON_LLVM_RELEASE="${MASON_LLVM_RELEASE:-9.0.1}"
8 |
9 | PLATFORM=$(uname | tr A-Z a-z)
10 | if [[ ${PLATFORM} == 'darwin' ]]; then
11 | PLATFORM="osx"
12 | fi
13 |
14 | MASON_URL="https://s3.amazonaws.com/mason-binaries/${PLATFORM}-$(uname -m)"
15 |
16 | llvm_toolchain_dir="$(pwd)/.toolchain"
17 |
18 | function run() {
19 | local config=${1}
20 | # unbreak bash shell due to rvm bug on osx: https://github.com/direnv/direnv/issues/210#issuecomment-203383459
21 | # this impacts any usage of scripts that are source'd (like this one)
22 | if [[ "${TRAVIS_OS_NAME:-}" == "osx" ]]; then
23 | echo 'shell_session_update() { :; }' > ~/.direnvrc
24 | fi
25 |
26 | #
27 | # COMPILER TOOLCHAIN
28 | #
29 |
30 | # We install clang++ without the mason client for a couple reasons:
31 | # 1) decoupling makes it viable to use a custom branch of mason that might
32 | # modify the upstream s3 bucket in a such a way that does not give
33 | # it access to build tools like clang++
34 | # 2) Allows us to short-circuit and use a global clang++ install if it
35 | # is available to save space for local builds.
36 | GLOBAL_CLANG="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/clang++/${MASON_LLVM_RELEASE}"
37 | GLOBAL_LLVM="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/llvm/${MASON_LLVM_RELEASE}"
38 | if [[ -d ${GLOBAL_LLVM} ]]; then
39 | echo "Detected '${GLOBAL_LLVM}/bin/clang++', using it"
40 | local llvm_toolchain_dir=${GLOBAL_LLVM}
41 | elif [[ -d ${GLOBAL_CLANG} ]]; then
42 | echo "Detected '${GLOBAL_CLANG}/bin/clang++', using it"
43 | local llvm_toolchain_dir=${GLOBAL_CLANG}
44 | elif [[ -d ${GLOBAL_CLANG} ]]; then
45 | echo "Detected '${GLOBAL_CLANG}/bin/clang++', using it"
46 | local llvm_toolchain_dir=${GLOBAL_CLANG}
47 | elif [[ ! -d ${llvm_toolchain_dir} ]]; then
48 | BINARY="${MASON_URL}/clang++/${MASON_LLVM_RELEASE}.tar.gz"
49 | echo "Downloading ${BINARY}"
50 | mkdir -p ${llvm_toolchain_dir}
51 | curl -sSfL ${BINARY} | tar --gunzip --extract --strip-components=1 --directory=${llvm_toolchain_dir}
52 | fi
53 |
54 | #
55 | # MASON
56 | #
57 |
58 | function setup_mason() {
59 | local install_dir=${1}
60 | local mason_release=${2}
61 | if [[ ! -d ${install_dir} ]]; then
62 | mkdir -p ${install_dir}
63 | curl -sSfL https://github.com/mapbox/mason/archive/${mason_release}.tar.gz | tar --gunzip --extract --strip-components=1 --directory=${install_dir}
64 | fi
65 | }
66 |
67 | setup_mason $(pwd)/.mason ${MASON_RELEASE}
68 |
69 | #
70 | # ENV SETTINGS
71 | #
72 |
73 | echo "export PATH=${llvm_toolchain_dir}/bin:$(pwd)/.mason:$(pwd)/mason_packages/.link/bin:"'${PATH}' > ${config}
74 | echo "export CXX=${llvm_toolchain_dir}/bin/clang++" >> ${config}
75 | echo "export MASON_RELEASE=${MASON_RELEASE}" >> ${config}
76 | echo "export MASON_LLVM_RELEASE=${MASON_LLVM_RELEASE}" >> ${config}
77 | # https://github.com/google/sanitizers/wiki/AddressSanitizerAsDso
78 | RT_BASE=${llvm_toolchain_dir}/lib/clang/${MASON_LLVM_RELEASE}/lib/$(uname | tr A-Z a-z)/libclang_rt
79 | if [[ $(uname -s) == 'Darwin' ]]; then
80 | RT_PRELOAD=${RT_BASE}.asan_osx_dynamic.dylib
81 | else
82 | RT_PRELOAD=${RT_BASE}.asan-x86_64.so
83 | fi
84 | echo "export MASON_LLVM_RT_PRELOAD=${RT_PRELOAD}" >> ${config}
85 | SUPPRESSION_FILE="/tmp/leak_suppressions.txt"
86 | echo "leak:__strdup" > ${SUPPRESSION_FILE}
87 | echo "leak:v8::internal" >> ${SUPPRESSION_FILE}
88 | echo "leak:node::CreateEnvironment" >> ${SUPPRESSION_FILE}
89 | echo "leak:node::Init" >> ${SUPPRESSION_FILE}
90 | echo "export ASAN_SYMBOLIZER_PATH=$(which llvm-symbolizer)" >> ${config}
91 | echo "export UBSAN_OPTIONS=print_stacktrace=1" >> ${config}
92 | echo "export LSAN_OPTIONS=suppressions=${SUPPRESSION_FILE}" >> ${config}
93 | echo "export ASAN_OPTIONS=symbolize=1:abort_on_error=1:detect_container_overflow=1:check_initialization_order=1:detect_stack_use_after_return=1" >> ${config}
94 | echo 'export MASON_SANITIZE="-fsanitize=address,undefined -fno-sanitize=vptr,function"' >> ${config}
95 | echo 'export MASON_SANITIZE_CXXFLAGS="${MASON_SANITIZE} -fno-sanitize=vptr,function -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common"' >> ${config}
96 | echo 'export MASON_SANITIZE_LDFLAGS="${MASON_SANITIZE}"' >> ${config}
97 |
98 | exit 0
99 | }
100 |
101 | function usage() {
102 | >&2 echo "Usage"
103 | >&2 echo ""
104 | >&2 echo "$ ./scripts/setup.sh --config local.env"
105 | >&2 echo "$ source local.env"
106 | >&2 echo ""
107 | exit 1
108 | }
109 |
110 | if [[ ! ${1:-} ]]; then
111 | usage
112 | fi
113 |
114 | # https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
115 | for i in "$@"
116 | do
117 | case $i in
118 | --config)
119 | if [[ ! ${2:-} ]]; then
120 | usage
121 | fi
122 | shift
123 | run $@
124 | ;;
125 | -h | --help)
126 | usage
127 | shift
128 | ;;
129 | *)
130 | usage
131 | ;;
132 | esac
133 | done
134 |
--------------------------------------------------------------------------------
/src/annotator.cpp:
--------------------------------------------------------------------------------
1 | #include "annotator.hpp"
2 | #include "extractor.hpp"
3 |
4 | #include
5 |
6 | // For boost RTree
7 | #include
8 | #include
9 |
10 | #include
11 | //#include
12 |
13 | RouteAnnotator::RouteAnnotator(const Database &db) : db(db) {}
14 |
15 | std::vector
16 | RouteAnnotator::coordinates_to_internal(const std::vector &points)
17 | {
18 |
19 | static const boost::geometry::strategy::distance::haversine haversine(6372795.0);
20 | static const double MAX_DISTANCE = 5;
21 |
22 | if (!db.rtree)
23 | throw RtreeError("RTree is null - call build_rtree() on database before use");
24 |
25 | std::vector internal_nodeids;
26 | for (const auto &point : points)
27 | {
28 | std::vector rtree_results;
29 |
30 | // Search for the nearest point to the one supplied
31 | db.rtree->query(boost::geometry::index::nearest(point, 1),
32 | std::back_inserter(rtree_results));
33 |
34 | // If we got exactly one hit (we should), append it to our nodeids list, if it
35 | // was within 1m
36 | /*
37 | BOOST_LOG_TRIVIAL(debug) << "Distance from " << point.get<0>() << "," << point.get<1>()
38 | << " to " << rtree_results[0].first.get<0>() << ","
39 | << rtree_results[0].first.get<1>() << " is "
40 | << boost::geometry::distance(point, rtree_results[0].first,
41 | haversine)
42 | << "\n";
43 | */
44 | if (rtree_results.size() == 1 &&
45 | boost::geometry::distance(point, rtree_results[0].first, haversine) < MAX_DISTANCE)
46 | {
47 | internal_nodeids.push_back(rtree_results[0].second);
48 | }
49 | // otherwise, insert a 0 value, this coordinate didn't match
50 | else
51 | {
52 | internal_nodeids.push_back(INVALID_INTERNAL_NODEID);
53 | }
54 | }
55 | return internal_nodeids;
56 | }
57 |
58 | std::vector
59 | RouteAnnotator::external_to_internal(const std::vector &external_nodeids)
60 | {
61 | // Convert external node ids into internal ones
62 | std::vector results;
63 | std::for_each(external_nodeids.begin(), external_nodeids.end(),
64 | [this, &results](const external_nodeid_t n) {
65 | const auto internal_node_id = db.external_internal_map.find(n);
66 | if (internal_node_id == db.external_internal_map.end())
67 | {
68 | // Push an invalid nodeid into the list if we didn't match
69 | results.push_back(INVALID_INTERNAL_NODEID);
70 | }
71 | else
72 | {
73 | results.push_back(internal_node_id->second);
74 | }
75 | });
76 | return results;
77 | }
78 |
79 | annotated_route_t RouteAnnotator::annotateRoute(const std::vector &route)
80 | {
81 | annotated_route_t result;
82 |
83 | for (std::size_t i = 0; i < route.size() - 1; i++)
84 | {
85 | const auto way_id = [&]() {
86 | if (route[i] < route[i + 1])
87 | {
88 | return db.pair_way_map.find(std::make_pair(route[i], route[i + 1]));
89 | }
90 | else
91 | {
92 | return db.pair_way_map.find(std::make_pair(route[i + 1], route[i]));
93 | }
94 | }();
95 | if (way_id != db.pair_way_map.end())
96 | {
97 | result.push_back(way_id->second.id);
98 | }
99 | else
100 | {
101 | result.push_back(INVALID_WAYID);
102 | }
103 | }
104 | return result;
105 | }
106 |
107 | std::string RouteAnnotator::get_tag_key(const std::size_t index)
108 | {
109 | return db.getstring(db.key_value_pairs[index].first);
110 | }
111 |
112 | std::string RouteAnnotator::get_tag_value(const std::size_t index)
113 | {
114 | return db.getstring(db.key_value_pairs[index].second);
115 | }
116 |
117 | tagrange_t RouteAnnotator::get_tag_range(const wayid_t way_id) { return db.way_tag_ranges[way_id]; }
118 |
119 | wayid_t RouteAnnotator::get_external_way_id(const wayid_t way_id)
120 | {
121 | return db.internal_to_external_way_id_map[way_id];
122 | }
123 |
--------------------------------------------------------------------------------
/src/annotator.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include
5 |
6 | #include "database.hpp"
7 | #include "types.hpp"
8 |
9 | /**
10 | * This is the wrapper object for the route annotator. It presents a simple
11 | * API for getting tag information back from a sequence of OSM nodes, or
12 | * real coordinates.
13 | */
14 | struct RouteAnnotator
15 | {
16 | public:
17 | /**
18 | * Constructs a route annotator.
19 | *
20 | * @param osmfilename the name of the OSM file to load
21 | */
22 | RouteAnnotator(const Database &db);
23 |
24 | /**
25 | * Convert a list of lon/lat coordinates into internal
26 | * node ids.
27 | *
28 | * @param points a vector of lon/lat coordinates
29 | * @return a vector of internal node ids. Will return INVALID_INTERNAL_NODEID
30 | * for coordinates that couldn't be matched.
31 | */
32 | std::vector coordinates_to_internal(const std::vector &points);
33 |
34 | /**
35 | * Convert OSM node ids (64bit) into internal node ids (32 bit).
36 | *
37 | * @param external_nodeids the list of OSM nodeids for the route
38 | * @return a vector of internal node ids. Will return INVALID_INTERNAL_NODEID
39 | * for coordinates that couldn't be matched.
40 | */
41 | std::vector
42 | external_to_internal(const std::vector &external_nodeids);
43 |
44 | /**
45 | * Gets the tags for a route
46 | *
47 | * @param route a list of connected internal node ids
48 | * @return a vector of way ids that each pair of nodes on the route touches,
49 | * or INVALID_WAYID values if there was no way found for a pair of nodes.
50 | */
51 | annotated_route_t annotateRoute(const std::vector &route);
52 |
53 | /**
54 | * Gets the key part for a tag
55 | *
56 | * @param index the index for the tag
57 | * @return the actual key name as a string
58 | */
59 | std::string get_tag_key(const std::size_t index);
60 |
61 | /**
62 | * Gets the value part of a tag
63 | *
64 | * @param index the index for the tag
65 | * @return the value of the tag as a string
66 | */
67 | std::string get_tag_value(const std::size_t index);
68 |
69 | /**
70 | * Gets the start and end indexes for the tags for a way.
71 | * You can iterate over the values between these two
72 | * indexes to get all the key/value pairs.
73 | *
74 | * @param wayid the way id to get the list of tags for
75 | * @return the start and end indexes for the tags for this way
76 | */
77 | tagrange_t get_tag_range(const wayid_t wayid);
78 |
79 | wayid_t get_external_way_id(const wayid_t way_id);
80 |
81 | struct RtreeError final : std::runtime_error
82 | {
83 | using base = std::runtime_error;
84 | using base::base;
85 | };
86 |
87 | private:
88 | // This is where all the data lives
89 | const Database &db;
90 | };
91 |
--------------------------------------------------------------------------------
/src/database.cpp:
--------------------------------------------------------------------------------
1 | #include "database.hpp"
2 |
3 | #include
4 |
5 | Database::Database() {}
6 | Database::Database(bool _createRTree) : createRTree(_createRTree) {}
7 |
8 | void Database::build_rtree()
9 | {
10 | rtree = createRTree
11 | ? std::make_unique<
12 | boost::geometry::index::rtree>>(
13 | used_nodes_list.begin(), used_nodes_list.end())
14 | : nullptr;
15 | }
16 |
17 | void Database::compact()
18 | {
19 | // Tricks to free memory, swap out data with empty versions
20 | // This frees the memory. shrink_to_fit doesn't guarantee that.
21 | std::vector().swap(used_nodes_list);
22 | std::unordered_map().swap(string_index);
23 |
24 | // Hint that these data structures can be shrunk.
25 | string_data.shrink_to_fit();
26 | string_offsets.shrink_to_fit();
27 |
28 | way_tag_ranges.shrink_to_fit();
29 | key_value_pairs.shrink_to_fit();
30 | string_offsets.shrink_to_fit();
31 | }
32 |
33 | std::string Database::getstring(const stringid_t stringid) const
34 | {
35 | BOOST_ASSERT(stringid < string_offsets.size());
36 | auto stringinfo = string_offsets[stringid];
37 | std::string result(string_data.begin() + stringinfo.first,
38 | string_data.begin() + stringinfo.first + stringinfo.second);
39 | return result;
40 | }
41 |
42 | stringid_t Database::addstring(const char *str)
43 | {
44 | auto idx = string_index.find(str);
45 | if (idx == string_index.end())
46 | {
47 | BOOST_ASSERT(string_index.size() < std::numeric_limits::max());
48 | string_index.emplace(str, static_cast(string_index.size()));
49 | auto string_length =
50 | static_cast(std::min(255, std::strlen(str)));
51 | std::copy(str, str + string_length, std::back_inserter(string_data));
52 | BOOST_ASSERT(string_data.size() < std::numeric_limits::max());
53 | BOOST_ASSERT(string_data.size() >= string_length);
54 | string_offsets.emplace_back(static_cast(string_data.size()) - string_length,
55 | string_length);
56 | // Return the position we know we just added out string at
57 | return static_cast(string_index.size() - 1);
58 | }
59 | BOOST_ASSERT(idx->second < std::numeric_limits::max());
60 | return static_cast(idx->second);
61 | }
62 |
63 | void Database::dump() const
64 | {
65 | std::cout << "String data is " << (string_data.capacity() * sizeof(char))
66 | << " Used: " << (string_data.size() * sizeof(char)) << "\n";
67 | std::cout << "way_tag_ranges = Allocated "
68 | << (way_tag_ranges.capacity() * sizeof(decltype(way_tag_ranges)::value_type))
69 | << " Used: "
70 | << (way_tag_ranges.size() * sizeof(decltype(way_tag_ranges)::value_type)) << "\n";
71 | std::cout << "keyvalue = Allocated "
72 | << (key_value_pairs.capacity() * sizeof(decltype(key_value_pairs)::value_type))
73 | << " Used: "
74 | << (key_value_pairs.size() * sizeof(decltype(key_value_pairs)::value_type)) << "\n";
75 | std::cout << "stringoffset = Allocated "
76 | << (string_offsets.capacity() * sizeof(decltype(string_offsets)::value_type))
77 | << " Used: "
78 | << (string_offsets.size() * sizeof(decltype(string_offsets)::value_type)) << "\n";
79 |
80 | std::cout << "pair_way_map = Allocated "
81 | << (pair_way_map.size() *
82 | (sizeof(decltype(pair_way_map)::value_type) + sizeof(wayid_t)))
83 | << " Load factor: " << pair_way_map.load_factor()
84 | << " Buckets: " << pair_way_map.bucket_count() << "\n";
85 | }
86 |
--------------------------------------------------------------------------------
/src/database.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "types.hpp"
4 | #include
5 |
6 | /**
7 | * The in-memory database holds all the useful data in memory.
8 | * Data is added here by the Extractor, then used by
9 | * the RouteAnnotator.
10 | */
11 | struct Database
12 | {
13 | Database();
14 | Database(bool _createRTree);
15 |
16 | public:
17 | /**
18 | * Only create RTree if explicitly told to
19 | */
20 | bool createRTree = false;
21 | /**
22 | * A map of internal node id pairs to the way they belong to
23 | * TODO: support multiple ways???
24 | */
25 | std::unordered_map pair_way_map;
26 |
27 | /**
28 | * Stores the start/end indexes for the tags for a way. Values
29 | * here refer to the key_value_pairs vector.
30 | */
31 | std::vector way_tag_ranges;
32 |
33 | /**
34 | * holds the key and value indexes for a tag. The values in
35 | * way_tag_ranges refer to this vector.
36 | */
37 | std::vector key_value_pairs;
38 |
39 | /**
40 | * The RTree we use to find internal nodes using coordinates.
41 | */
42 | std::unique_ptr>> rtree;
43 |
44 | /**
45 | * The map of external (OSM 64 bit) node ids to our internal
46 | * node ID values (32 bit, to save space)
47 | */
48 | std::unordered_map external_internal_map;
49 |
50 | /**
51 | * Adds a string to our character buffer
52 | *
53 | * @param str a C-style null-terminated string
54 | * @return the ID of the string so it can be found later
55 | */
56 | stringid_t addstring(const char *str);
57 |
58 | /**
59 | * Gets a string from the character buffer
60 | *
61 | * @param stringid the id of the desired string, as returned by
62 | * addstring earlier
63 | * @return the requested string
64 | */
65 | std::string getstring(const stringid_t stringid) const;
66 |
67 | /**
68 | * Builds the RTree. Needs to be called after
69 | * all OSM data parsing has been added.
70 | */
71 | void build_rtree();
72 | /**
73 | * Reclaims memory by discarding temporary data
74 | * and shrinking vectors that have auto-grown. Needs to be called after
75 | * all OSM data parsing has been added.
76 | */
77 | void compact();
78 |
79 | /**
80 | * Dumps some info about what's in our database
81 | */
82 | void dump() const;
83 |
84 | // A temporary list of the nodes that we actually used
85 | std::vector used_nodes_list;
86 |
87 | // A list of the OSM way IDs
88 | std::vector internal_to_external_way_id_map;
89 |
90 | private:
91 | // The character data for all strings
92 | std::vector string_data;
93 | // The start/end positions of each string in the string_data buffer
94 | std::vector string_offsets;
95 | // A temporary lookup table so that we can re-use strings
96 | std::unordered_map string_index;
97 | // TODO pull rtree creation out of compact function
98 | };
99 |
--------------------------------------------------------------------------------
/src/extractor.cpp:
--------------------------------------------------------------------------------
1 | #include "extractor.hpp"
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | // Basic libosmium includes
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | // Needed for lon/lat lookups inside way handler
15 | #include
16 | #include
17 |
18 | // For iterating over pairs of nodes/coordinates
19 | #include
20 | #include
21 |
22 | #include
23 |
24 | // Node indexing types for libosmium
25 | // We need these because we need access to the lon/lat for the noderefs inside the way
26 | // callback in our handler.
27 | typedef osmium::index::map::Dummy index_neg_type;
28 | typedef osmium::index::map::SparseMemArray
29 | index_pos_type;
30 | // typedef osmium::index::map::DenseMmapArray
31 | // index_pos_type;
32 | typedef osmium::handler::NodeLocationsForWays location_handler_type;
33 |
34 | void Extractor::ParseTags(std::ifstream &tagfile)
35 | {
36 | std::string line;
37 | while (std::getline(tagfile, line))
38 | {
39 | tags_filter.add_rule(true, osmium::TagMatcher(line));
40 | }
41 | }
42 |
43 | void Extractor::SetupDatabase()
44 | {
45 | if (db.createRTree)
46 | {
47 | std::cout << "Constructing RTree ... " << std::flush;
48 | db.build_rtree();
49 | }
50 | db.compact();
51 | std::cout << "done\n" << std::flush;
52 | db.dump();
53 | }
54 |
55 | void Extractor::ParseFile(const osmium::io::File &osmfile)
56 | {
57 | osmium::io::Reader fileReader(osmfile, osmium::osm_entity_bits::way |
58 | (db.createRTree ? osmium::osm_entity_bits::node
59 | : osmium::osm_entity_bits::nothing));
60 | if (db.createRTree)
61 | {
62 | int fd = open("nodes.cache", O_RDWR | O_CREAT, 0666);
63 | if (fd == -1)
64 | {
65 | throw std::runtime_error(strerror(errno));
66 | }
67 | index_pos_type index_pos{fd};
68 | index_neg_type index_neg;
69 | location_handler_type location_handler(index_pos, index_neg);
70 | location_handler.ignore_errors();
71 | osmium::apply(fileReader, location_handler, *this);
72 | }
73 | else
74 | {
75 | osmium::apply(fileReader, *this);
76 | }
77 | std::cout << "done\n";
78 | std::cout << "Number of node pairs indexed: " << db.pair_way_map.size() << "\n";
79 | std::cout << "Number of ways indexed: " << db.way_tag_ranges.size() << "\n";
80 | }
81 |
82 | Extractor::Extractor(const std::vector &osm_files, Database &db) : db(db)
83 | {
84 | for (const std::string &file : osm_files)
85 | {
86 | std::cout << "Parsing " << file << " ... " << std::flush;
87 | osmium::io::File osmfile{file};
88 | ParseFile(osmfile);
89 | }
90 | SetupDatabase();
91 | }
92 |
93 | Extractor::Extractor(const std::vector &osm_files,
94 | Database &db,
95 | const std::string &tagfilename)
96 | : db(db)
97 | {
98 | // add tags to tag filter object for use in way parsing
99 | if (!tagfilename.empty())
100 | {
101 | std::cout << "Parsing " << tagfilename << " ... " << std::flush;
102 | std::ifstream tagfile(tagfilename);
103 | if (!tagfile.is_open())
104 | {
105 | throw std::runtime_error(strerror(errno));
106 | }
107 | ParseTags(tagfile);
108 | }
109 | for (const std::string &file : osm_files)
110 | {
111 | std::cout << "Parsing " << file << " ... " << std::flush;
112 | osmium::io::File osmfile{file};
113 | ParseFile(osmfile);
114 | }
115 | SetupDatabase();
116 | }
117 |
118 | Extractor::Extractor(const char *buffer,
119 | std::size_t buffersize,
120 | const std::string &format,
121 | Database &db)
122 | : db(db)
123 | {
124 | std::cout << "Parsing OSM buffer in format " << format << " ... " << std::flush;
125 | osmium::io::File osmfile{buffer, buffersize, format};
126 | ParseFile(osmfile);
127 | SetupDatabase();
128 | }
129 |
130 | bool Extractor::FilterWay(const osmium::Way &way)
131 | {
132 | if (tags_filter.empty())
133 | {
134 | // if not filtering by tags, filter by certain highway types by default
135 | const char *highway = way.tags().get_value_by_key("highway");
136 | std::vector highway_types = {
137 | "motorway", "motorway_link", "trunk", "trunk_link", "primary",
138 | "primary_link", "secondary", "secondary_link", "tertiary", "tertiary_link",
139 | "residential", "living_street", "unclassified", "service", "ferry",
140 | "movable", "shuttle_train", "default"};
141 | return highway &&
142 | std::any_of(highway_types.begin(), highway_types.end(),
143 | [&highway](const auto &way) { return std::strcmp(highway, way) == 0; });
144 | }
145 | else
146 | {
147 | for (auto &tag : way.tags())
148 | {
149 | // use this way if we find a tag that we're interested in
150 | if (tags_filter(tag))
151 | {
152 | return true;
153 | }
154 | }
155 | // if tag is not found, filter by certain highway types by default
156 | const char *highway = way.tags().get_value_by_key("highway");
157 | std::vector highway_types = {
158 | "motorway", "motorway_link", "trunk", "trunk_link", "primary",
159 | "primary_link", "secondary", "secondary_link", "tertiary", "tertiary_link",
160 | "residential", "living_street", "unclassified", "service", "ferry",
161 | "movable", "shuttle_train", "default"};
162 | return highway &&
163 | std::any_of(highway_types.begin(), highway_types.end(),
164 | [&highway](const auto &way) { return std::strcmp(highway, way) == 0; });
165 | }
166 | }
167 |
168 | // get all the digits
169 | std::string Extractor::get_digits(const std::string &value)
170 | {
171 | std::string digits;
172 | for (auto it = value.cbegin(); it != value.cend(); ++it)
173 | {
174 | if (std::isdigit(*it))
175 | digits += *it;
176 | else
177 | return digits;
178 | }
179 | return digits;
180 | }
181 |
182 | void Extractor::way(const osmium::Way &way)
183 | {
184 |
185 | // Check if the way contains tags we are interested in
186 | const bool usable = FilterWay(way);
187 |
188 | if (usable && way.nodes().size() > 1)
189 | {
190 | BOOST_ASSERT(db.key_value_pairs.size() < std::numeric_limits::max());
191 | const auto tagstart = static_cast(db.key_value_pairs.size());
192 | // Create a map of the tags for this way, add the strings to the stringbuffer
193 | // and then add the tag map to the way map.
194 | for (auto &tag : way.tags())
195 | {
196 | // use this way if we find a tag that we're interested in
197 | if (tags_filter(tag))
198 | {
199 | if (std::string(tag.key()) == "maxspeed")
200 | {
201 | std::string digits = get_digits(std::string(tag.value()));
202 | if (!digits.empty())
203 | {
204 | std::string value = std::string(tag.value());
205 | if (value.find("mph") != std::string::npos)
206 | {
207 | uint32_t speed = stoi(digits);
208 | std::uint32_t s = std::round(speed * kKmPerMile);
209 | digits = std::to_string(s);
210 | }
211 | const auto key_pos = db.addstring(tag.key());
212 | const auto val_pos = db.addstring(digits.c_str());
213 | db.key_value_pairs.emplace_back(key_pos, val_pos);
214 | }
215 | }
216 | else
217 | {
218 | const auto key_pos = db.addstring(tag.key());
219 | const auto val_pos = db.addstring(tag.value());
220 | db.key_value_pairs.emplace_back(key_pos, val_pos);
221 | }
222 | }
223 | }
224 |
225 | BOOST_ASSERT(db.key_value_pairs.size() < std::numeric_limits::max());
226 | const auto tagend = static_cast(db.key_value_pairs.size());
227 | db.way_tag_ranges.emplace_back(tagstart, tagend);
228 |
229 | BOOST_ASSERT(db.way_tag_ranges.size() < std::numeric_limits::max());
230 | const auto way_id =
231 | static_cast(db.way_tag_ranges.empty() ? 0 : (db.way_tag_ranges.size() - 1));
232 | db.internal_to_external_way_id_map.push_back(way.id());
233 |
234 | // This iterates over each pair of nodes.
235 | // Given the nodes 1,2,3,4,5,6
236 | // This loop will be called with (1,2), (2,3), (3,4), (4,5), (5, 6)
237 | for (auto n = way.nodes().cbegin(); n != way.nodes().cend() - 1; n++)
238 | {
239 | const auto external_a = n;
240 | const auto external_b = n + 1;
241 | const auto external_a_ref = external_a->ref();
242 | const auto external_b_ref = external_b->ref();
243 | try
244 | {
245 |
246 | internal_nodeid_t internal_a_id;
247 | internal_nodeid_t internal_b_id;
248 |
249 | const auto tmp_a = db.external_internal_map.find(external_a_ref);
250 |
251 | if (tmp_a == db.external_internal_map.end())
252 | {
253 | internal_a_id = db.external_internal_map.size();
254 | if (db.createRTree)
255 | {
256 | point_t a{external_a->location().lon(), external_a->location().lat()};
257 | BOOST_ASSERT(db.used_nodes_list.size() == db.external_internal_map.size());
258 | db.used_nodes_list.emplace_back(a, internal_a_id);
259 | }
260 | db.external_internal_map.emplace(external_a_ref, internal_a_id);
261 | }
262 | else
263 | {
264 | internal_a_id = tmp_a->second;
265 | }
266 |
267 | const auto tmp_b = db.external_internal_map.find(external_b_ref);
268 | if (tmp_b == db.external_internal_map.end())
269 | {
270 | internal_b_id = db.external_internal_map.size();
271 | if (db.createRTree)
272 | {
273 | point_t b{external_b->location().lon(), external_b->location().lat()};
274 | BOOST_ASSERT(db.used_nodes_list.size() == db.external_internal_map.size());
275 | db.used_nodes_list.emplace_back(b, internal_b_id);
276 | }
277 | db.external_internal_map.emplace(external_b_ref, internal_b_id);
278 | }
279 | else
280 | {
281 | internal_b_id = tmp_b->second;
282 | }
283 |
284 | if (internal_a_id < internal_b_id)
285 | {
286 | // true here indicates storage is forward
287 | db.pair_way_map.emplace(std::make_pair(internal_a_id, internal_b_id),
288 | way_storage_t{way_id, true});
289 | }
290 | else
291 | {
292 | // false here indicates storage is backward
293 | db.pair_way_map.emplace(std::make_pair(internal_b_id, internal_a_id),
294 | way_storage_t{way_id, false});
295 | }
296 | }
297 | catch (const osmium::invalid_location &e)
298 | {
299 | // std::cerr << "WARNING: Invalid location for one of nodes " <<
300 | // external_a_ref << " or " << external_b_ref << "\n";
301 | }
302 | }
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/src/extractor.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | // Basic libosmium includes
3 | #include
4 | #include
5 | #include
6 | // We take any input that libosmium supports (XML, PBF, osm.bz2, etc)
7 | #include
8 |
9 | #include "database.hpp"
10 | #include "types.hpp"
11 |
12 | #include
13 |
14 | /**
15 | * The handler for libosmium. This class basically contains one callback that's called by
16 | * libosmium as it parses our OSM file.
17 | *
18 | * Basically, it's responsible for populating the Database object with nodes, ways
19 | * and tags.
20 | */
21 | struct Extractor final : osmium::handler::Handler
22 | {
23 | /**
24 | * Constructs an extractor. It requres a Database object it
25 | * can dump data into.
26 | *
27 | * @param d the Database object where everything will end up
28 | */
29 | Extractor(const std::vector &osm_files, Database &d);
30 | Extractor(const std::vector &osm_files,
31 | Database &d,
32 | const std::string &tagfilename);
33 |
34 | /**
35 | * Constructs an extractor from in-memory OSM XML data.
36 | * It requres a Database object it can dump data into.
37 | *
38 | * @param buffer a character buffer holding the osmium parseable data
39 | * @param buffersize the buffer size (duh)
40 | * @param format the format of the buffer for libosmium. One of
41 | * pbf, xml, opl, json, o5m, osm, osh or osc
42 | */
43 | Extractor(const char *buffer, std::size_t buffersize, const std::string &format, Database &d);
44 |
45 | /**
46 | * Collect all the digits in the string until we hit a non-numeric value.
47 | *
48 | * @param value the value that needs to be processed.
49 | * @return a string containing only digits.
50 | */
51 | std::string get_digits(const std::string &value);
52 |
53 | /**
54 | * Osmium way handler - called once for each way.
55 | *
56 | * @param way the current way being processed
57 | */
58 | void way(const osmium::Way &way);
59 |
60 | private:
61 | // Internal reference to the db we're going to dump everything
62 | // into
63 | Database &db;
64 | /**
65 | * shared constructor set up operations
66 | */
67 | void ParseFile(const osmium::io::File &osmfile);
68 | void SetupDatabase();
69 | /**
70 | * Optional tag filtering on OSM data extraction
71 | */
72 | osmium::TagsFilter tags_filter{false};
73 | bool FilterWay(const osmium::Way &way);
74 | void ParseTags(std::ifstream &tagfile);
75 | };
76 |
--------------------------------------------------------------------------------
/src/main_bindings.cpp:
--------------------------------------------------------------------------------
1 | #include "nodejs_bindings.hpp"
2 | #include "segment_bindings.hpp"
3 | #include "way_bindings.hpp"
4 |
5 | NAN_MODULE_INIT(Init)
6 | {
7 | Annotator::Init(target);
8 | SegmentSpeedLookup::Init(target);
9 | WaySpeedLookup::Init(target);
10 | }
11 |
12 | NODE_MODULE(route_annotator, Init)
13 |
--------------------------------------------------------------------------------
/src/nodejs_bindings.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include "extractor.hpp"
8 | #include "types.hpp"
9 |
10 | #include "nodejs_bindings.hpp"
11 |
12 | #include
13 |
14 | NAN_MODULE_INIT(Annotator::Init)
15 | {
16 | const auto whoami = Nan::New("Annotator").ToLocalChecked();
17 |
18 | auto fnTp = Nan::New(New);
19 | fnTp->SetClassName(whoami);
20 | fnTp->InstanceTemplate()->SetInternalFieldCount(1);
21 |
22 | SetPrototypeMethod(fnTp, "loadOSMExtract", loadOSMExtract);
23 | SetPrototypeMethod(fnTp, "annotateRouteFromNodeIds", annotateRouteFromNodeIds);
24 | SetPrototypeMethod(fnTp, "annotateRouteFromLonLats", annotateRouteFromLonLats);
25 | SetPrototypeMethod(fnTp, "getAllTagsForWayId", getAllTagsForWayId);
26 |
27 | const auto fn = Nan::GetFunction(fnTp).ToLocalChecked();
28 |
29 | constructor().Reset(fn);
30 |
31 | Nan::Set(target, whoami, fn);
32 | }
33 |
34 | NAN_METHOD(Annotator::New)
35 | {
36 | bool coordinates = false;
37 | if (info.Length() != 0)
38 | {
39 | if (!info[0]->IsObject())
40 | return Nan::ThrowTypeError("Options should be an object");
41 | const auto options = info[0].As();
42 | auto tryParseCoordinates = Nan::Get(options, Nan::New("coordinates").ToLocalChecked());
43 | if (!tryParseCoordinates.IsEmpty())
44 | {
45 | auto parsedCoords = tryParseCoordinates.ToLocalChecked();
46 | if (parsedCoords->IsBoolean())
47 | {
48 | coordinates = Nan::To(parsedCoords).FromJust();
49 | }
50 | else
51 | {
52 | return Nan::ThrowTypeError("Coordinates value should be a boolean");
53 | }
54 | }
55 | else
56 | {
57 | // options object isn't empty, and it doesn't have a 'coordinates' option
58 | // we don't accept any other options right now
59 | return Nan::ThrowError("Unrecognized annotator options");
60 | }
61 | }
62 |
63 | if (info.IsConstructCall())
64 | {
65 | auto *const self = new Annotator;
66 | self->createRTree = coordinates;
67 | self->Wrap(info.This());
68 | info.GetReturnValue().Set(info.This());
69 | }
70 | else
71 | {
72 | return Nan::ThrowTypeError(
73 | "Cannot call constructor as function, you need to use 'new' keyword");
74 | }
75 | }
76 |
77 | NAN_METHOD(Annotator::loadOSMExtract)
78 | {
79 | // In case we already loaded a dataset, this function will transactionally swap in a new one
80 | auto *const self = Nan::ObjectWrap::Unwrap(info.Holder());
81 |
82 | // Validate file paths and callback
83 | if (info.Length() < 2)
84 | {
85 | return Nan::ThrowTypeError("Expected at least two arguments");
86 | }
87 |
88 | std::vector osm_paths;
89 | std::string tag_path;
90 | // validate OSM file paths - either a string or array of strings
91 | if (!info[0]->IsString() && !info[0]->IsArray())
92 | {
93 | return Nan::ThrowTypeError("OSM files expected as string (or array of strings)");
94 | }
95 | // Validate callback
96 | if (info.Length() == 2)
97 | {
98 | if (!info[1]->IsFunction())
99 | {
100 | return Nan::ThrowTypeError("Missing callback function");
101 | }
102 | }
103 | // Validate tag file and callback
104 | else if (info.Length() == 3)
105 | {
106 | if (!info[1]->IsString() || !info[2]->IsFunction())
107 | {
108 | return Nan::ThrowTypeError(
109 | "Expecting a string (or array of strings), a string, and a callback");
110 | }
111 | // convert tag file path into string
112 | const v8::String::Utf8Value tag_utf8String(v8::Isolate::GetCurrent(), info[1]);
113 | if (!(*tag_utf8String))
114 | return Nan::ThrowError("Unable to convert to Utf8String");
115 |
116 | tag_path.assign(*tag_utf8String, tag_utf8String.length());
117 | }
118 | // Parse osm files into vector
119 | if (info[0]->IsString())
120 | {
121 | const v8::String::Utf8Value osm_utf8String(v8::Isolate::GetCurrent(), info[0]);
122 | osm_paths.emplace_back(*osm_utf8String, osm_utf8String.length());
123 | }
124 | else if (info[0]->IsArray())
125 | {
126 | const auto file_array = v8::Local::Cast(info[0]);
127 | if (file_array->Length() < 1)
128 | return Nan::ThrowTypeError("Input OSM files array can't be empty");
129 | for (std::uint32_t idx = 0; idx < file_array->Length(); ++idx)
130 | {
131 | if (!Nan::Get(file_array, idx).ToLocalChecked()->IsString())
132 | {
133 | // TODO include the idx number in this error message
134 | return Nan::ThrowError("Unable to convert file path to Utf8String");
135 | }
136 |
137 | const v8::String::Utf8Value file{v8::Isolate::GetCurrent(),
138 | Nan::Get(file_array, idx).ToLocalChecked()};
139 | if (!(*file))
140 | return Nan::ThrowError("Unable to convert file path to Utf8String");
141 | osm_paths.emplace_back(*file, file.length());
142 | }
143 | }
144 | // gotta have some files
145 | if (osm_paths.empty())
146 | return Nan::ThrowError("No file paths found");
147 |
148 | struct OSMLoader final : Nan::AsyncWorker
149 | {
150 | explicit OSMLoader(Annotator &self_,
151 | Nan::Callback *callback,
152 | std::vector osm_paths_,
153 | std::string tag_path_)
154 | : Nan::AsyncWorker(callback, "annotator:osm.load"), self{self_},
155 | osm_paths{std::move(osm_paths_)}, tag_path{tag_path_}
156 | {
157 | }
158 |
159 | void Execute() override
160 | {
161 | try
162 | {
163 | // Note: provide strong exception safety guarantee (rollback)
164 | auto database = std::make_unique(self.createRTree);
165 | Extractor extractor{osm_paths, *database, tag_path};
166 | auto annotator = std::make_unique(*database);
167 |
168 | // Transactionally swap (noexcept)
169 | swap(self.database, database);
170 | swap(self.annotator, annotator);
171 | }
172 | catch (const std::exception &e)
173 | {
174 | return SetErrorMessage(e.what());
175 | }
176 | }
177 |
178 | void HandleOKCallback() override
179 | {
180 | Nan::HandleScope scope;
181 | const constexpr auto argc = 1u;
182 | v8::Local argv[argc] = {Nan::Null()};
183 | callback->Call(argc, argv, async_resource);
184 | }
185 |
186 | Annotator &self;
187 | std::vector osm_paths;
188 | std::string tag_path;
189 | };
190 |
191 | auto *callback = info.Length() == 3 ? new Nan::Callback{info[2].As()}
192 | : new Nan::Callback{info[1].As()};
193 | Nan::AsyncQueueWorker(
194 | new OSMLoader{*self, callback, std::move(osm_paths), std::move(tag_path)});
195 | }
196 |
197 | NAN_METHOD(Annotator::annotateRouteFromNodeIds)
198 | {
199 | auto *const self = Nan::ObjectWrap::Unwrap(info.Holder());
200 |
201 | if (!self->database || !self->annotator)
202 | return Nan::ThrowError("No OSM data loaded");
203 |
204 | if (info.Length() != 2 || !info[0]->IsArray() || !info[1]->IsFunction())
205 | return Nan::ThrowTypeError("Array of node ids and callback expected");
206 |
207 | const auto jsNodeIds = info[0].As();
208 |
209 | // Guard against empty or one nodeId for which no wayId can be assigned
210 | if (jsNodeIds->Length() < 2)
211 | return Nan::ThrowTypeError("At least two node ids required");
212 |
213 | std::vector externalIds(jsNodeIds->Length());
214 |
215 | for (std::size_t i{0}; i < jsNodeIds->Length(); ++i)
216 | {
217 | const auto nodeIdValue = Nan::Get(jsNodeIds, i).ToLocalChecked();
218 |
219 | if (!nodeIdValue->IsNumber())
220 | return Nan::ThrowTypeError("Array of number type expected");
221 |
222 | // Javascript has no UInt64 type, we have to go through floating point types.
223 | // Only safe until Number.MAX_SAFE_INTEGER, which is 2^53-1, guard with checked cast.
224 | const auto nodeIdDouble = Nan::To(nodeIdValue).FromJust();
225 |
226 | try
227 | {
228 | const auto nodeId = boost::numeric_cast(nodeIdDouble);
229 | externalIds[i] = nodeId;
230 | }
231 | catch (const boost::numeric::bad_numeric_cast &e)
232 | {
233 | return Nan::ThrowError(e.what());
234 | };
235 | }
236 |
237 | struct WayIdsFromNodeIdsLoader final : Nan::AsyncWorker
238 | {
239 | explicit WayIdsFromNodeIdsLoader(Annotator &self_,
240 | Nan::Callback *callback,
241 | std::vector externalIds_)
242 | : Nan::AsyncWorker(callback, "annotator:osm.annotatefromnodeids"), self{self_},
243 | externalIds{std::move(externalIds_)}
244 | {
245 | }
246 |
247 | void Execute() override
248 | {
249 | const auto internalIds = self.annotator->external_to_internal(externalIds);
250 | wayIds = self.annotator->annotateRoute(internalIds);
251 | }
252 |
253 | void HandleOKCallback() override
254 | {
255 | Nan::HandleScope scope;
256 |
257 | auto annotated = Nan::New(wayIds.size());
258 |
259 | for (std::size_t i{0}; i < wayIds.size(); ++i)
260 | {
261 | const auto wayId = wayIds[i];
262 |
263 | if (wayId == INVALID_WAYID)
264 | (void)Nan::Set(annotated, i, Nan::Null());
265 | else
266 | (void)Nan::Set(annotated, i, Nan::New(wayIds[i]));
267 | }
268 |
269 | const constexpr auto argc = 2u;
270 | v8::Local argv[argc] = {Nan::Null(), annotated};
271 |
272 | callback->Call(argc, argv, async_resource);
273 | }
274 |
275 | Annotator &self;
276 | std::vector externalIds;
277 | annotated_route_t wayIds;
278 | };
279 |
280 | auto *callback = new Nan::Callback{info[1].As()};
281 | Nan::AsyncQueueWorker(new WayIdsFromNodeIdsLoader{*self, callback, std::move(externalIds)});
282 | }
283 |
284 | NAN_METHOD(Annotator::annotateRouteFromLonLats)
285 | {
286 | auto *const self = Nan::ObjectWrap::Unwrap(info.Holder());
287 |
288 | if (!self->database || !self->annotator)
289 | return Nan::ThrowError("No OSM data loaded");
290 |
291 | if (info.Length() != 2 || !info[0]->IsArray() || !info[1]->IsFunction())
292 | return Nan::ThrowTypeError("Array of [lon, lat] arrays and callback expected");
293 |
294 | auto jsLonLats = info[0].As();
295 |
296 | // Guard against empty or one coordinate for which no wayId can be assigned
297 | if (jsLonLats->Length() < 2)
298 | return Nan::ThrowTypeError("At least 2 coordinates must be supplied");
299 |
300 | std::vector coordinates(jsLonLats->Length());
301 |
302 | for (std::size_t i{0}; i < jsLonLats->Length(); ++i)
303 | {
304 | auto lonLatValue = Nan::Get(jsLonLats, i).ToLocalChecked();
305 |
306 | if (!lonLatValue->IsArray())
307 | return Nan::ThrowTypeError("Array of [lon, lat] expected");
308 |
309 | auto lonLatArray = lonLatValue.As();
310 |
311 | if (lonLatArray->Length() != 2)
312 | return Nan::ThrowTypeError("Array of [lon, lat] expected");
313 |
314 | const auto lonValue = Nan::Get(lonLatArray, 0).ToLocalChecked();
315 | const auto latValue = Nan::Get(lonLatArray, 1).ToLocalChecked();
316 |
317 | if (!lonValue->IsNumber() || !latValue->IsNumber())
318 | return Nan::ThrowTypeError("Array of two numbers [lon, lat] expected");
319 |
320 | const auto lon = Nan::To(lonValue).FromJust();
321 | const auto lat = Nan::To(latValue).FromJust();
322 |
323 | coordinates[i] = {lon, lat};
324 | }
325 |
326 | struct WayIdsFromLonLatsLoader final : Nan::AsyncWorker
327 | {
328 | explicit WayIdsFromLonLatsLoader(Annotator &self_,
329 | Nan::Callback *callback,
330 | std::vector coordinates_)
331 | : Nan::AsyncWorker(callback, "annotator:osm.annotatefromlonlats"), self{self_},
332 | coordinates{std::move(coordinates_)}
333 | {
334 | }
335 |
336 | void Execute() override
337 | {
338 | try
339 | {
340 | const auto internalIds = self.annotator->coordinates_to_internal(coordinates);
341 | wayIds = self.annotator->annotateRoute(internalIds);
342 | }
343 | catch (const RouteAnnotator::RtreeError &e)
344 | {
345 | return SetErrorMessage("Annotator not created with coordinates support");
346 | }
347 | catch (const std::exception &e)
348 | {
349 | return SetErrorMessage(e.what());
350 | }
351 | }
352 |
353 | void HandleOKCallback() override
354 | {
355 | Nan::HandleScope scope;
356 |
357 | auto annotated = Nan::New(wayIds.size());
358 |
359 | for (std::size_t i{0}; i < wayIds.size(); ++i)
360 | {
361 | const auto wayId = wayIds[i];
362 |
363 | if (wayId == INVALID_WAYID)
364 | (void)Nan::Set(annotated, i, Nan::Null());
365 | else
366 | (void)Nan::Set(annotated, i, Nan::New(wayIds[i]));
367 | }
368 |
369 | const constexpr auto argc = 2u;
370 | v8::Local argv[argc] = {Nan::Null(), annotated};
371 |
372 | callback->Call(argc, argv, async_resource);
373 | }
374 |
375 | Annotator &self;
376 | std::vector coordinates;
377 | annotated_route_t wayIds;
378 | };
379 |
380 | auto *callback = new Nan::Callback{info[1].As()};
381 | Nan::AsyncQueueWorker(new WayIdsFromLonLatsLoader{*self, callback, std::move(coordinates)});
382 | }
383 |
384 | NAN_METHOD(Annotator::getAllTagsForWayId)
385 | {
386 | auto *const self = Nan::ObjectWrap::Unwrap(info.Holder());
387 |
388 | if (!self->database || !self->annotator)
389 | return Nan::ThrowError("No OSM data loaded");
390 |
391 | if (info.Length() != 2 || !info[0]->IsNumber() || !info[1]->IsFunction())
392 | return Nan::ThrowTypeError("A numeric way ID and a callback expected");
393 |
394 | const auto wayId = Nan::To(info[0]).FromJust();
395 |
396 | struct TagsForWayIdLoader final : Nan::AsyncWorker
397 | {
398 | explicit TagsForWayIdLoader(Annotator &self_, Nan::Callback *callback, wayid_t wayId_)
399 | : Nan::AsyncWorker(callback, "annotator:osm.gettagsforids"), self{self_},
400 | wayId{std::move(wayId_)}
401 | {
402 | }
403 |
404 | void Execute() override { range = self.annotator->get_tag_range(wayId); }
405 |
406 | void HandleOKCallback() override
407 | {
408 | Nan::HandleScope scope;
409 |
410 | auto tags = Nan::New();
411 |
412 | for (auto i = range.first; i < range.second; ++i)
413 | {
414 | const auto key = self.annotator->get_tag_key(i);
415 | const auto value = self.annotator->get_tag_value(i);
416 |
417 | Nan::Set(tags, Nan::New(std::cref(key)).ToLocalChecked(),
418 | Nan::New(std::cref(value)).ToLocalChecked());
419 | }
420 |
421 | Nan::Set(tags, Nan::New("_way_id").ToLocalChecked(),
422 | Nan::New(std::to_string(self.annotator->get_external_way_id(wayId)))
423 | .ToLocalChecked());
424 |
425 | const constexpr auto argc = 2u;
426 | v8::Local argv[argc] = {Nan::Null(), tags};
427 |
428 | callback->Call(argc, argv, async_resource);
429 | }
430 |
431 | Annotator &self;
432 | wayid_t wayId;
433 | tagrange_t range;
434 | };
435 |
436 | auto *callback = new Nan::Callback{info[1].As()};
437 | Nan::AsyncQueueWorker(new TagsForWayIdLoader{*self, callback, std::move(wayId)});
438 | }
439 |
440 | Nan::Persistent &Annotator::constructor()
441 | {
442 | static Nan::Persistent init;
443 | return init;
444 | }
445 |
--------------------------------------------------------------------------------
/src/nodejs_bindings.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 |
8 | #include "annotator.hpp"
9 | #include "database.hpp"
10 |
11 | class Annotator final : public Nan::ObjectWrap
12 | {
13 | public:
14 | /* Set up wrapper Javascript object */
15 | static NAN_MODULE_INIT(Init);
16 |
17 | private:
18 | /* Constructor call for Javascript object, as in "new Annotator(..)" or "Annotator(..)" */
19 | static NAN_METHOD(New);
20 |
21 | /* Member function for Javascript object to parse and load the OSM extract */
22 | static NAN_METHOD(loadOSMExtract);
23 |
24 | /* Member function for Javascript object: [nodeId, nodeId, ..] -> [wayId, wayId, ..] */
25 | static NAN_METHOD(annotateRouteFromNodeIds);
26 |
27 | /* Member function for Javascript object: [[lon, lat], [lon, lat]] -> [wayId, wayId, ..] */
28 | static NAN_METHOD(annotateRouteFromLonLats);
29 |
30 | /* Member function for Javascript object: wayId -> [[key, value], [key, value]] */
31 | static NAN_METHOD(getAllTagsForWayId);
32 |
33 | /* Thread-safe singleton constructor */
34 | static Nan::Persistent &constructor();
35 |
36 | /* Wrapping Annotator; both database and annotator do not provide default ctor: wrap in ptr */
37 | bool createRTree = false;
38 | std::unique_ptr database;
39 | std::unique_ptr annotator;
40 | };
41 |
--------------------------------------------------------------------------------
/src/segment_bindings.cpp:
--------------------------------------------------------------------------------
1 | #include "segment_bindings.hpp"
2 | #include "types.hpp"
3 | #include
4 | #include
5 |
6 | NAN_MODULE_INIT(SegmentSpeedLookup::Init)
7 | {
8 | const auto whoami = Nan::New("SegmentSpeedLookup").ToLocalChecked();
9 |
10 | auto fnTp = Nan::New(New);
11 | fnTp->SetClassName(whoami);
12 | fnTp->InstanceTemplate()->SetInternalFieldCount(1);
13 |
14 | SetPrototypeMethod(fnTp, "loadCSV", loadCSV);
15 | SetPrototypeMethod(fnTp, "getRouteSpeeds", getRouteSpeeds);
16 |
17 | const auto fn = Nan::GetFunction(fnTp).ToLocalChecked();
18 | constructor().Reset(fn);
19 |
20 | Nan::Set(target, whoami, fn);
21 | }
22 |
23 | NAN_METHOD(SegmentSpeedLookup::New)
24 | {
25 | if (info.Length() != 0)
26 | return Nan::ThrowTypeError("No types expected");
27 |
28 | if (info.IsConstructCall())
29 | {
30 | auto *const self = new SegmentSpeedLookup;
31 | self->Wrap(info.This());
32 | info.GetReturnValue().Set(info.This());
33 | }
34 | else
35 | {
36 | return Nan::ThrowTypeError(
37 | "Cannot call constructor as function, you need to use 'new' keyword");
38 | }
39 | }
40 |
41 | /**
42 | * Loads a csv file asynchronously
43 | * @function loadCSV
44 | * @param {string} path the path to the CSV file to load
45 | * @param {function} callback function to call when the file is done loading
46 | */
47 | NAN_METHOD(SegmentSpeedLookup::loadCSV)
48 | {
49 | // In case we already loaded a dataset, this function will transactionally swap in a new one
50 | if (info.Length() != 2 || (!info[0]->IsString() && !info[0]->IsArray()) ||
51 | !info[1]->IsFunction())
52 | return Nan::ThrowTypeError("String (or array of strings) and callback expected");
53 |
54 | std::vector paths;
55 |
56 | if (info[0]->IsString())
57 | {
58 | const Nan::Utf8String utf8String(info[0]);
59 |
60 | if (!(*utf8String))
61 | return Nan::ThrowError("Unable to convert to Utf8String");
62 |
63 | paths.push_back({*utf8String, *utf8String + utf8String.length()});
64 | }
65 | else if (info[0]->IsArray())
66 | {
67 | auto arr = v8::Local::Cast(info[0]);
68 | if (arr->Length() < 1)
69 | {
70 | return Nan::ThrowTypeError("Array must contain at least one filename");
71 | }
72 | for (std::uint32_t idx = 0; idx < arr->Length(); ++idx)
73 | {
74 | const Nan::Utf8String utf8String(Nan::Get(arr, idx).ToLocalChecked());
75 |
76 | if (!(*utf8String))
77 | return Nan::ThrowError("Unable to convert to Utf8String");
78 |
79 | paths.push_back({*utf8String, *utf8String + utf8String.length()});
80 | }
81 | }
82 | else
83 | {
84 | return Nan::ThrowTypeError("First parameter should be a string or an array");
85 | }
86 |
87 | struct CSVLoader final : Nan::AsyncWorker
88 | {
89 | explicit CSVLoader(v8::Local self_,
90 | Nan::Callback *callback,
91 | std::vector paths_)
92 | : Nan::AsyncWorker(callback, "annotator:speed.load"), paths{std::move(paths_)}
93 | {
94 | SaveToPersistent("self", self_);
95 | }
96 |
97 | void Execute() override
98 | {
99 | try
100 | {
101 | map = std::make_shared();
102 | for (const auto &path : paths)
103 | {
104 | map->loadCSV(path);
105 | }
106 | }
107 | catch (const std::exception &e)
108 | {
109 | return SetErrorMessage(e.what());
110 | }
111 | }
112 |
113 | void HandleOKCallback() override
114 | {
115 | Nan::HandleScope scope;
116 | auto self = GetFromPersistent("self")
117 | ->ToObject(v8::Isolate::GetCurrent()->GetCurrentContext())
118 | .ToLocalChecked();
119 | auto *unwrapped = Nan::ObjectWrap::Unwrap(self);
120 | swap(unwrapped->datamap, map);
121 | const constexpr auto argc = 1u;
122 | v8::Local argv[argc] = {Nan::Null()};
123 | callback->Call(argc, argv, async_resource);
124 | }
125 |
126 | std::vector paths;
127 | std::shared_ptr map;
128 | };
129 |
130 | auto *callback = new Nan::Callback{info[1].As()};
131 | Nan::AsyncQueueWorker(new CSVLoader{info.Holder(), callback, std::move(paths)});
132 | }
133 |
134 | /**
135 | * Fetches the values in the hashtable for pairs of nodes
136 | * @function getRouteSpeeds
137 | * @param {array} nodes an array of node IDs to look up in pairs
138 | * @param {function} callback receives the results of the search as an array
139 | * @example
140 | * container.getRouteSpeeds([23,43,12],(err,result) => {
141 | * console.log(result);
142 | * });
143 | */
144 | NAN_METHOD(SegmentSpeedLookup::getRouteSpeeds)
145 | {
146 | auto *const self = Nan::ObjectWrap::Unwrap(info.Holder());
147 |
148 | if (info.Length() != 2 || !info[0]->IsArray() || !info[1]->IsFunction())
149 | return Nan::ThrowTypeError("Two arguments expected: nodeIds (Array), Callback");
150 |
151 | auto callback = info[1].As();
152 | const auto jsNodeIds = info[0].As();
153 | // Guard against empty or one nodeId for which no wayId can be assigned
154 | if (jsNodeIds->Length() < 2)
155 | return Nan::ThrowTypeError(
156 | "getRouteSpeeds expects 'nodeIds' (Array(Number)) of at least length 2");
157 |
158 | std::vector nodes_to_query(jsNodeIds->Length());
159 |
160 | for (uint32_t i = 0; i < jsNodeIds->Length(); ++i)
161 | {
162 | v8::Local jsNodeId = Nan::Get(jsNodeIds, i).ToLocalChecked();
163 | if (!jsNodeId->IsNumber())
164 | return Nan::ThrowTypeError("NodeIds must be an array of numbers");
165 | auto signedNodeId = Nan::To(jsNodeId).FromJust();
166 | if (signedNodeId < 0)
167 | return Nan::ThrowTypeError(
168 | "getRouteSpeeds expects 'nodeId' within (Array(Number))to be non-negative");
169 |
170 | external_nodeid_t nodeId = static_cast(signedNodeId);
171 | nodes_to_query[i] = nodeId;
172 | }
173 |
174 | struct Worker final : Nan::AsyncWorker
175 | {
176 | using Base = Nan::AsyncWorker;
177 |
178 | Worker(std::shared_ptr datamap_,
179 | Nan::Callback *callback,
180 | std::vector nodeIds)
181 | : Base(callback, "annotator:speed.annotatefromnodeids"), datamap{datamap_},
182 | nodeIds(std::move(nodeIds))
183 | {
184 | }
185 |
186 | void Execute() override
187 | {
188 | if (datamap)
189 | {
190 | result_annotations = datamap->getValues(nodeIds);
191 | }
192 | else
193 | {
194 | result_annotations.resize(nodeIds.size() - 1);
195 | std::fill(result_annotations.begin(), result_annotations.end(), INVALID_SPEED);
196 | }
197 | }
198 |
199 | void HandleOKCallback() override
200 | {
201 | Nan::HandleScope scope;
202 |
203 | auto jsAnnotations = Nan::New(result_annotations.size());
204 |
205 | for (std::size_t i = 0; i < result_annotations.size(); ++i)
206 | {
207 | auto jsAnnotation = Nan::New(result_annotations[i]);
208 | (void)Nan::Set(jsAnnotations, i, jsAnnotation);
209 | }
210 |
211 | const auto argc = 2u;
212 | v8::Local argv[argc] = {Nan::Null(), jsAnnotations};
213 |
214 | callback->Call(argc, argv, async_resource);
215 | }
216 |
217 | std::shared_ptr datamap;
218 | std::vector nodeIds;
219 | std::vector result_annotations;
220 | };
221 |
222 | Nan::AsyncQueueWorker(
223 | new Worker{self->datamap, new Nan::Callback{callback}, std::move(nodes_to_query)});
224 | }
225 |
226 | Nan::Persistent &SegmentSpeedLookup::constructor()
227 | {
228 | static Nan::Persistent init;
229 | return init;
230 | }
231 |
--------------------------------------------------------------------------------
/src/segment_bindings.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "segment_speed_map.hpp"
6 |
7 | class SegmentSpeedLookup : public Nan::ObjectWrap
8 | {
9 | public:
10 | static NAN_MODULE_INIT(Init);
11 |
12 | private:
13 | static NAN_METHOD(New);
14 |
15 | static NAN_METHOD(loadCSV);
16 |
17 | static NAN_METHOD(getRouteSpeeds);
18 |
19 | static Nan::Persistent &constructor(); // CPP Land
20 |
21 | std::shared_ptr datamap; // if you want async call
22 | };
23 |
--------------------------------------------------------------------------------
/src/segment_speed_map.cpp:
--------------------------------------------------------------------------------
1 | #include "segment_speed_map.hpp"
2 | #include
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 |
12 | using spp::sparse_hash_map;
13 |
14 | SegmentSpeedMap::SegmentSpeedMap(){};
15 |
16 | SegmentSpeedMap::SegmentSpeedMap(const std::string &input_filename) { loadCSV(input_filename); }
17 |
18 | SegmentSpeedMap::SegmentSpeedMap(const std::vector &input_filenames)
19 | {
20 | for (const auto &input_filename : input_filenames)
21 | {
22 | loadCSV(input_filename);
23 | }
24 | }
25 |
26 | void SegmentSpeedMap::loadCSV(const std::string &input_filename)
27 | {
28 | namespace ph = boost::phoenix;
29 | namespace qi = boost::spirit::qi;
30 |
31 | boost::iostreams::mapped_file_source mmap(input_filename);
32 | auto first = mmap.begin(), last = mmap.end();
33 | qi::parse(first, last,
34 | (
35 | // parse data that does not have mph or kph data
36 | ((qi::ulong_long >> ',' >> qi::ulong_long >> ',' >>
37 | qi::uint_)[ph::bind(&SegmentSpeedMap::add, this, qi::_1, qi::_2, qi::_3)]) |
38 | // if the above fails, try to parse data checking for mph or kph.
39 | ((qi::ulong_long >> ',' >> qi::ulong_long >> ',' >>
40 | ("mph" >> qi::attr(true) | "kph" >> qi::attr(false) | "" >> qi::attr(false)) >>
41 | ',' >> qi::uint_)[ph::bind(&SegmentSpeedMap::add_with_unit, this, qi::_1,
42 | qi::_2, qi::_4, qi::_3)])) %
43 | qi::eol >>
44 | *qi::eol);
45 |
46 | if (first != last)
47 | {
48 | auto bol = first - 1;
49 | while (bol > mmap.begin() && *bol != '\n')
50 | --bol;
51 | auto line_number = std::count(mmap.begin(), first, '\n') + 1;
52 | throw std::runtime_error("CSV parsing failed at " + input_filename + ':' +
53 | std::to_string(line_number) + ": " +
54 | std::string(bol + 1, std::find(first, last, '\n')));
55 | }
56 | }
57 |
58 | void SegmentSpeedMap::add_with_unit(const external_nodeid_t &from,
59 | const external_nodeid_t &to,
60 | const uint32_t &speed,
61 | const bool &mph)
62 | {
63 | if (mph)
64 | {
65 | std::uint32_t s = std::round(speed * kKmPerMile);
66 |
67 | if (s > INVALID_SPEED - 1)
68 | {
69 | std::cout << "CSV parsing failed. From Node: " << std::to_string(from)
70 | << " To Node: " << std::to_string(to) << " Speed: " << std::to_string(s)
71 | << std::endl;
72 | }
73 | else
74 | annotations[Segment(from, to)] = s;
75 | }
76 | else
77 | {
78 |
79 | if (speed > INVALID_SPEED - 1)
80 | {
81 | std::cout << "CSV parsing failed. From Node: " << std::to_string(from)
82 | << " To Node: " << std::to_string(to) << " Speed: " << std::to_string(speed)
83 | << std::endl;
84 | }
85 | else
86 | annotations[Segment(from, to)] = speed;
87 | }
88 | }
89 |
90 | void SegmentSpeedMap::add(const external_nodeid_t &from,
91 | const external_nodeid_t &to,
92 | const std::uint32_t &speed)
93 | {
94 | if (speed > INVALID_SPEED - 1)
95 | {
96 | std::cout << " CSV parsing failed. From Node: " << std::to_string(from)
97 | << " To Node: " << std::to_string(to) << " Speed: " << std::to_string(speed)
98 | << std::endl;
99 | return;
100 | }
101 |
102 | annotations[Segment(from, to)] = speed;
103 | }
104 |
105 | bool SegmentSpeedMap::hasKey(const external_nodeid_t &from, const external_nodeid_t &to) const
106 | {
107 | return annotations.count(Segment(from, to)) > 0;
108 | }
109 |
110 | segment_speed_t SegmentSpeedMap::getValue(const external_nodeid_t &from,
111 | const external_nodeid_t &to) const
112 | {
113 | // Save the result of find so that we don't need to repeat the lookup to get the value
114 | auto result = annotations.find(Segment(from, to));
115 | if (result == annotations.end())
116 | {
117 | throw std::runtime_error("Segment from NodeID " + std::to_string(from) + " to NodeId " +
118 | std::to_string(to) + " doesn't exist in the hashmap.");
119 | }
120 |
121 | // Use the already retrieved value as the result
122 | return result->second;
123 | }
124 |
125 | std::vector
126 | SegmentSpeedMap::getValues(const std::vector &route) const
127 | {
128 | std::vector speeds;
129 | if (route.size() < 2)
130 | {
131 | throw std::runtime_error(
132 | "NodeID Array should have more than 2 NodeIds for getValues method.");
133 | }
134 |
135 | speeds.resize(route.size() - 1);
136 | for (std::size_t segment_index = 0; segment_index < speeds.size(); ++segment_index)
137 | {
138 | auto from = route[segment_index];
139 | auto to = route[segment_index + 1];
140 | auto result = annotations.find(Segment(from, to));
141 | if (result == annotations.end())
142 | {
143 | speeds[segment_index] = INVALID_SPEED;
144 | }
145 | else
146 | {
147 | speeds[segment_index] = result->second;
148 | }
149 | }
150 | return speeds;
151 | }
152 |
--------------------------------------------------------------------------------
/src/segment_speed_map.hpp:
--------------------------------------------------------------------------------
1 | #ifndef SEGMENT_SPEED_MAP_H
2 | #define SEGMENT_SPEED_MAP_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "types.hpp"
10 |
11 | using spp::sparse_hash_map;
12 |
13 | /**
14 | * This type describes a segment on the map - two nodes
15 | */
16 | struct Segment
17 | {
18 | bool operator==(const Segment &o) const { return to == o.to && from == o.from; }
19 |
20 | Segment(const external_nodeid_t from, const external_nodeid_t to) : from(from), to(to) {}
21 |
22 | external_nodeid_t from;
23 | external_nodeid_t to;
24 | };
25 |
26 | namespace std
27 | {
28 | // inject specialization of std::hash for Segment into namespace std
29 | // ----------------------------------------------------------------
30 | template <> struct hash
31 | {
32 | std::size_t operator()(Segment const &p) const
33 | {
34 | // As of 2017, we know that OSM node IDs are about 2^32 big. In a 64bit
35 | // value, most of the top bits are zero.
36 | // A quick way to come up with a unique ID is to merge two IDs together,
37 | // shifting one into the upper empty bits of the other.
38 | // This produces few/no collisions, which is good for hashtable/map
39 | // performance.
40 | return (p.to << 31) + p.from;
41 | }
42 | };
43 | }
44 |
45 | class SegmentSpeedMap
46 | {
47 | public:
48 | /**
49 | * Do-nothing constructor
50 | */
51 | SegmentSpeedMap();
52 |
53 | /**
54 | * Loads from,to,speed data from a single file
55 | */
56 | SegmentSpeedMap(const std::string &input_filename);
57 |
58 | /**
59 | * Loads from,to,speed data from multiple files
60 | */
61 | SegmentSpeedMap(const std::vector &input_filenames);
62 |
63 | /**
64 | * Parses and loads another CSV file into the existing data
65 | */
66 | void loadCSV(const std::string &input_filename);
67 |
68 | /**
69 | * Adds a single to/from pair value with support for unit.
70 | */
71 | inline void add_with_unit(const external_nodeid_t &from,
72 | const external_nodeid_t &to,
73 | const std::uint32_t &speed,
74 | const bool &mph);
75 |
76 | /**
77 | * Adds a single to/from pair value
78 | */
79 | inline void
80 | add(const external_nodeid_t &from, const external_nodeid_t &to, const std::uint32_t &speed);
81 |
82 | /**
83 | * Checks if a pair of nodes exist together in the map
84 | */
85 | bool hasKey(const external_nodeid_t &from, const external_nodeid_t &to) const;
86 |
87 | /**
88 | * Gets the value for a pair of nodes.
89 | * @throws a runtime_exception if the pair aren't found.
90 | */
91 | segment_speed_t getValue(const external_nodeid_t &from, const external_nodeid_t &to) const;
92 |
93 | /**
94 | * Given a list of nodes, returns the speed for each sequential pair.
95 | * e.g. given A,B,C,D,E, will return speeds for AB,BC,CD,DE
96 | * If certain pairs don't exist, the value in the result array will be INVALID_SPEED
97 | * There should be at least 2 values in in the `route` array
98 | */
99 | std::vector getValues(const std::vector &route) const;
100 |
101 | private:
102 | sparse_hash_map annotations;
103 | };
104 |
105 | #endif
106 |
--------------------------------------------------------------------------------
/src/types.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 |
8 | // Type declarations for node ids and way ids. We don't need the full 64 bits
9 | // for ways as the highest way number isn't close to 2^32 yet.
10 | typedef std::uint64_t external_nodeid_t;
11 | typedef std::uint32_t internal_nodeid_t;
12 | typedef std::uint32_t wayid_t;
13 | typedef std::uint8_t segment_speed_t;
14 |
15 | constexpr float kKmPerMile = 1.609344f;
16 |
17 | // Way ID, and whether it the node pair for it is stored forward or backward
18 | typedef struct
19 | {
20 | wayid_t id;
21 | bool forward;
22 | } way_storage_t;
23 |
24 | static constexpr segment_speed_t INVALID_SPEED = std::numeric_limits::max();
25 |
26 | static constexpr internal_nodeid_t INVALID_INTERNAL_NODEID =
27 | std::numeric_limits::max();
28 | static constexpr wayid_t INVALID_WAYID = std::numeric_limits::max();
29 |
30 | typedef boost::geometry::model::
31 | point>
32 | point_t;
33 | typedef std::pair value_t;
34 |
35 | // Data types for our lookup tables
36 | typedef std::pair internal_nodepair_t;
37 |
38 | typedef std::unordered_map external_internal_map_t;
39 |
40 | typedef std::vector annotated_route_t;
41 |
42 | // Every unique string gets an ID of this type
43 | typedef std::uint32_t stringid_t;
44 |
45 | // A way has several tags. The tagrange is the index of the
46 | // first tag table entry, second is the index of the last
47 | // tag table entry
48 | typedef std::pair tagrange_t;
49 |
50 | // Pairs of string ids in the string table. first = string ID
51 | // of the key, second = string ID of the value
52 | typedef std::pair keyvalue_index_t;
53 |
54 | // Indexes of strings in the char buffer. first = first character
55 | // position, second = length of string
56 | typedef std::pair stringoffset_t;
57 |
58 | // Custom hashing functions for our data types to use in standard containers (sets, maps, etc).
59 | namespace std
60 | {
61 | // This is so that we have reasonable hashing of pairs of nodeids
62 | template <> struct hash
63 | {
64 | inline size_t operator()(const internal_nodepair_t &v) const
65 | {
66 | return (static_cast(v.first) << 32) + v.second;
67 | }
68 | };
69 | } // namespace std
70 |
--------------------------------------------------------------------------------
/src/way_bindings.cpp:
--------------------------------------------------------------------------------
1 | #include "way_bindings.hpp"
2 | #include "types.hpp"
3 | #include
4 | #include
5 |
6 | NAN_MODULE_INIT(WaySpeedLookup::Init)
7 | {
8 | const auto whoami = Nan::New("WaySpeedLookup").ToLocalChecked();
9 |
10 | auto fnTp = Nan::New(New);
11 | fnTp->SetClassName(whoami);
12 | fnTp->InstanceTemplate()->SetInternalFieldCount(1);
13 |
14 | SetPrototypeMethod(fnTp, "loadCSV", loadCSV);
15 | SetPrototypeMethod(fnTp, "getRouteSpeeds", getRouteSpeeds);
16 |
17 | const auto fn = Nan::GetFunction(fnTp).ToLocalChecked();
18 | constructor().Reset(fn);
19 |
20 | Nan::Set(target, whoami, fn);
21 | }
22 |
23 | NAN_METHOD(WaySpeedLookup::New)
24 | {
25 | if (info.Length() != 0)
26 | return Nan::ThrowTypeError("No types expected");
27 |
28 | if (info.IsConstructCall())
29 | {
30 | auto *const self = new WaySpeedLookup;
31 | self->Wrap(info.This());
32 | info.GetReturnValue().Set(info.This());
33 | }
34 | else
35 | {
36 | return Nan::ThrowTypeError(
37 | "Cannot call constructor as function, you need to use 'new' keyword");
38 | }
39 | }
40 |
41 | /**
42 | * Loads a csv file asynchronously
43 | * @function loadCSV
44 | * @param {string} path the path to the CSV file to load
45 | * @param {function} callback function to call when the file is done loading
46 | */
47 | NAN_METHOD(WaySpeedLookup::loadCSV)
48 | {
49 | // In case we already loaded a dataset, this function will transactionally swap in a new one
50 | if (info.Length() != 2 || (!info[0]->IsString() && !info[0]->IsArray()) ||
51 | !info[1]->IsFunction())
52 | return Nan::ThrowTypeError("String (or array of strings) and callback expected");
53 |
54 | std::vector paths;
55 |
56 | if (info[0]->IsString())
57 | {
58 | const Nan::Utf8String utf8String(info[0]);
59 |
60 | if (!(*utf8String))
61 | return Nan::ThrowError("Unable to convert to Utf8String");
62 |
63 | paths.push_back({*utf8String, *utf8String + utf8String.length()});
64 | }
65 | else if (info[0]->IsArray())
66 | {
67 | auto arr = v8::Local::Cast(info[0]);
68 | if (arr->Length() < 1)
69 | {
70 | return Nan::ThrowTypeError("Array must contain at least one filename");
71 | }
72 | for (std::uint32_t idx = 0; idx < arr->Length(); ++idx)
73 | {
74 | const Nan::Utf8String utf8String(Nan::Get(arr, idx).ToLocalChecked());
75 |
76 | if (!(*utf8String))
77 | return Nan::ThrowError("Unable to convert to Utf8String");
78 |
79 | paths.push_back({*utf8String, *utf8String + utf8String.length()});
80 | }
81 | }
82 | else
83 | {
84 | return Nan::ThrowTypeError("First parameter should be a string or an array");
85 | }
86 |
87 | struct CSVLoader final : Nan::AsyncWorker
88 | {
89 | explicit CSVLoader(v8::Local self_,
90 | Nan::Callback *callback,
91 | std::vector paths_)
92 | : Nan::AsyncWorker(callback, "annotator:speed.load"), paths{std::move(paths_)}
93 | {
94 | SaveToPersistent("self", self_);
95 | }
96 |
97 | void Execute() override
98 | {
99 | try
100 | {
101 | map = std::make_shared();
102 | for (const auto &path : paths)
103 | {
104 | map->loadCSV(path);
105 | }
106 | }
107 | catch (const std::exception &e)
108 | {
109 | return SetErrorMessage(e.what());
110 | }
111 | }
112 |
113 | void HandleOKCallback() override
114 | {
115 | Nan::HandleScope scope;
116 | auto self = GetFromPersistent("self")
117 | ->ToObject(v8::Isolate::GetCurrent()->GetCurrentContext())
118 | .ToLocalChecked();
119 | auto *unwrapped = Nan::ObjectWrap::Unwrap(self);
120 | swap(unwrapped->datamap, map);
121 | const constexpr auto argc = 1u;
122 | v8::Local argv[argc] = {Nan::Null()};
123 | callback->Call(argc, argv, async_resource);
124 | }
125 |
126 | std::vector paths;
127 | std::shared_ptr map;
128 | };
129 |
130 | auto *callback = new Nan::Callback{info[1].As()};
131 | Nan::AsyncQueueWorker(new CSVLoader{info.Holder(), callback, std::move(paths)});
132 | }
133 |
134 | /**
135 | * Fetches the values in the hashtable for ways
136 | * @function getRouteSpeeds
137 | * @param {array} ways an array of ways IDs to look up
138 | * @param {function} callback receives the results of the search as an array
139 | * @example
140 | * container.getRouteSpeeds([23,43,12],(err,result) => {
141 | * console.log(result);
142 | * });
143 | */
144 | NAN_METHOD(WaySpeedLookup::getRouteSpeeds)
145 | {
146 | auto *const self = Nan::ObjectWrap::Unwrap(info.Holder());
147 |
148 | if (info.Length() != 2 || !info[0]->IsArray() || !info[1]->IsFunction())
149 | return Nan::ThrowTypeError("Two arguments expected: waysIds (Array), Callback");
150 |
151 | auto callback = info[1].As();
152 | const auto jsWayIds = info[0].As();
153 | // Guard against empty or one nodeId for which no wayId can be assigned
154 | if (jsWayIds->Length() < 1)
155 | return Nan::ThrowTypeError(
156 | "getRouteSpeeds expects 'wayIds' (Array(Number)) of at least length 1");
157 |
158 | std::vector ways_to_query(jsWayIds->Length());
159 |
160 | for (uint32_t i = 0; i < jsWayIds->Length(); ++i)
161 | {
162 | v8::Local jsWayId = Nan::Get(jsWayIds, i).ToLocalChecked();
163 | if (!jsWayId->IsNumber())
164 | return Nan::ThrowTypeError("WayIds must be an array of numbers");
165 | auto signedWayId = Nan::To(jsWayId).FromJust();
166 | if (signedWayId < 0)
167 | return Nan::ThrowTypeError(
168 | "getRouteSpeeds expects 'wayId' within (Array(Number))to be non-negative");
169 |
170 | wayid_t wayId = static_cast(signedWayId);
171 | ways_to_query[i] = wayId;
172 | }
173 |
174 | struct Worker final : Nan::AsyncWorker
175 | {
176 | using Base = Nan::AsyncWorker;
177 |
178 | Worker(std::shared_ptr datamap_,
179 | Nan::Callback *callback,
180 | std::vector wayIds)
181 | : Base(callback, "annotator:speed.annotatefromwayids"), datamap{datamap_},
182 | wayIds(std::move(wayIds))
183 | {
184 | }
185 |
186 | void Execute() override
187 | {
188 | if (datamap)
189 | {
190 | result_annotations = datamap->getValues(wayIds);
191 | }
192 | else
193 | {
194 | result_annotations.resize(wayIds.size());
195 | std::fill(result_annotations.begin(), result_annotations.end(), INVALID_SPEED);
196 | }
197 | }
198 |
199 | void HandleOKCallback() override
200 | {
201 | Nan::HandleScope scope;
202 |
203 | auto jsAnnotations = Nan::New(result_annotations.size());
204 |
205 | for (std::size_t i = 0; i < result_annotations.size(); ++i)
206 | {
207 | auto jsAnnotation = Nan::New(result_annotations[i]);
208 | (void)Nan::Set(jsAnnotations, i, jsAnnotation);
209 | }
210 |
211 | const auto argc = 2u;
212 | v8::Local argv[argc] = {Nan::Null(), jsAnnotations};
213 |
214 | callback->Call(argc, argv, async_resource);
215 | }
216 |
217 | std::shared_ptr datamap;
218 | std::vector wayIds;
219 | std::vector result_annotations;
220 | };
221 |
222 | Nan::AsyncQueueWorker(
223 | new Worker{self->datamap, new Nan::Callback{callback}, std::move(ways_to_query)});
224 | }
225 |
226 | Nan::Persistent &WaySpeedLookup::constructor()
227 | {
228 | static Nan::Persistent init;
229 | return init;
230 | }
231 |
--------------------------------------------------------------------------------
/src/way_bindings.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "way_speed_map.hpp"
6 |
7 | class WaySpeedLookup : public Nan::ObjectWrap
8 | {
9 | public:
10 | static NAN_MODULE_INIT(Init);
11 |
12 | private:
13 | static NAN_METHOD(New);
14 |
15 | static NAN_METHOD(loadCSV);
16 |
17 | static NAN_METHOD(getRouteSpeeds);
18 |
19 | static Nan::Persistent &constructor(); // CPP Land
20 |
21 | std::shared_ptr datamap; // if you want async call
22 | };
23 |
--------------------------------------------------------------------------------
/src/way_speed_map.cpp:
--------------------------------------------------------------------------------
1 | #include "way_speed_map.hpp"
2 | #include
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 |
13 | using spp::sparse_hash_map;
14 |
15 | WaySpeedMap::WaySpeedMap(){};
16 |
17 | WaySpeedMap::WaySpeedMap(const std::string &input_filename) { loadCSV(input_filename); }
18 |
19 | WaySpeedMap::WaySpeedMap(const std::vector &input_filenames)
20 | {
21 | for (const auto &input_filename : input_filenames)
22 | {
23 | loadCSV(input_filename);
24 | }
25 | }
26 |
27 | void WaySpeedMap::loadCSV(const std::string &input_filename)
28 | {
29 | namespace ph = boost::phoenix;
30 | namespace qi = boost::spirit::qi;
31 |
32 | boost::iostreams::mapped_file_source mmap(input_filename);
33 | auto first = mmap.begin(), last = mmap.end();
34 | qi::parse(first, last,
35 | -((qi::uint_ >> ',' >> ((+(qi::char_ - ',')) | "" >> qi::lit("")) >> ',' >>
36 | ("mph" >> qi::attr(true) | "kph" >> qi::attr(false) | "" >> qi::attr(false)) >>
37 | ',' >> qi::uint_)[ph::bind(&WaySpeedMap::add, this, qi::_1, qi::_3, qi::_4)] %
38 | qi::eol) >>
39 | *qi::eol);
40 |
41 | if (first != last)
42 | {
43 | auto bol = first - 1;
44 | while (bol > mmap.begin() && *bol != '\n')
45 | --bol;
46 | auto line_number = std::count(mmap.begin(), first, '\n') + 1;
47 | throw std::runtime_error("CSV parsing failed at " + input_filename + ':' +
48 | std::to_string(line_number) + ": " +
49 | std::string(bol + 1, std::find(first, last, '\n')));
50 | }
51 | }
52 |
53 | void WaySpeedMap::add(const wayid_t &way, const bool &mph, const std::uint32_t &speed)
54 | {
55 |
56 | if (mph)
57 | {
58 | std::uint32_t s = std::round(speed * kKmPerMile);
59 |
60 | if (s > INVALID_SPEED - 1)
61 | {
62 | std::cout << "CSV parsing failed. Way: " << std::to_string(way)
63 | << " Speed: " << std::to_string(s) << std::endl;
64 | }
65 | else
66 | annotations[way] = s;
67 | }
68 | else
69 | {
70 | if (speed > INVALID_SPEED - 1)
71 | {
72 | std::cout << "CSV parsing failed. From Node: " << std::to_string(way)
73 | << " Speed: " << std::to_string(speed) << std::endl;
74 | }
75 | else
76 | annotations[way] = speed;
77 | }
78 | }
79 |
80 | bool WaySpeedMap::hasKey(const wayid_t &way) const { return (annotations.count(way) > 0); }
81 |
82 | segment_speed_t WaySpeedMap::getValue(const wayid_t &way) const
83 | {
84 | // Save the result of find so that we don't need to repeat the lookup to get the value
85 | auto result = annotations.find(way);
86 | if (result == annotations.end())
87 | throw std::runtime_error("Way ID " + std::to_string(way) +
88 | " doesn't exist in the hashmap.");
89 |
90 | // Use the already retrieved value as the result
91 | return result->second;
92 | }
93 |
94 | std::vector WaySpeedMap::getValues(const std::vector &route) const
95 | {
96 | std::vector speeds;
97 | if (route.size() < 1)
98 | throw std::runtime_error("Way Array should have at least 1 way ID for getValues method.");
99 |
100 | speeds.resize(route.size());
101 | for (std::size_t way_index = 0; way_index < speeds.size(); ++way_index)
102 | {
103 | auto result = annotations.find(route[way_index]);
104 | if (result == annotations.end())
105 | speeds[way_index] = INVALID_SPEED;
106 | else
107 | speeds[way_index] = result->second;
108 | }
109 | return speeds;
110 | }
111 |
--------------------------------------------------------------------------------
/src/way_speed_map.hpp:
--------------------------------------------------------------------------------
1 | #ifndef WAY_SPEED_MAP_H
2 | #define WAY_SPEED_MAP_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "types.hpp"
10 |
11 | using spp::sparse_hash_map;
12 |
13 | class WaySpeedMap
14 | {
15 | public:
16 | /**
17 | * Do-nothing constructor
18 | */
19 | WaySpeedMap();
20 |
21 | /**
22 | * Loads from,to,speed data from a single file
23 | */
24 | WaySpeedMap(const std::string &input_filename);
25 |
26 | /**
27 | * Loads way,speed data from multiple files
28 | */
29 | WaySpeedMap(const std::vector &input_filenames);
30 |
31 | /**
32 | * Parses and loads another CSV file into the existing data
33 | */
34 | void loadCSV(const std::string &input_filename);
35 |
36 | /**
37 | * Adds a single way, speed key value pair
38 | */
39 | inline void add(const wayid_t &way, const bool &mph, const std::uint32_t &speed);
40 |
41 | /**
42 | * Checks if a way exists in the map
43 | */
44 | bool hasKey(const wayid_t &way) const;
45 |
46 | /**
47 | * Gets the value for a ways.
48 | * @throws a runtime_exception if the way is not found.
49 | */
50 | segment_speed_t getValue(const wayid_t &way) const;
51 |
52 | /**
53 | * Given a list of ways, returns the speed for each way.
54 | * If ways don't exist, the value in the result array will be INVALID_SPEED
55 | * There should be at least 1 values in in the `route` array
56 | */
57 | std::vector