├── .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 | [![Build Status](https://travis-ci.org/mapbox/route-annotator.svg?branch=master)](https://travis-ci.org/mapbox/route-annotator) [![CodeCov](https://codecov.io/gh/mapbox/route-annotator/branch/master/graph/badge.svg)](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 getValues(const std::vector &ways) const; 58 | 59 | private: 60 | sparse_hash_map annotations; 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /test/basic-tests.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE basic tests 2 | 3 | #include 4 | 5 | /* 6 | * This file will contain an automatically generated main function. 7 | */ 8 | -------------------------------------------------------------------------------- /test/basic/annotator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "annotator.hpp" 6 | #include "database.hpp" 7 | 8 | BOOST_AUTO_TEST_SUITE(annotator_test) 9 | 10 | BOOST_AUTO_TEST_CASE(annotator_test_no_rtree) 11 | { 12 | 13 | Database db(false); 14 | const auto keyid = db.addstring("highway"); 15 | const auto valueid = db.addstring("primary"); 16 | db.key_value_pairs.emplace_back(keyid, valueid); 17 | db.way_tag_ranges.emplace_back(0, 0); 18 | db.pair_way_map.emplace(internal_nodepair_t{0, 1}, way_storage_t{0, true}); 19 | db.compact(); 20 | RouteAnnotator annotator(db); 21 | 22 | std::vector coordinates{point_t{1, 1}, point_t{1.5, 1.5}, point_t{2, 2}, 23 | point_t{3, 1}}; 24 | BOOST_CHECK_THROW(annotator.coordinates_to_internal(coordinates), RouteAnnotator::RtreeError); 25 | 26 | // Check for a route pair that exists 27 | std::vector route{0, 1}; 28 | auto result = annotator.annotateRoute(route); 29 | BOOST_CHECK_EQUAL(result.size(), 1); 30 | BOOST_CHECK_EQUAL(result[0], 0); 31 | 32 | auto tagrange = annotator.get_tag_range(result[0]); 33 | BOOST_CHECK_EQUAL(tagrange.first, 0); 34 | BOOST_CHECK_EQUAL(tagrange.second, 0); 35 | 36 | BOOST_CHECK_EQUAL(annotator.get_tag_key(tagrange.first), "highway"); 37 | BOOST_CHECK_EQUAL(annotator.get_tag_value(tagrange.first), "primary"); 38 | } 39 | 40 | BOOST_AUTO_TEST_CASE(annotator_test_basic) 41 | { 42 | 43 | Database db(true); 44 | const auto keyid = db.addstring("highway"); 45 | const auto valueid = db.addstring("primary"); 46 | db.key_value_pairs.emplace_back(keyid, valueid); 47 | db.way_tag_ranges.emplace_back(0, 0); 48 | db.pair_way_map.emplace(internal_nodepair_t{0, 1}, way_storage_t{0, true}); 49 | db.build_rtree(); 50 | db.compact(); 51 | RouteAnnotator annotator(db); 52 | 53 | // Check for a route pair that exists 54 | std::vector route{0, 1}; 55 | auto result = annotator.annotateRoute(route); 56 | BOOST_CHECK_EQUAL(result.size(), 1); 57 | BOOST_CHECK_EQUAL(result[0], 0); 58 | 59 | // Check for a route pair that doesn't exist 60 | route = std::vector{1, 2}; 61 | result = annotator.annotateRoute(route); 62 | BOOST_CHECK_EQUAL(result.size(), 1); 63 | BOOST_CHECK_EQUAL(result[0], INVALID_WAYID); 64 | 65 | route = std::vector{2, 0, 1, 7}; 66 | result = annotator.annotateRoute(route); 67 | BOOST_CHECK_EQUAL(result.size(), 3); 68 | BOOST_CHECK_EQUAL(result[0], INVALID_WAYID); 69 | BOOST_CHECK_EQUAL(result[1], 0); 70 | BOOST_CHECK_EQUAL(result[2], INVALID_WAYID); 71 | 72 | auto tagrange = annotator.get_tag_range(result[1]); 73 | BOOST_CHECK_EQUAL(tagrange.first, 0); 74 | BOOST_CHECK_EQUAL(tagrange.second, 0); 75 | 76 | BOOST_CHECK_EQUAL(annotator.get_tag_key(tagrange.first), "highway"); 77 | BOOST_CHECK_EQUAL(annotator.get_tag_value(tagrange.first), "primary"); 78 | } 79 | 80 | BOOST_AUTO_TEST_CASE(annotator_test_externalids) 81 | { 82 | 83 | Database db(true); 84 | db.pair_way_map.emplace(internal_nodepair_t{0, 1}, way_storage_t{0, true}); 85 | db.external_internal_map.emplace(12345, 7); 86 | db.external_internal_map.emplace(12346, 9); 87 | db.external_internal_map.emplace(12347, 13); 88 | db.build_rtree(); 89 | db.compact(); 90 | RouteAnnotator annotator(db); 91 | 92 | // Check that we match the expected coordinates 93 | std::vector external_nodeids{234857, 12345, 394875, 12346}; 94 | auto result = annotator.external_to_internal(external_nodeids); 95 | BOOST_CHECK_EQUAL(result.size(), 4); 96 | BOOST_CHECK_EQUAL(result[0], INVALID_INTERNAL_NODEID); 97 | BOOST_CHECK_EQUAL(result[1], 7); 98 | BOOST_CHECK_EQUAL(result[2], INVALID_INTERNAL_NODEID); 99 | BOOST_CHECK_EQUAL(result[3], 9); 100 | } 101 | 102 | BOOST_AUTO_TEST_CASE(annotator_test_coordinates) 103 | { 104 | 105 | Database db(true); 106 | db.used_nodes_list.emplace_back(point_t{1, 1}, 7); 107 | db.used_nodes_list.emplace_back(point_t{2, 2}, 9); 108 | db.used_nodes_list.emplace_back(point_t{3, 3}, 13); 109 | db.build_rtree(); 110 | db.compact(); 111 | RouteAnnotator annotator(db); 112 | 113 | // Check that we match the expected coordinates 114 | std::vector coordinates{point_t{1, 1}, point_t{1.5, 1.5}, point_t{2, 2}, 115 | point_t{3, 1}}; 116 | auto result = annotator.coordinates_to_internal(coordinates); 117 | BOOST_CHECK_EQUAL(result.size(), 4); 118 | BOOST_CHECK_EQUAL(result[0], 7); 119 | BOOST_CHECK_EQUAL(result[1], INVALID_INTERNAL_NODEID); 120 | BOOST_CHECK_EQUAL(result[2], 9); 121 | BOOST_CHECK_EQUAL(result[3], INVALID_INTERNAL_NODEID); 122 | } 123 | 124 | BOOST_AUTO_TEST_SUITE_END() 125 | -------------------------------------------------------------------------------- /test/basic/database.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "annotator.hpp" 6 | #include "database.hpp" 7 | 8 | BOOST_AUTO_TEST_SUITE(database_test) 9 | 10 | // Verify that the bearing-bounds checking function behaves as expected 11 | BOOST_AUTO_TEST_CASE(database_string_test) 12 | { 13 | BOOST_CHECK_EQUAL(true, true); 14 | 15 | Database db; 16 | 17 | // point_t a{1, 1}; 18 | const auto id = db.addstring("test"); 19 | db.compact(); 20 | BOOST_CHECK_EQUAL(db.getstring(id), "test"); 21 | BOOST_CHECK_EQUAL(id, 0); 22 | 23 | Database db_with_rtree(true); 24 | db_with_rtree.compact(); 25 | db_with_rtree.build_rtree(); 26 | 27 | // Disabling this test - there are no external inputs here, 28 | // so we disable range checking for performance. 29 | // BOOST_CHECK_THROW(db.getstring(id+1),std::out_of_range); 30 | } 31 | 32 | BOOST_AUTO_TEST_SUITE_END() 33 | -------------------------------------------------------------------------------- /test/basic/extractor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "annotator.hpp" 6 | #include "database.hpp" 7 | #include "extractor.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | BOOST_AUTO_TEST_SUITE(extractor_test) 14 | 15 | BOOST_AUTO_TEST_CASE(annotator_test_basic) 16 | { 17 | 18 | std::string buffer("\n" 19 | "\n" 20 | "\n" 21 | "\n" 22 | "\n" 23 | "\n" 24 | " \n" 25 | " \n" 26 | " \n" 27 | " \n" 28 | "\n" 29 | ""); 30 | 31 | Database db(true); 32 | Extractor extractor(buffer.c_str(), buffer.size(), "xml", db); 33 | BOOST_CHECK(db.rtree); 34 | 35 | BOOST_CHECK_EQUAL(db.pair_way_map.size(), 2); 36 | BOOST_CHECK_EQUAL(db.external_internal_map.size(), 3); 37 | BOOST_CHECK_EQUAL(db.external_internal_map[101], 0); 38 | BOOST_CHECK_EQUAL(db.external_internal_map[202], 1); 39 | 40 | // TODO: check this - how should we handle items that don't exist ? 41 | BOOST_CHECK_EQUAL(db.external_internal_map[1], 0); 42 | } 43 | 44 | BOOST_AUTO_TEST_SUITE_END() 45 | -------------------------------------------------------------------------------- /test/basic/rtree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "annotator.hpp" 6 | #include "database.hpp" 7 | 8 | BOOST_AUTO_TEST_SUITE(rtree_test) 9 | 10 | // Verify that the bearing-bounds checking function behaves as expected 11 | BOOST_AUTO_TEST_CASE(rtree_basic_test) 12 | { 13 | Database db(true); 14 | 15 | db.used_nodes_list.emplace_back(point_t{1, 1}, 73); 16 | db.build_rtree(); 17 | db.compact(); 18 | BOOST_CHECK_EQUAL(db.rtree->size(), 1); 19 | } 20 | 21 | BOOST_AUTO_TEST_CASE(rtree_not_initialized) 22 | { 23 | Database db; 24 | 25 | static const internal_nodeid_t TESTNODE{73}; 26 | 27 | db.used_nodes_list.emplace_back(point_t{1, 1}, TESTNODE); 28 | 29 | RouteAnnotator annotator(db); 30 | 31 | std::vector points{point_t{1, 1}}; 32 | BOOST_CHECK_THROW(annotator.coordinates_to_internal(points), std::runtime_error); 33 | } 34 | 35 | BOOST_AUTO_TEST_CASE(rtree_radius_test) 36 | { 37 | Database db(true); 38 | 39 | static const internal_nodeid_t TESTNODE{73}; 40 | 41 | db.used_nodes_list.emplace_back(point_t{1, 1}, TESTNODE); 42 | db.build_rtree(); 43 | db.compact(); 44 | 45 | RouteAnnotator annotator(db); 46 | 47 | std::vector points{point_t{1, 1}}; 48 | auto results = annotator.coordinates_to_internal(points); 49 | BOOST_CHECK_EQUAL(results.size(), 1); 50 | BOOST_CHECK_EQUAL(results[0], TESTNODE); 51 | 52 | // This should be within about 15km 53 | points = std::vector{point_t{1.1, 1.1}}; 54 | results = annotator.coordinates_to_internal(points); 55 | BOOST_CHECK_EQUAL(results.size(), 1); 56 | BOOST_CHECK_EQUAL(results[0], INVALID_INTERNAL_NODEID); 57 | 58 | // This should be within about 1.1m 59 | points = std::vector{point_t{1.00005, 1}}; 60 | results = annotator.coordinates_to_internal(points); 61 | BOOST_CHECK_EQUAL(results.size(), 1); 62 | BOOST_CHECK_EQUAL(results[0], INVALID_INTERNAL_NODEID); 63 | 64 | // This should be within about 10cm 65 | points = std::vector{point_t{1.000001, 1}}; 66 | results = annotator.coordinates_to_internal(points); 67 | BOOST_CHECK_EQUAL(results.size(), 1); 68 | BOOST_CHECK_EQUAL(results[0], TESTNODE); 69 | 70 | // Test at high latitudes 71 | db = Database(true); 72 | db.used_nodes_list.emplace_back(point_t{1, 65}, TESTNODE); 73 | db.build_rtree(); 74 | db.compact(); 75 | 76 | // This should be about 0.45m 77 | points = std::vector{point_t{1.00001, 65}}; 78 | results = annotator.coordinates_to_internal(points); 79 | BOOST_CHECK_EQUAL(results.size(), 1); 80 | BOOST_CHECK_EQUAL(results[0], TESTNODE); 81 | 82 | // This should be about 6.5m (so should NOT match) 83 | points = std::vector{point_t{1.00013, 65}}; 84 | results = annotator.coordinates_to_internal(points); 85 | BOOST_CHECK_EQUAL(results.size(), 1); 86 | BOOST_CHECK_EQUAL(results[0], INVALID_INTERNAL_NODEID); 87 | } 88 | 89 | BOOST_AUTO_TEST_SUITE_END() 90 | -------------------------------------------------------------------------------- /test/bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "annotator.hpp" 9 | #include "database.hpp" 10 | #include "extractor.hpp" 11 | 12 | #include 13 | 14 | /** 15 | * Simple program to show how to initalize the annotator 16 | * from a C++ utility. 17 | */ 18 | int main(int argc, char *argv[]) 19 | { 20 | 21 | if (argc == 0) 22 | { 23 | std::cerr << "Usage: " << argv[0] << " filename.[osm|pbf]" << std::endl; 24 | return EXIT_FAILURE; 25 | } 26 | 27 | std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); 28 | 29 | Database db(false); 30 | Extractor extractor({std::string(argv[1])}, db); 31 | std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); 32 | 33 | std::cout << "Done in " 34 | << std::chrono::duration_cast(end - start).count() << "ms" 35 | << std::endl; 36 | } 37 | -------------------------------------------------------------------------------- /test/congestion-tests.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE congestion tests 2 | 3 | #include 4 | 5 | /* 6 | * This file will contain an automatically generated main function. 7 | */ 8 | -------------------------------------------------------------------------------- /test/congestion/congestion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "segment_speed_map.hpp" 7 | #include "types.hpp" 8 | 9 | BOOST_AUTO_TEST_SUITE(congestions_test) 10 | 11 | // Verify that the bearing-bounds checking function behaves as expected 12 | BOOST_AUTO_TEST_CASE(congestion_test_basic) 13 | { 14 | SegmentSpeedMap map("test/congestion/fixtures/congestion.csv"); 15 | 16 | BOOST_CHECK_EQUAL(map.getValue(86909055, 86909053), 81); 17 | BOOST_CHECK_EQUAL(map.hasKey(100, 100), false); 18 | } 19 | 20 | BOOST_AUTO_TEST_CASE(congestion_test_many) 21 | { 22 | SegmentSpeedMap map("test/congestion/fixtures/congestion.csv"); 23 | 24 | BOOST_CHECK_EQUAL(map.getValue(86909055, 86909053), 81); 25 | BOOST_CHECK_EQUAL(map.getValue(86909053, 86909050), 81); 26 | BOOST_CHECK_EQUAL(map.getValue(86909050, 86909053), 81); 27 | BOOST_CHECK_EQUAL(map.getValue(86909053, 86909055), 81); 28 | BOOST_CHECK_EQUAL(map.getValue(86623322, 86622998), 89); 29 | BOOST_CHECK_EQUAL(map.getValue(86622998, 86623322), 88); 30 | BOOST_CHECK_EQUAL(map.getValue(86909074, 86909072), 79); 31 | BOOST_CHECK_EQUAL(map.getValue(297977101, 312122762), 5); 32 | BOOST_CHECK_EQUAL(map.getValue(69395079, 69402983), 23); 33 | BOOST_CHECK_EQUAL(map.getValue(3860306483, 1362215135), 6); 34 | BOOST_CHECK_EQUAL(map.getValue(1362215135, 297976455), 10); 35 | } 36 | 37 | BOOST_AUTO_TEST_CASE(congestion_test_get_values) 38 | { 39 | SegmentSpeedMap map("test/congestion/fixtures/congestion.csv"); 40 | 41 | std::vector nodes{86909055, 86909053, 86909050, 86909053, 42 | 86909055, 3860306483, 1362215135, 297976455}; 43 | std::vector speeds{81, 81, 81, 81, INVALID_SPEED, 6, 10}; 44 | std::vector response = map.getValues(nodes); 45 | BOOST_CHECK_EQUAL_COLLECTIONS(response.begin(), response.end(), speeds.begin(), speeds.end()); 46 | } 47 | 48 | BOOST_AUTO_TEST_CASE(congestion_load_multiple) 49 | { 50 | std::vector nodes; 51 | std::vector expected; 52 | std::vector actual; 53 | 54 | std::vector paths = {"test/congestion/fixtures/congestion.csv", 55 | "test/congestion/fixtures/congestion2.csv"}; 56 | SegmentSpeedMap map(paths); 57 | 58 | nodes = {86909055, 86909053, 86909050, 86909053, 86909055, 3860306483, 1362215135, 297976455}; 59 | expected = {81, 81, 81, 81, INVALID_SPEED, 6, 10}; 60 | actual = map.getValues(nodes); 61 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 62 | 63 | nodes = {90, 91, 92, 93, 94, 297976455}; 64 | expected = {2, 3, 4, 5, 10}; 65 | actual = map.getValues(nodes); 66 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 67 | } 68 | 69 | BOOST_AUTO_TEST_CASE(congestion_load_incremental) 70 | { 71 | std::vector nodes; 72 | std::vector expected; 73 | std::vector actual; 74 | 75 | std::vector paths = {"test/congestion/fixtures/congestion.csv"}; 76 | SegmentSpeedMap map(paths); 77 | 78 | nodes = {86909055, 86909053, 86909050, 86909053, 86909055, 3860306483, 1362215135, 297976455}; 79 | expected = {81, 81, 81, 81, INVALID_SPEED, 6, 10}; 80 | actual = map.getValues(nodes); 81 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 82 | 83 | nodes = {90, 91, 92, 93, 94, 297976455}; 84 | expected = {INVALID_SPEED, INVALID_SPEED, INVALID_SPEED, INVALID_SPEED, INVALID_SPEED}; 85 | actual = map.getValues(nodes); 86 | 87 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 88 | 89 | map.loadCSV("test/congestion/fixtures/congestion2.csv"); 90 | 91 | expected = {2, 3, 4, 5, 10}; 92 | actual = map.getValues(nodes); 93 | 94 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 95 | } 96 | 97 | BOOST_AUTO_TEST_CASE(congestion_speeds_test_speed_greater_than_max) 98 | { 99 | std::vector paths = {"test/congestion/fixtures/congestion_speed_max.csv"}; 100 | SegmentSpeedMap map(paths); 101 | 102 | std::vector nodes = {6909081, 86909079, 69402983}; 103 | std::vector expected = {159, INVALID_SPEED}; 104 | std::vector actual = map.getValues(nodes); 105 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 106 | 107 | map.loadCSV("test/congestion/fixtures/congestion_speed_max2.csv"); 108 | expected = {159, INVALID_SPEED}; // first import was not overwritten. 109 | actual = map.getValues(nodes); 110 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 111 | } 112 | 113 | // Execptions 114 | BOOST_AUTO_TEST_CASE(congestion_test_string_input) 115 | { 116 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/string_input.csv"), 117 | std::exception); 118 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/string_input2.csv"), 119 | std::exception); 120 | } 121 | 122 | BOOST_AUTO_TEST_CASE(congestion_test_more_columns) 123 | { 124 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/more_columns.csv"), 125 | std::exception); 126 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/more_columns2.csv"), 127 | std::exception); 128 | } 129 | 130 | BOOST_AUTO_TEST_CASE(congestion_test_fewer_columns) 131 | { 132 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/fewer_columns.csv"), 133 | std::exception); 134 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/fewer_columns2.csv"), 135 | std::exception); 136 | } 137 | 138 | BOOST_AUTO_TEST_CASE(congestion_test_negative_number) 139 | { 140 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/negative_number.csv"), 141 | std::exception); 142 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/negative_number2.csv"), 143 | std::exception); 144 | } 145 | 146 | BOOST_AUTO_TEST_CASE(congestion_test_invalid_csv) 147 | { 148 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/invalid_csv.csv"), 149 | std::exception); 150 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/invalid_csv2.csv"), 151 | std::exception); 152 | } 153 | 154 | BOOST_AUTO_TEST_CASE(congestion_test_header) 155 | { 156 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/header.csv"), std::exception); 157 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/header2.csv"), std::exception); 158 | } 159 | 160 | BOOST_AUTO_TEST_CASE(congestion_test_not_a_number) 161 | { 162 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/not_a_number.csv"), 163 | std::exception); 164 | BOOST_CHECK_THROW(SegmentSpeedMap map("test/congestion/fixtures/not_a_number2.csv"), 165 | std::exception); 166 | } 167 | 168 | BOOST_AUTO_TEST_CASE(congestion_test_many_exceptions) 169 | { 170 | SegmentSpeedMap map("test/congestion/fixtures/congestion.csv"); 171 | 172 | BOOST_CHECK_THROW(map.getValue(86909050, 86900053), std::exception); 173 | BOOST_CHECK_THROW(map.getValue(86900053, 86900050), std::exception); 174 | BOOST_CHECK_THROW(map.getValue(86903150, 86900053), std::exception); 175 | BOOST_CHECK_THROW(map.getValue(86909253, 86900055), std::exception); 176 | BOOST_CHECK_THROW(map.getValue(86623122, 86620998), std::exception); 177 | BOOST_CHECK_THROW(map.getValue(86600998, 86620322), std::exception); 178 | BOOST_CHECK_THROW(map.getValue(86900074, 86900072), std::exception); 179 | BOOST_CHECK_THROW(map.getValue(207977101, 312122762), std::exception); 180 | BOOST_CHECK_THROW(map.getValue(63395079, 69402983), std::exception); 181 | BOOST_CHECK_THROW(map.getValue(3560306483, 1362215135), std::exception); 182 | BOOST_CHECK_THROW(map.getValue(1362215135, 297076455), std::exception); 183 | } 184 | 185 | BOOST_AUTO_TEST_CASE(congestion_test_get_values_exceptions) 186 | { 187 | SegmentSpeedMap map("test/congestion/fixtures/congestion.csv"); 188 | 189 | std::vector nodes{86909055}; 190 | BOOST_CHECK_THROW(map.getValues(nodes), std::exception); 191 | 192 | nodes = {297076455}; 193 | BOOST_CHECK_THROW(map.getValues(nodes), std::exception); 194 | 195 | nodes = {86909050, 86900053, 86900050, 297076455}; 196 | std::vector expected{INVALID_SPEED, INVALID_SPEED, INVALID_SPEED}; 197 | std::vector actual = map.getValues(nodes); 198 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 199 | } 200 | 201 | BOOST_AUTO_TEST_SUITE_END() 202 | -------------------------------------------------------------------------------- /test/congestion/fixtures/congestion.csv: -------------------------------------------------------------------------------- 1 | 86909055,86909053,81 2 | 86909053,86909050,81 3 | 86909050,86909053,81 4 | 86909053,86909055,81 5 | 86623322,86622998,89 6 | 86622998,86623322,88 7 | 86909074,86909072,79 8 | 86909072,86909069,79 9 | 86909069,86909068,79 10 | 86909068,86909066,79 11 | 86909066,86909064,79 12 | 86909064,86909061,79 13 | 86909061,86909057,79 14 | 86909057,86623322,79 15 | 86623322,86909057,80 16 | 86909057,86909061,80 17 | 86909061,86909064,80 18 | 86909064,86909066,80 19 | 86909066,86909068,80 20 | 86909068,86909069,80 21 | 86909069,86909072,80 22 | 86909072,86909074,80 23 | 86909091,86909089,82 24 | 86909089,86909087,82 25 | 86909087,86909085,82 26 | 86909085,86909084,82 27 | 86909084,86909081,82 28 | 86909081,86909079,82 29 | 86909079,86909076,82 30 | 86909076,86909074,82 31 | 86909074,86909076,78 32 | 86909076,86909079,78 33 | 86909079,86909081,78 34 | 86909081,86909084,78 35 | 297977101,312122762,,5 36 | 69395079,69402983,mph,14 37 | 3860306483,1362215135,mph,11 38 | 3860306483,1362215135,mph,4 39 | 74403857,74393346,mph,6 40 | 1362215135,297976455,kph,10 41 | -------------------------------------------------------------------------------- /test/congestion/fixtures/congestion2.csv: -------------------------------------------------------------------------------- 1 | 90,91,2 2 | 91,92,3 3 | 92,93,4 4 | 93,94,5 5 | 94,297976455,kph,10 6 | 94,95,6 7 | -------------------------------------------------------------------------------- /test/congestion/fixtures/congestion_speed_max.csv: -------------------------------------------------------------------------------- 1 | 6909081,86909079,159 2 | 69395079,69402983,mph,159 3 | -------------------------------------------------------------------------------- /test/congestion/fixtures/congestion_speed_max2.csv: -------------------------------------------------------------------------------- 1 | 6909081,86909079,858 2 | 69395079,69402983,mph,159 3 | -------------------------------------------------------------------------------- /test/congestion/fixtures/fewer_columns.csv: -------------------------------------------------------------------------------- 1 | 86909055,81 2 | 86909053,86909050,81 3 | 86909053,86909040,81 -------------------------------------------------------------------------------- /test/congestion/fixtures/fewer_columns2.csv: -------------------------------------------------------------------------------- 1 | 86909055,81,mph 2 | 86909053,86909050,81 3 | 86909053,86909040,81 4 | -------------------------------------------------------------------------------- /test/congestion/fixtures/header.csv: -------------------------------------------------------------------------------- 1 | from_node_id, to_node_id, speed 2 | 86909055,86909053,81 3 | 86909053,86909050,81 4 | 86909053,86909040,81 -------------------------------------------------------------------------------- /test/congestion/fixtures/header2.csv: -------------------------------------------------------------------------------- 1 | from_node_id, to_node_id, unit, speed 2 | 86909055,86909053,mph,81 3 | 86909053,86909050,mph,81 4 | 86909053,86909040,mph,81 5 | -------------------------------------------------------------------------------- /test/congestion/fixtures/invalid_csv.csv: -------------------------------------------------------------------------------- 1 | 86909055:86909053:81 2 | 86909053,86909050,81 3 | 86909053,86909040,81 -------------------------------------------------------------------------------- /test/congestion/fixtures/invalid_csv2.csv: -------------------------------------------------------------------------------- 1 | 86909055:86909053:mph:81 2 | 86909053,86909050,mph,81 3 | 86909053,86909040,mph,81 4 | -------------------------------------------------------------------------------- /test/congestion/fixtures/more_columns.csv: -------------------------------------------------------------------------------- 1 | 86909055,81,86,81 2 | 86909053,86909050,81 3 | 86909053,86909040,81 4 | -------------------------------------------------------------------------------- /test/congestion/fixtures/more_columns2.csv: -------------------------------------------------------------------------------- 1 | 86909055,81,86,mph,81 2 | 86909053,86909050,81 3 | 86909053,86909040,81 4 | -------------------------------------------------------------------------------- /test/congestion/fixtures/negative_number.csv: -------------------------------------------------------------------------------- 1 | 86909055,-81,86909053 2 | 86909053,86909050,81 3 | 86909053,86909040,81 -------------------------------------------------------------------------------- /test/congestion/fixtures/negative_number2.csv: -------------------------------------------------------------------------------- 1 | 86909055,-81,mph,86909053 2 | 86909053,86909050,mph,81 3 | 86909053,86909040,mph,81 4 | -------------------------------------------------------------------------------- /test/congestion/fixtures/not_a_number.csv: -------------------------------------------------------------------------------- 1 | 86909055,sdfsdf,81 2 | 86909053,86909050,81 3 | 86909053,86909040,81 -------------------------------------------------------------------------------- /test/congestion/fixtures/not_a_number2.csv: -------------------------------------------------------------------------------- 1 | 86909055,sdfsdf,mph,81 2 | 86909053,86909050,mph,81 3 | 86909053,86909040,mph,81 4 | -------------------------------------------------------------------------------- /test/congestion/fixtures/string_input.csv: -------------------------------------------------------------------------------- 1 | 86909055,86909053,81 2 | 86909053,86909050,81 3 | 86909050,"absdfm",81 -------------------------------------------------------------------------------- /test/congestion/fixtures/string_input2.csv: -------------------------------------------------------------------------------- 1 | 86909055,86909053,mph,81 2 | 86909053,86909050,mph,81 3 | 86909050,"absdfm",mph,81 4 | -------------------------------------------------------------------------------- /test/data/baltimore.osm.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/route-annotator/fc370703adc31cf0f2de5383102e1f5aaf184c12/test/data/baltimore.osm.pbf -------------------------------------------------------------------------------- /test/data/multiple-tags: -------------------------------------------------------------------------------- 1 | maxspeed 2 | tunnel 3 | -------------------------------------------------------------------------------- /test/data/paris.extract.osm.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/route-annotator/fc370703adc31cf0f2de5383102e1f5aaf184c12/test/data/paris.extract.osm.pbf -------------------------------------------------------------------------------- /test/data/tags: -------------------------------------------------------------------------------- 1 | maxspeed 2 | -------------------------------------------------------------------------------- /test/load.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | const bindings = require('../index'); 3 | const annotator = new bindings.Annotator({ coordinates: true }); 4 | const segmentmap = new bindings.SegmentSpeedLookup(); 5 | const waymap = new bindings.WaySpeedLookup(); 6 | 7 | const path = require('path'); 8 | 9 | test('invalid initialization', (t) => { 10 | try { 11 | const tempannotator1 = new bindings.Annotator({ coordinates2: true }); 12 | t.fail('Should not get here'); 13 | t.end(); 14 | } 15 | catch(err) { 16 | t.ok('Should fail to initialize on invalid options'); 17 | } 18 | 19 | try { 20 | const tempannotator2 = bindings.Annotator({ coordinates: false }); 21 | t.fail('Should not get here'); 22 | t.end(); 23 | } 24 | catch(err) { 25 | t.ok('Should fail to initialize, needs `new` keyword'); 26 | t.end(); 27 | } 28 | }); 29 | 30 | test('annotator use before initialization', (t) => { 31 | try { 32 | annotator.annotateRouteFromNodeIds([1,2], (err, wayIds) => { 33 | t.fail('Should not get here'); 34 | }) 35 | t.fail('Should not get here'); 36 | } 37 | catch (err) { 38 | t.ok(err, "Should fail if not yet initialized"); 39 | } 40 | try { 41 | annotator.annotateRouteFromLonLats([1,2], (err, wayIds) => { 42 | t.fail('Should not get here'); 43 | }) 44 | t.fail('Should not get here'); 45 | } 46 | catch (err) { 47 | t.ok(err, "Should fail if not yet initialized"); 48 | } 49 | 50 | try { 51 | annotator.getAllTagsForWayId(99, (err, wayIds) => { 52 | t.fail('Should not get here'); 53 | }) 54 | t.fail('Should not get here'); 55 | } 56 | catch (err) { 57 | t.ok(err, "Should fail if not yet initialized"); 58 | t.end(); 59 | } 60 | 61 | }); 62 | 63 | test('invalid load options', function(t) { 64 | try { 65 | annotator.loadOSMExtract(1234, (err) => { 66 | t.fail('Should never get here');; 67 | }); 68 | t.fail('Should never get here');; 69 | } 70 | catch (err) { 71 | t.ok(err, 'Should fail if you don\'t pass in a filename'); 72 | } 73 | 74 | try { 75 | annotator.loadOSMExtract("dummyfile", 1234); 76 | t.fail('Should never get here');; 77 | } 78 | catch (err) { 79 | t.ok(err, 'Should fail if the second parameter isn\'t a callback'); 80 | } 81 | 82 | try { 83 | annotator.loadOSMExtract([1234], (err) => { 84 | t.fail("Should never get here"); 85 | }); 86 | t.fail('Should never get here');; 87 | } 88 | catch (err) { 89 | t.ok(err, 'Should fail if an array member isn\'t a string'); 90 | t.end(); 91 | } 92 | 93 | }); 94 | 95 | test('load extract', function(t) { 96 | annotator.loadOSMExtract(path.join(__dirname,'data/winthrop.osm'), (err) => { 97 | if (err) throw err; 98 | t.end(); 99 | }); 100 | }); 101 | 102 | test('invalid annotation parameters', (t) => { 103 | try { 104 | annotator.annotateRouteFromNodeIds([-1, 2], (err, wayIds) => { 105 | t.fail("Should never get here"); 106 | }); 107 | t.fail("Should never get here"); 108 | } 109 | catch (err) { 110 | t.ok(err, "Should fail if a nodeID is not convertible to unsigned integer"); 111 | } 112 | 113 | try { 114 | annotator.annotateRouteFromNodeIds(["asdf", 2], (err, wayIds) => { 115 | t.fail("Should never get here"); 116 | }); 117 | t.fail("Should never get here"); 118 | } 119 | catch (err) { 120 | t.ok(err, "Should fail if a nodeID is not a number"); 121 | } 122 | 123 | try { 124 | annotator.annotateRouteFromLonLats(["asdf", 2], (err, wayIds) => { 125 | t.fail("Should never get here"); 126 | }); 127 | t.fail("Should never get here"); 128 | } 129 | catch (err) { 130 | t.ok(err, "Should fail if lonlats isn't an array of arrays"); 131 | } 132 | 133 | try { 134 | annotator.annotateRouteFromLonLats([[1,2]], (err, wayIds) => { 135 | t.fail("Should never get here"); 136 | }); 137 | t.fail("Should never get here"); 138 | } 139 | catch (err) { 140 | t.ok(err, "Should fail if not enough lonlats are supplied"); 141 | } 142 | 143 | try { 144 | annotator.annotateRouteFromLonLats([[1,2],[1]], (err, wayIds) => { 145 | t.fail("Should never get here"); 146 | }); 147 | t.fail("Should never get here"); 148 | } 149 | catch (err) { 150 | t.ok(err, "Should fail if not all lonlats are pairs (too short)"); 151 | } 152 | 153 | try { 154 | annotator.annotateRouteFromLonLats([[1,2],[1,2,3]], (err, wayIds) => { 155 | t.fail("Should never get here"); 156 | }); 157 | t.fail("Should never get here"); 158 | } 159 | catch (err) { 160 | t.ok(err, "Should fail if not all lonlats are pairs (too long)"); 161 | } 162 | 163 | try { 164 | annotator.annotateRouteFromLonLats([[1,2],[1,"2"]], (err, wayIds) => { 165 | t.fail("Should never get here"); 166 | }); 167 | t.fail("Should never get here"); 168 | } 169 | catch (err) { 170 | t.ok(err, "Should fail if not all lonlats are numeric values"); 171 | t.end(); 172 | } 173 | 174 | }); 175 | 176 | test('annotate by node', function(t) { 177 | //var nodes = [-120.1872774,48.4715898,-120.1882910,48.4725110]; 178 | //var nodes = [7.422155,43.7368838,7.4230139,43.7369751]; 179 | var nodes = [50253600,50253602,50137292]; 180 | annotator.annotateRouteFromNodeIds(nodes, (err, wayIds) => { 181 | if (err) throw err; 182 | t.same(wayIds, [0,0], "Verify got two hits on the first way"); 183 | annotator.getAllTagsForWayId(wayIds[0], (err, tags) => { 184 | if (err) throw err; 185 | t.equal(tags._way_id,'6091729',"Got correct _way_id attribute on match"); 186 | t.end(); 187 | }); 188 | }); 189 | }); 190 | 191 | test('annotate by coordinate', function(t) { 192 | var coords = [[-120.1872774,48.4715898],[-120.1882910,48.4725110],[0,0]]; 193 | annotator.annotateRouteFromLonLats(coords, (err, wayIds) => { 194 | if (err) throw err; 195 | t.same(wayIds, [0, null], "Got back the expected way IDs"); 196 | annotator.getAllTagsForWayId(wayIds[0], (err, tags) => { 197 | if (err) throw err; 198 | t.equal(tags._way_id,'6091729',"Got correct _way_id attribute on match"); 199 | t.end(); 200 | }); 201 | }); 202 | }); 203 | 204 | test('invalid get tags parameters', (t) => { 205 | try { 206 | annotator.getAllTagsForWayId("invalid", (err, wayIds) => { 207 | t.fail("Should never get here"); 208 | }); 209 | t.fail("Should never get here"); 210 | } 211 | catch (err) { 212 | t.ok("Should fail if a non-numeric way ID is supplied"); 213 | } 214 | 215 | try { 216 | annotator.getAllTagsForWayId(99); 217 | t.fail("Should never get here"); 218 | } 219 | catch (err) { 220 | t.ok("Should fail if a no callback is supplied"); 221 | } 222 | 223 | try { 224 | annotator.getAllTagsForWayId(99, 99); 225 | t.fail("Should never get here"); 226 | } 227 | catch (err) { 228 | t.ok("Should fail if second argument isn't a function"); 229 | t.end(); 230 | } 231 | 232 | 233 | }); 234 | 235 | test('SegmentSpeedLookup: initialization failure', function(t) { 236 | t.throws(bindings.SegmentSpeedLookup, /Cannot call constructor/, "Check that lookup can't be constructed without new"); 237 | t.end(); 238 | }); 239 | 240 | test('SegmentSpeedLookup: initialization failure with parameters', function(t) { 241 | t.throws(function() { var foo = new bindings.SegmentSpeedLookup("test"); }, /No types expected/, "Check that lookup can't be constructed with parameters"); 242 | t.end(); 243 | }); 244 | 245 | 246 | test('SegmentSpeedLookup: check CSV loading', function(t) { 247 | t.throws(function(cb) { 248 | var foo = new bindings.SegmentSpeedLookup(); 249 | foo.loadCSV(1,(err) => {cb(err);}); 250 | }, /and callback expected/, "Only understands strings and arrays of strings"); 251 | 252 | t.throws(function(cb) { 253 | var foo = new bindings.SegmentSpeedLookup(); 254 | foo.loadCSV("testfile.csv"); 255 | }, /and callback expected/, "Needs a callback function"); 256 | 257 | t.throws(function(cb) { 258 | var foo = new bindings.SegmentSpeedLookup(); 259 | foo.loadCSV([],(err) => {cb(err);}); 260 | }, /at least one filename/, "Doesn't know what to do with an empty array"); 261 | t.end(); 262 | }); 263 | 264 | test('SegmentSpeedLookup: missing single files', function(t) { 265 | var foo = new bindings.SegmentSpeedLookup(); 266 | foo.loadCSV("doesnotexist.csv",(err) => { 267 | t.ok(err, "Fails if the file does not exist"); 268 | t.end(); 269 | }); 270 | }); 271 | 272 | test('SegmentSpeedLookup: missing multifiles', function(t) { 273 | var foo = new bindings.SegmentSpeedLookup(); 274 | foo.loadCSV([path.join(__dirname,'congestion/fixtures/congestion.csv'), 275 | "doesnotexist.csv"],(err) => { 276 | t.ok(err, "Fails if any of the files in the list does not exist"); 277 | t.end(); 278 | }); 279 | }); 280 | 281 | test('SegmentSpeedLookup: load invalid parameters', (t) => { 282 | try { 283 | segmentmap.loadCSV(1234, (err) => { 284 | t.fail("Should never get here");; 285 | t.end(); 286 | }); 287 | t.fail("Should never get here");; 288 | } 289 | catch (err) { 290 | t.ok(err, "Fails if the first parameter isn't a string or array"); 291 | } 292 | 293 | try { 294 | segmentmap.loadCSV(1234, 1234, (err) => { 295 | t.fail("Should never get here");; 296 | t.end(); 297 | }); 298 | t.fail("Should never get here");; 299 | } 300 | catch (err) { 301 | t.ok(err, "Fails if the second parameter isn't a function"); 302 | t.end(); 303 | } 304 | 305 | }); 306 | 307 | test('SegmentSpeedLookup: load valid CSV', function(t) { 308 | segmentmap.loadCSV( 309 | [path.join(__dirname,'congestion/fixtures/congestion.csv'), 310 | path.join(__dirname,'congestion/fixtures/congestion2.csv')] 311 | , (err) => { 312 | if (err) throw err; 313 | t.end(); 314 | }); 315 | }); 316 | 317 | test('SegmentSpeedLookup: lookup with various invalid parameters', function(t) { 318 | try { 319 | segmentmap.getRouteSpeeds([86909066], (err, resp)=> { 320 | t.fail("Should never get here"); 321 | }); 322 | t.fail("Should never get here"); 323 | } 324 | catch (err) { 325 | t.ok(err, "Should fail if only one coordinate is passed"); 326 | } 327 | try { 328 | segmentmap.getRouteSpeeds(86909066, (err, resp)=> { 329 | t.fail("Should never get here"); 330 | }); 331 | t.fail("Should never get here"); 332 | } 333 | catch (err) { 334 | t.ok(err, "Should fail if first param isn't an array"); 335 | } 336 | 337 | try { 338 | segmentmap.getRouteSpeeds([86909066,86909064], 99, (err, resp)=> { 339 | t.fail("Should never get here"); 340 | }); 341 | t.fail("Should never get here"); 342 | } 343 | catch (err) { 344 | t.ok(err, "Should fail if more than 2 parameters passed"); 345 | } 346 | 347 | try { 348 | segmentmap.getRouteSpeeds([86909066,86909064], 99); 349 | t.fail("Should never get here"); 350 | } 351 | catch (err) { 352 | t.ok(err, "Should fail if second parameter isn't a function"); 353 | } 354 | 355 | try { 356 | segmentmap.getRouteSpeeds([86909066,"asdfasdf"], (err, resp)=> { 357 | t.fail("Should never get here"); 358 | }); 359 | t.fail("Should never get here"); 360 | } 361 | catch (err) { 362 | t.ok(err, "Should fail if a value in the node list isn't a number"); 363 | } 364 | 365 | try { 366 | segmentmap.getRouteSpeeds([86909066,-86909064], 99, (err, resp)=> { 367 | t.fail("Should never get here"); 368 | }); 369 | t.fail("Should never get here"); 370 | } 371 | catch (err) { 372 | t.ok(err, "Should fail if a value in the node list is negative"); 373 | t.end(); 374 | } 375 | 376 | }); 377 | 378 | test('SegmentSpeedLookup: lookup node pair', function(t) { 379 | segmentmap.getRouteSpeeds([86909066,86909064,86909066,999], (err, resp)=> { 380 | if (err) { console.log(err); throw err; } 381 | t.same(resp, [79,80,255], "Verify expected speed results"); 382 | t.end(); 383 | }); 384 | }); 385 | 386 | test('SegmentSpeedLookup: lookup node pair from second file', function(t) { 387 | segmentmap.getRouteSpeeds([90,91,92,93], (err, resp)=> { 388 | if (err) { console.log(err); throw err; } 389 | t.same(resp, [2,3,4], "Verify expected speed results"); 390 | t.end(); 391 | }); 392 | }); 393 | 394 | test('SegmentSpeedLookup: make sure that it works even if CSV is not loaded', function(t) { 395 | var speedlookup = new bindings.SegmentSpeedLookup(); 396 | speedlookup.getRouteSpeeds([90,91,92,93], (err, resp)=> { 397 | if (err) { console.log(err); throw err; } 398 | t.ok(resp, "Return results even if CSV is not loaded"); 399 | t.end(); 400 | }); 401 | }); 402 | 403 | test('SegmentSpeedLookup: dont load CSV and see if you get the correct response (3 INVALID_SPEEDs)', function(t) { 404 | var speedlookup = new bindings.SegmentSpeedLookup(); 405 | speedlookup.getRouteSpeeds([90,91,92,93], (err, resp)=> { 406 | if (err) { console.log(err); throw err; } 407 | t.same(resp, [ 255, 255, 255 ], "Verify expected speed results"); 408 | t.end(); 409 | }); 410 | }); 411 | 412 | test('annotator with invalid options object', function(t) { 413 | try { 414 | const coordsFalse = new bindings.Annotator(true); 415 | } 416 | catch(err) { 417 | t.ok(err, 'returns error with non-object options') 418 | t.end(); 419 | } 420 | }); 421 | 422 | test('annotator with non-boolean coordinates options', function(t) { 423 | try { 424 | const coordsFalse = new bindings.Annotator({ coordinates: 1}); 425 | } 426 | catch(err) { 427 | t.ok(err, 'returns error with unrecognized annotator options'); 428 | t.end(); 429 | } 430 | }); 431 | 432 | test('annotator with unrecognized options', function(t) { 433 | try { 434 | const coordsFalse = new bindings.Annotator({ coordinate: true }); 435 | } 436 | catch(err) { 437 | t.ok(err, 'returns error with unrecognized annotator options'); 438 | t.end(); 439 | } 440 | }); 441 | 442 | test('annotator without coordinates support - by default', function(t) { 443 | const coordsFalse = new bindings.Annotator(); 444 | coordsFalse.loadOSMExtract(path.join(__dirname,'data/winthrop.osm'), (err) => { 445 | if (err) throw err; 446 | var coords = [[-120.1872774,48.4715898],[-120.1882910,48.4725110]]; 447 | coordsFalse.annotateRouteFromLonLats(coords, (err) => { 448 | t.ok(err, 'returns error when annotator not built with coordinates support'); 449 | t.end(); 450 | }); 451 | }); 452 | }); 453 | 454 | test('annotator without coordinates support - explicit', function(t) { 455 | const coordsFalse = new bindings.Annotator({ coordinates: false }); 456 | coordsFalse.loadOSMExtract(path.join(__dirname,'data/winthrop.osm'), (err) => { 457 | if (err) throw err; 458 | var coords = [[-120.1872774,48.4715898],[-120.1882910,48.4725110]]; 459 | coordsFalse.annotateRouteFromLonLats(coords, (err) => { 460 | t.ok(err, 'returns error when annotator not built with coordinates support'); 461 | t.end(); 462 | }); 463 | }); 464 | }); 465 | 466 | test('WaySpeedLookup: initialization failure', function(t) { 467 | t.throws(bindings.WaySpeedLookup, /Cannot call constructor/, "Check that lookup can't be constructed without new"); 468 | t.end(); 469 | }); 470 | 471 | test('WaySpeedLookup: initialization failure with parameters', function(t) { 472 | t.throws(function() { var foo = new bindings.WaySpeedLookup("test"); }, /No types expected/, "Check that lookup can't be constructed with parameters"); 473 | t.end(); 474 | }); 475 | 476 | test('WaySpeedLookup: check CSV loading', function(t) { 477 | t.throws(function(cb) { 478 | var foo = new bindings.WaySpeedLookup(); 479 | foo.loadCSV(1,(err) => {cb(err);}); 480 | }, /and callback expected/, "Only understands strings and arrays of strings"); 481 | 482 | t.throws(function(cb) { 483 | var foo = new bindings.WaySpeedLookup(); 484 | foo.loadCSV("testfile.csv"); 485 | }, /and callback expected/, "Needs a callback function"); 486 | 487 | t.throws(function(cb) { 488 | var foo = new bindings.WaySpeedLookup(); 489 | foo.loadCSV([],(err) => {cb(err);}); 490 | }, /at least one filename/, "Doesn't know what to do with an empty array"); 491 | t.end(); 492 | }); 493 | 494 | test('WaySpeedLookup: missing single files', function(t) { 495 | var foo = new bindings.WaySpeedLookup(); 496 | foo.loadCSV("doesnotexist.csv",(err) => { 497 | t.ok(err, "Fails if the file does not exist"); 498 | t.end(); 499 | }); 500 | }); 501 | 502 | test('WaySpeedLookup: missing multifiles', function(t) { 503 | var foo = new bindings.WaySpeedLookup(); 504 | foo.loadCSV([path.join(__dirname,'wayspeeds/fixtures/way_speeds.csv'), 505 | "doesnotexist.csv"],(err) => { 506 | t.ok(err, "Fails if any of the files in the list does not exist"); 507 | t.end(); 508 | }); 509 | }); 510 | 511 | test('WaySpeedLookup: load invalid parameters', (t) => { 512 | try { 513 | waymap.loadCSV(1234, (err) => { 514 | t.fail("Should never get here");; 515 | t.end(); 516 | }); 517 | t.fail("Should never get here");; 518 | } 519 | catch (err) { 520 | t.ok(err, "Fails if the first parameter isn't a string or array"); 521 | } 522 | 523 | try { 524 | waymap.loadCSV(1234, 1234, (err) => { 525 | t.fail("Should never get here");; 526 | t.end(); 527 | }); 528 | t.fail("Should never get here");; 529 | } 530 | catch (err) { 531 | t.ok(err, "Fails if the second parameter isn't a function"); 532 | t.end(); 533 | } 534 | 535 | }); 536 | 537 | test('WaySpeedLookup: load valid CSV', function(t) { 538 | waymap.loadCSV( 539 | [path.join(__dirname,'wayspeeds/fixtures/way_speeds.csv'), 540 | path.join(__dirname,'wayspeeds/fixtures/way_speeds2.csv')] 541 | , (err) => { 542 | if (err) throw err; 543 | t.end(); 544 | }); 545 | }); 546 | 547 | test('WaySpeedLookup: lookup with various invalid parameters', function(t) { 548 | try { 549 | waymap.getRouteSpeeds([], (err, resp)=> { 550 | t.fail("Should never get here"); 551 | }); 552 | t.fail("Should never get here"); 553 | } 554 | catch (err) { 555 | t.ok(err, "Should fail if only one coordinate is passed"); 556 | } 557 | try { 558 | waymap.getRouteSpeeds(15665879, (err, resp)=> { 559 | t.fail("Should never get here"); 560 | }); 561 | t.fail("Should never get here"); 562 | } 563 | catch (err) { 564 | t.ok(err, "Should fail if first param isn't an array"); 565 | } 566 | 567 | try { 568 | waymap.getRouteSpeeds([301595694], 30, (err, resp)=> { 569 | t.fail("Should never get here"); 570 | }); 571 | t.fail("Should never get here"); 572 | } 573 | catch (err) { 574 | t.ok(err, "Should fail if more than 2 parameters passed"); 575 | } 576 | 577 | try { 578 | waymap.getRouteSpeeds([301595694], 30); 579 | t.fail("Should never get here"); 580 | } 581 | catch (err) { 582 | t.ok(err, "Should fail if second parameter isn't a function"); 583 | } 584 | 585 | try { 586 | waymap.getRouteSpeeds([301595694,"asdfasdf"], (err, resp)=> { 587 | t.fail("Should never get here"); 588 | }); 589 | t.fail("Should never get here"); 590 | } 591 | catch (err) { 592 | t.ok(err, "Should fail if a value in the node list isn't a number"); 593 | } 594 | 595 | try { 596 | waymap.getRouteSpeeds([301595694,-301595694], 30, (err, resp)=> { 597 | t.fail("Should never get here"); 598 | }); 599 | t.fail("Should never get here"); 600 | } 601 | catch (err) { 602 | t.ok(err, "Should fail if a value in the node list is negative"); 603 | t.end(); 604 | } 605 | 606 | }); 607 | 608 | test('WaySpeedLookup: lookup ways', function(t) { 609 | waymap.getRouteSpeeds([301595694,165499294,106817824,999], (err, resp)=> { 610 | if (err) { console.log(err); throw err; } 611 | t.same(resp, [30,70,113,255], "Verify expected speed results"); 612 | t.end(); 613 | }); 614 | }); 615 | 616 | test('WaySpeedLookup: lookup ways from second file', function(t) { 617 | waymap.getRouteSpeeds([45619838,173681583,171537086,999], (err, resp)=> { 618 | if (err) { console.log(err); throw err; } 619 | t.same(resp, [65,19,97,255], "Verify expected speed results"); 620 | t.end(); 621 | }); 622 | }); 623 | 624 | test('WaySpeedLookup: make sure that it works even if CSV is not loaded', function(t) { 625 | var waylookup = new bindings.WaySpeedLookup(); 626 | waylookup.getRouteSpeeds([6697274,11714049,6663351,6670232], (err, resp)=> { 627 | if (err) { console.log(err); throw err; } 628 | t.ok(resp, "Return results even if CSV is not loaded"); 629 | t.end(); 630 | }); 631 | }); 632 | 633 | test('WaySpeedLookup: dont load CSV and see if you get the correct response (4 INVALID_SPEEDs)', function(t) { 634 | var waylookup = new bindings.WaySpeedLookup(); 635 | waylookup.getRouteSpeeds([6697274,11714049,6663351,6670232], (err, resp)=> { 636 | if (err) { console.log(err); throw err; } 637 | t.same(resp, [ 255, 255, 255, 255 ], "Verify expected speed results"); 638 | t.end(); 639 | }); 640 | }); 641 | -------------------------------------------------------------------------------- /test/tags-filter.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tape'); 3 | const async = require('async'); 4 | 5 | const bindings = require('../index'); 6 | const MONACO_FILE = path.join(__dirname,'data/monaco.extract.osm'); 7 | const BALTIMORE_FILE = path.join(__dirname,'data/baltimore.osm.pbf'); 8 | const TAGS_FILE = path.join(__dirname, 'data/tags'); // data/tags => 'maxspeed' 9 | 10 | test('loadOSMExtract errors', function(t) { 11 | const annotator = new bindings.Annotator(); 12 | t.throws(function() { 13 | annotator.loadOSMExtract([], (err) => {}); 14 | }, 'Empty array of osm files'); 15 | t.throws(function() { 16 | annotator.loadOSMExtract([[MONACO_FILE]]); 17 | }, 'Nested array of files'); 18 | t.throws(function() { 19 | annotator.loadOSMExtract([path.join(__dirname,'data/paris.extract.osm.pbf'), [MONACO_FILE]]); 20 | }, 'Array containing first a string then another array'); 21 | t.throws(function() { 22 | annotator.loadOSMExtract('./fake-file'); 23 | }, 'Non-existent file'); 24 | t.throws(function() { 25 | annotator.loadOSMExtract(['./fake-file']); 26 | }, 'Non-existent file in an array'); 27 | t.throws(function() { 28 | annotator.loadOSMExtract([MONACO_FILE]); 29 | }, 'no callback provided'); 30 | t.throws(function() { 31 | annotator.loadOSMExtract((err) => {}); 32 | }, 'only callback is provided'); 33 | t.throws(function() { 34 | annotator.loadOSMExtract([MONACO_FILE], [TAGS_FILE], (err) => {}); 35 | }, 'tags file provided as array'); 36 | t.end(); 37 | }); 38 | 39 | test('load extract as string with tags', function(t) { 40 | const annotator = new bindings.Annotator({ coordinates: true }); 41 | annotator.loadOSMExtract([MONACO_FILE], TAGS_FILE, (err) => { 42 | if (err) throw err; 43 | t.end(); 44 | }); 45 | }); 46 | 47 | test('load multiple extracts in array with tags', function(t) { 48 | const annotator = new bindings.Annotator(); 49 | annotator.loadOSMExtract([MONACO_FILE, path.join(__dirname,'data/paris.extract.osm.pbf')], TAGS_FILE, (err) => { 50 | t.ifError(err); 51 | t.end(); 52 | }); 53 | }); 54 | 55 | test('load extract with tags', function(t) { 56 | // data/multiple-tags => 'maxspeed, tunnel' 57 | const annotator = new bindings.Annotator(); 58 | annotator.loadOSMExtract(MONACO_FILE, path.join(__dirname, 'data/multiple-tags'), (err) => { 59 | if (err) throw err; 60 | t.end(); 61 | }); 62 | }); 63 | 64 | test('only one nodeId in array', function(t) { 65 | const annotator = new bindings.Annotator(); 66 | var nodes = [2109571486]; 67 | t.throws(function() { annotator.annotateRouteFromNodeIds(nodes, (err, wayIds) => { })}); 68 | t.end(); 69 | }); 70 | 71 | test('annotate by node on data filtered by tag', function(t) { 72 | const annotator = new bindings.Annotator(); 73 | // data/multiple-tags => 'maxspeed, tunnel' 74 | annotator.loadOSMExtract(MONACO_FILE, path.join(__dirname, 'data/multiple-tags'), (err) => { 75 | t.error(err, 'Load monaco'); 76 | var q = async.queue((task, callback) => { 77 | annotator.annotateRouteFromNodeIds(task.nodes, (err, wayIds) => { 78 | t.error(err, 'No error annotating'); 79 | callback(wayIds); 80 | }); 81 | }); 82 | // Get annotations with tags maxspeed 83 | var NodesWithMaxspeed = {nodes: [1079045359,1079045454]}; 84 | q.push(NodesWithMaxspeed, (ids) => { 85 | t.equals(ids.length, 1, 'One way id returned'); 86 | annotator.getAllTagsForWayId(ids[0], (err, tags) => { 87 | t.error(err, 'No error'); 88 | t.ok(tags.maxspeed, 'Has maxspeed tag'); 89 | t.equal(tags.maxspeed, '50', 'Got correct maxspeed'); 90 | t.equal(tags._way_id, '93091314', 'Got correct _way_id attribute on match'); 91 | }); 92 | }); 93 | // Get annotations with tags tunnel 94 | var NodesWithTunnel = {nodes: [1347559127,1868280229,1868280238,1868280241]}; 95 | q.push(NodesWithTunnel, (ids) => { 96 | t.equals(ids.length, 3); 97 | annotator.getAllTagsForWayId(ids[0], (err, tags) => { 98 | console.log(tags); 99 | t.error(err, 'No error'); 100 | t.ok(tags.tunnel, 'Has tunnel tag'); 101 | // TODO: decide whether tag filter should limit what gets indexed 102 | // t.equals(tags.name, 'Tunnel Rocher Canton', 'has correct name'); 103 | t.equal(tags._way_id, '80378486', 'Got correct _way_id attribute on match'); 104 | }); 105 | }); 106 | // Get no annotations back 107 | var NodesWithoutMaxspeedOrTunnel = {nodes: [25193333,1204288453,3854490634]}; 108 | q.push(NodesWithoutMaxspeedOrTunnel, (ids) => { 109 | t.equals(ids.length, 2, 'One way id returned'); 110 | t.ok(ids.every(i => i == null), 'Way ids are null'); 111 | }); 112 | q.drain = () => { 113 | t.end(); 114 | }; 115 | }); 116 | }); 117 | 118 | test('annotate by node on data filtered by tag', function(t) { 119 | const annotator = new bindings.Annotator(); 120 | // data/multiple-tags => 'maxspeed, tunnel' 121 | annotator.loadOSMExtract(BALTIMORE_FILE, path.join(__dirname, 'data/multiple-tags'), (err) => { 122 | t.error(err, 'Load monaco'); 123 | var q = async.queue((task, callback) => { 124 | annotator.annotateRouteFromNodeIds(task.nodes, (err, wayIds) => { 125 | t.error(err, 'No error annotating'); 126 | callback(wayIds); 127 | }); 128 | }); 129 | // Get annotations with tags maxspeed 130 | var NodesWithMaxspeed = {nodes: [49461073,2494188158]}; 131 | q.push(NodesWithMaxspeed, (ids) => { 132 | t.equals(ids.length, 1, 'One way id returned'); 133 | annotator.getAllTagsForWayId(ids[0], (err, tags) => { 134 | t.error(err, 'No error'); 135 | t.ok(tags.maxspeed, 'Has maxspeed tag'); 136 | t.equal(tags.maxspeed, '40', 'Got correct maxspeed'); 137 | t.equal(tags._way_id, '326116174', 'Got correct _way_id attribute on match'); 138 | }); 139 | }); 140 | // Get annotations with tags maxspeed 141 | NodesWithMaxspeed = {nodes: [2719002521,2719002531]}; 142 | q.push(NodesWithMaxspeed, (ids) => { 143 | t.equals(ids.length, 1, 'One way id returned'); 144 | annotator.getAllTagsForWayId(ids[0], (err, tags) => { 145 | t.error(err, 'No error'); 146 | t.ok(tags.maxspeed, 'Has maxspeed tag'); 147 | t.equal(tags.maxspeed, '89', 'Got correct maxspeed'); 148 | t.equal(tags._way_id, '266323344', 'Got correct _way_id attribute on match'); 149 | }); 150 | }); 151 | // Get no annotations back 152 | var NodesWithoutMaxspeedOrTunnel = {nodes: [25193333,1204288453,3854490634]}; 153 | q.push(NodesWithoutMaxspeedOrTunnel, (ids) => { 154 | t.equals(ids.length, 2, 'One way id returned'); 155 | t.ok(ids.every(i => i == null), 'Way ids are null'); 156 | }); 157 | q.drain = () => { 158 | t.end(); 159 | }; 160 | }); 161 | }); 162 | 163 | -------------------------------------------------------------------------------- /test/way-speed-tests.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE wayspeed tests 2 | 3 | #include 4 | 5 | /* 6 | * This file will contain an automatically generated main function. 7 | */ 8 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/fewer_columns.csv: -------------------------------------------------------------------------------- 1 | 106817824,C8CAABb9SQHjAQA6ADIBAw==,mph 2 | 172938030,C8KBjxmEogHjAAAdAB8BAw==,mph,70 3 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 4 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 5 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 6 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 7 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 8 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 9 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 10 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,mph,40 11 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 12 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/header.csv: -------------------------------------------------------------------------------- 1 | way_id,openlr,unit,speed 2 | 106817824,C8CAABb9SQHjAQA6ADIBAw==,mph,70 3 | 172938030,C8KBjxmEogHjAAAdAB8BAw==,mph,70 4 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 5 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 6 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 7 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 8 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 9 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 10 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 11 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,mph,40 12 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 13 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/invalid_csv.csv: -------------------------------------------------------------------------------- 1 | 106817824,C8CAABb9SQHjAQA6ADIBAw==,mph:70 2 | 172938030,C8KBjxmEogHjAAAdAB8BAw==,mph,70 3 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 4 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 5 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 6 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 7 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 8 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 9 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 10 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,mph,40 11 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 12 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/more_columns.csv: -------------------------------------------------------------------------------- 1 | 106817824,C8CAABb9SQHjAQA6ADIBAw==,mph,70,C8KBjxmEogHjAAAdAB8BAw==,mph,70 2 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 3 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 4 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 5 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 6 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 7 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 8 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 9 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,mph,40 10 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 11 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/negative_number.csv: -------------------------------------------------------------------------------- 1 | 106817824,C8CAABb9SQHjAQA6ADIBAw==,mph,-70 2 | 172938030,C8KBjxmEogHjAAAdAB8BAw==,mph,70 3 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 4 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 5 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 6 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 7 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 8 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 9 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 10 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,mph,40 11 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 12 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/not_a_number.csv: -------------------------------------------------------------------------------- 1 | asdf,C8CAABb9SQHjAQA6ADIBAw==,mph,70 2 | 172938030,C8KBjxmEogHjAAAdAB8BAw==,mph,70 3 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 4 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 5 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 6 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 7 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 8 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 9 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 10 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,mph,40 11 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 12 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/string_input.csv: -------------------------------------------------------------------------------- 1 | 106817824,C8CAABb9SQHjAQA6ADIBAw==,mph,70 2 | 172938030,C8KBjxmEogHjAAAdAB8BAw==,mph,70 3 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 4 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 5 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 6 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 7 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 8 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 9 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 10 | "301595694",C8mD7hvvWwH/Af/+ADUBHw==,mph,40 11 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 12 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/way_speeds.csv: -------------------------------------------------------------------------------- 1 | 106817824,,mph,70 2 | 172938030,C8KBjxmEogHjAAAdAB8BAw==,mph,70 3 | 50324987,C7wzpxUcGwH2Dfz5/xYBFg==,mph,60 4 | 231738435,C8cdqBzDogHsBADL/0YBDA==,mph,40 5 | 106780378,C8BOdRXSZQHxAf/a/60BEQ==,mph,70 6 | 116748066,C8FZvhXk3AH+Cv9OAiABHg==,mph,65 7 | 495903221,C8F03xdbjAHkBQDuAMMBBA==,mph,70 8 | 406215748,C7nsuhT7vQHkBQEPAKIBBQ==,mph,30 9 | 35307314,C7VMgRxM2QH3Bv5b/8UBFg==,mph,65 10 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,mph,40 11 | 301595694,C8mD7hvvWwH/Af/+ADUBHw==,,30 12 | 37737776,C8cd7xyJxgH7AP/SABsBGw==,mph,55 13 | 49800696,C8mE7RvnWAH1G/tU+9oBFQ==,mph,55 14 | 68309369,C6qy7BwY0AHmBQGCAEMBBw==,mph,65 15 | 30445039,C6qrVRwYhAHkAgCQAE8BBQ==,mph,65 16 | 37833352,C7tUzBkpsgH4NvJf/+kBGA==,mph,70 17 | 90611936,C8YeBh0FcwHgAQAEADgBAA==,mph,55 18 | 34420709,C6hDDR5Q0wH9AP/mACUBHQ==,mph,65 19 | 40476324,C8JomRya2gH7KvghBjcBHA==,mph,70 20 | 40476284,C8I80RzH/gHuBgBw/qcBDg==,mph,70 21 | 128824479,C8R/Cx6bNQHvAwAF/y0BDw==,mph,70 22 | 49216123,C6vngRgPhAHsCAFG/rEBDA==,mph,65 23 | 171537086,C8RgUxfndgHnAgCKAAEBBw==,mph,70 24 | 165666090,C6wKJBztZQH1deIS940BFg==,mph,80 25 | 165499294,C8JxfByT1AH8AP/fACQBHA==,kph,70 26 | -------------------------------------------------------------------------------- /test/wayspeeds/fixtures/way_speeds2.csv: -------------------------------------------------------------------------------- 1 | 51369345,C7A00RvbagHjIQVIBXABAw==,mph,80 2 | 484971837,C73Ivxg8HgHyAP/x/+gBEg==,mph,70 3 | 18091525,C72joB8LygHwDf87/VQBEQ==,mph,70 4 | 122629120,C7n7NxT9YgH5Af+eABYBGQ==,mph,65 5 | 60867206,C7v4FxvFbwHhAQAXADcBAQ==,mph,60 6 | 589687901,C7w2ERnvzgH0AP/q//MBFA==,mph,75 7 | 88495140,C7u2jh2e+gHuAAAQ/90BDg==,mph,70 8 | 538616021,C8VNlhU+lQH/Bv/JAUIBHw==,mph,70 9 | 429788361,C8JxrxXZdgHnPw8zAHgBCA==,mph,70 10 | 45619838,C8Xk8RV4NQH3AP/WAAABFw==,,65 11 | 106571322,C8FEbhcwCAHxAf/Z/60BEQ==,mph,70 12 | 77914961,C8U8FhWBTgHoAAAw//8BCA==,mph,70 13 | 173681583,C8Lq/xkYuQHuAgAu/5IBDg==,kph,19 14 | 124652998,C8ScIhZZ4gH8CP7CAUYBHA==,mph,70 15 | 51257718,C7nz/RTZkAHnKgndAKkBCA==,mph,70 16 | 46976442,C8ZQ+xTO/wH+Av/PAIoBHg==,mph,65 17 | 405237655,C8VNfRU/KwH/Af/zAFQBHw==,mph,70 18 | 101831344,C82QSh5r4gHhAAAJABEBAQ==,mph,65 19 | 426659455,C8JBlhfauwH+Bf+UAQMBHg==,mph,60 20 | 80646391,C8A13h1uQwHnAQBdAAYBBw==,mph,65 21 | 406985857,C7niDBUNpwH+AP/9AAkBHg==,mph,70 22 | 46114800,C8RbKxfmDgHmAQBFABABBg==,mph,70 23 | 171537086,C8RgUxfndgHnAgCKAAEBBw==,mph,60 24 | 259154662,C7WEJxxuNAHjAAALAAsBAw==,mph,75 25 | -------------------------------------------------------------------------------- /test/wayspeeds/wayspeeds.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "way_speed_map.hpp" 7 | #include "types.hpp" 8 | 9 | BOOST_AUTO_TEST_SUITE(way_speeds_test) 10 | 11 | // Verify that the bearing-bounds checking function behaves as expected 12 | BOOST_AUTO_TEST_CASE(way_speeds_test_basic) 13 | { 14 | WaySpeedMap map("test/wayspeeds/fixtures/way_speeds.csv"); 15 | 16 | BOOST_CHECK_EQUAL(map.getValue(106817824), 113); 17 | BOOST_CHECK_EQUAL(map.hasKey(100), false); 18 | } 19 | 20 | BOOST_AUTO_TEST_CASE(way_speeds_test_many) 21 | { 22 | WaySpeedMap map("test/wayspeeds/fixtures/way_speeds.csv"); 23 | 24 | BOOST_CHECK_EQUAL(map.getValue(106817824), 113); 25 | BOOST_CHECK_EQUAL(map.getValue(231738435), 64); 26 | BOOST_CHECK_EQUAL(map.getValue(406215748), 48); 27 | BOOST_CHECK_EQUAL(map.getValue(301595694), 30); 28 | BOOST_CHECK_EQUAL(map.getValue(165499294), 70); 29 | BOOST_CHECK_EQUAL(map.getValue(49800696), 88); 30 | } 31 | 32 | BOOST_AUTO_TEST_CASE(way_speeds_test_get_values) 33 | { 34 | WaySpeedMap map("test/wayspeeds/fixtures/way_speeds.csv"); 35 | 36 | std::vector ways{106817824, 231738435, 406215748, 301595694, 165499294, 49800696}; 37 | std::vector speeds{113, 64, 48, 30, 70, 88}; 38 | std::vector response = map.getValues(ways); 39 | BOOST_CHECK_EQUAL_COLLECTIONS(response.begin(), response.end(), speeds.begin(), speeds.end()); 40 | } 41 | 42 | BOOST_AUTO_TEST_CASE(way_speeds_load_multiple) 43 | { 44 | std::vector ways; 45 | std::vector expected; 46 | std::vector actual; 47 | 48 | std::vector paths = {"test/wayspeeds/fixtures/way_speeds.csv", 49 | "test/wayspeeds/fixtures/way_speeds2.csv"}; 50 | WaySpeedMap map(paths); 51 | 52 | ways = {106817824, 231738435, 173681583, 45619838, 51369345, 171537086}; 53 | expected = {113, 64, 19, 65, 129, 97}; 54 | actual = map.getValues(ways); 55 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 56 | } 57 | 58 | BOOST_AUTO_TEST_CASE(way_speeds_load_incremental) 59 | { 60 | std::vector ways; 61 | std::vector expected; 62 | std::vector actual; 63 | 64 | std::vector paths = {"test/wayspeeds/fixtures/way_speeds.csv"}; 65 | WaySpeedMap map(paths); 66 | 67 | ways = {106817824, 231738435, 173681583, 45619838, 51369345, 171537086}; 68 | expected = {113, 64, INVALID_SPEED, INVALID_SPEED, INVALID_SPEED, 113}; 69 | actual = map.getValues(ways); 70 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 71 | 72 | map.loadCSV("test/wayspeeds/fixtures/way_speeds2.csv"); 73 | expected = {113, 64, 19, 65, 129, 97}; 74 | actual = map.getValues(ways); 75 | 76 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 77 | } 78 | 79 | BOOST_AUTO_TEST_CASE(way_speeds_test_speed_greater_than_max) 80 | { 81 | std::vector paths = {"test/wayspeeds/fixtures/way_speed_max.csv"}; 82 | WaySpeedMap map(paths); 83 | 84 | std::vector ways = {172938031,172938030}; 85 | std::vector expected = {159,INVALID_SPEED}; 86 | std::vector actual = map.getValues(ways); 87 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 88 | 89 | map.loadCSV("test/wayspeeds/fixtures/way_speed_max2.csv"); 90 | expected = {159, INVALID_SPEED}; //first import was not overwritten. 91 | actual = map.getValues(ways); 92 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 93 | } 94 | 95 | // Execptions 96 | BOOST_AUTO_TEST_CASE(way_speeds_test_string_input) 97 | { 98 | BOOST_CHECK_THROW(WaySpeedMap map("test/wayspeeds/fixtures/string_input.csv"), 99 | std::exception); 100 | } 101 | 102 | BOOST_AUTO_TEST_CASE(way_speeds_test_more_columns) 103 | { 104 | BOOST_CHECK_THROW(WaySpeedMap map("test/wayspeeds/fixtures/more_columns.csv"), 105 | std::exception); 106 | } 107 | 108 | BOOST_AUTO_TEST_CASE(way_speeds_test_fewer_columns) 109 | { 110 | BOOST_CHECK_THROW(WaySpeedMap map("test/wayspeeds/fixtures/fewer_columns.csv"), 111 | std::exception); 112 | } 113 | 114 | BOOST_AUTO_TEST_CASE(way_speeds_test_negative_number) 115 | { 116 | BOOST_CHECK_THROW(WaySpeedMap map("test/wayspeeds/fixtures/negative_number.csv"), 117 | std::exception); 118 | } 119 | 120 | BOOST_AUTO_TEST_CASE(way_speeds_test_invalid_csv) 121 | { 122 | BOOST_CHECK_THROW(WaySpeedMap map("test/wayspeeds/fixtures/invalid_csv.csv"), 123 | std::exception); 124 | } 125 | 126 | BOOST_AUTO_TEST_CASE(way_speeds_test_header) 127 | { 128 | BOOST_CHECK_THROW(WaySpeedMap map("test/wayspeeds/fixtures/header.csv"), std::exception); 129 | } 130 | 131 | BOOST_AUTO_TEST_CASE(way_speeds_test_not_a_number) 132 | { 133 | BOOST_CHECK_THROW(WaySpeedMap map("test/wayspeeds/fixtures/not_a_number.csv"), 134 | std::exception); 135 | } 136 | 137 | BOOST_AUTO_TEST_CASE(way_speeds_test_many_exceptions) 138 | { 139 | WaySpeedMap map("test/wayspeeds/fixtures/way_speeds.csv"); 140 | 141 | BOOST_CHECK_THROW(map.getValue(1), std::exception); 142 | BOOST_CHECK_THROW(map.getValue(3), std::exception); 143 | BOOST_CHECK_THROW(map.getValue(6988117), std::exception); 144 | } 145 | 146 | BOOST_AUTO_TEST_CASE(way_speeds_test_get_values_exceptions) 147 | { 148 | WaySpeedMap map("test/wayspeeds/fixtures/way_speeds.csv"); 149 | 150 | std::vector ways{}; 151 | BOOST_CHECK_THROW(map.getValues(ways), std::exception); 152 | 153 | ways = {1, 2, 3}; 154 | std::vector expected{INVALID_SPEED, INVALID_SPEED, INVALID_SPEED}; 155 | std::vector actual = map.getValues(ways); 156 | BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 157 | } 158 | 159 | BOOST_AUTO_TEST_CASE(way_speeds_test_has_key) 160 | { 161 | WaySpeedMap map("test/wayspeeds/fixtures/way_speeds.csv"); 162 | 163 | BOOST_CHECK_EQUAL(map.hasKey(106817824), true); 164 | BOOST_CHECK_EQUAL(map.hasKey(1), false); 165 | 166 | BOOST_CHECK_EQUAL(map.hasKey(51369345),false); 167 | map.loadCSV("test/wayspeeds/fixtures/way_speeds2.csv"); 168 | BOOST_CHECK_EQUAL(map.hasKey(51369345),true); 169 | } 170 | 171 | BOOST_AUTO_TEST_SUITE_END() 172 | --------------------------------------------------------------------------------