├── .babelrc ├── .github └── workflows │ ├── push-package.yml │ └── run-tests.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── css ├── images │ ├── images │ │ ├── layers-2x.png │ │ ├── layers.png │ │ ├── marker-icon-2x.png │ │ ├── marker-icon.png │ │ └── marker-shadow.png │ ├── layers-2x.png │ ├── layers.png │ ├── loading.gif │ ├── marker-icon-2x.png │ ├── marker-icon.png │ └── marker-shadow.png ├── leaflet.css └── style.css ├── dist └── graphhopper-client.js ├── img ├── included.png ├── marker-icon-green.png ├── marker-icon-red.png ├── marker-icon.png ├── screenshot-geocoding.png ├── screenshot-isochrone.png ├── screenshot-map-matching.png ├── screenshot-matrix.png ├── screenshot-routing.png └── screenshot-vrp.png ├── index.html ├── js ├── bouncemarker.js ├── demo.js ├── jquery-3.2.1.min.js ├── leaflet.js └── togeojson.js ├── map-matching-examples ├── bike.gpx └── car.gpx ├── package-lock.json ├── package.json ├── route-optimization-examples ├── american_road_trip.json ├── tsp_lonlat_end.json ├── tsp_lonlat_new.json ├── uk50.json └── vrp_lonlat_new.json ├── spec ├── GraphHopperGeocodingSpec.js ├── GraphHopperIsochroneSpec.js ├── GraphHopperMapMatchingSpec.js ├── GraphHopperMatrixSpec.js ├── GraphHopperOptimizationSpec.js ├── GraphHopperRoutingSpec.js ├── helpers │ └── config.js └── jasmine.json ├── src ├── GHInput.js ├── GHUtil.js ├── GraphHopperGeocoding.js ├── GraphHopperIsochrone.js ├── GraphHopperMapMatching.js ├── GraphHopperMatrix.js ├── GraphHopperOptimization.js ├── GraphHopperRouting.js └── main-template.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/push-package.yml: -------------------------------------------------------------------------------- 1 | name: Publish to GitHub Packages 2 | on: release 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: actions/setup-node@v4 10 | with: 11 | node-version: 20 12 | - run: npm install 13 | - run: export GHKEY=${{ secrets.GHKEY }}; npm test 14 | - uses: JS-DevTools/npm-publish@v3 15 | with: 16 | token: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | on: push 3 | 4 | jobs: 5 | tests: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: actions/setup-node@v4 10 | with: 11 | node-version: 20 12 | - run: npm install 13 | - run: export GHKEY=${{ secrets.GHKEY }}; npm test 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | *~ 4 | /nbproject/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea 4 | .idea/**/* -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.11.1 (2019-01-16) 2 | 3 | **Improvements** 4 | 5 | - Add more details to error messages ([#47](https://github.com/graphhopper/directions-api-js-client/issues/47) by [karussell](https://github.com/karussell)) 6 | 7 | 0.11 (2018-11-30) 8 | 9 | **Improvements** 10 | 11 | - Avoid sending local parameters ([#45](https://github.com/graphhopper/directions-api-js-client/issues/45) by [karussell](https://github.com/karussell)) 12 | 13 | 0.10 (2018-07-03) 14 | 15 | **API Changes** 16 | 17 | - The avoid parameter was changed from `avoid: [ 'motorway', 'toll' ]` to `avoid: 'motorway,toll'` ([#51](https://github.com/graphhopper/geocoder-converter/pull/51) by [Boldtrn](https://github.com/boldtrn)). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript client for the Directions API 2 | 3 | This project offers JavaScript clients for the [GraphHopper Directions API](https://www.graphhopper.com). 4 | 5 | ## Getting Started 6 | 7 | ### NPM 8 | 9 | Install the lib with npm: 10 | 11 | ```npm install graphhopper-js-api-client --save``` 12 | 13 | You can either require the whole client enabling you to use every GraphHopper API, but you can also only require the pieces you need. 14 | 15 | ```javascript 16 | require('graphhopper-js-api-client'); 17 | 18 | window.onload = function() { 19 | let defaultKey = "[Sign-up for free and get your own key: https://www.graphhopper.com/products/]"; 20 | let ghRouting = new GraphHopper.Routing({key: defaultKey}, {profile:"car", elevation: false}); 21 | 22 | ghRouting.doRequest({points:[[8.534317, 47.400905], [8.538265, 47.394108]]}) 23 | .then(json => { 24 | // Add your own result handling here 25 | console.log(json); 26 | }) 27 | .catch(err => { 28 | console.error(err.message); 29 | }); 30 | }; 31 | ``` 32 | 33 | ## Running Tests 34 | 35 | In order to run the tests, you have to register for a key on [GraphHopper](https://www.graphhopper.com/). 36 | Either set your key as environment variable using `export GHKEY=YOUR_KEY` or set your key in `spec/helpers/config.js`. 37 | You can run all tests via `npm test`. 38 | If you only want to run a single spec file, you can use the `--spec` option, e.g., `npm test --spec spec/GraphHopperRoutingSpec.js`. 39 | 40 | ## Integrate the APIs in your application 41 | 42 | You can either use our [bundled version](./dist/graphhopper-client.js), including all APIs or you can use only the 43 | pieces you need. 44 | 45 | ### GraphHopper Routing API 46 | 47 | ![GraphHopper Routing API screenshot](./img/screenshot-routing.png) 48 | 49 | You need [the routing client](./src/GraphHopperRouting.js). 50 | 51 | There is also a different client developed from the community [here](https://www.npmjs.com/package/lrm-graphhopper). 52 | 53 | ### GraphHopper Route Optimization API 54 | 55 | ![Route Optimization API screenshot](./img/screenshot-vrp.png) 56 | 57 | You need [the optimization client](./src/GraphHopperOptimization.js). 58 | 59 | ### GraphHopper Isochrone API 60 | 61 | ![GraphHopper Isochrone API screenshot](https://github.com/graphhopper/directions-api-js-client/blob/master/img/screenshot-isochrone.png) 62 | 63 | You need [the isochrone client](./src/GraphHopperIsochrone.js) 64 | 65 | ### GraphHopper Matrix API 66 | 67 | ![GraphHopper Matrix API screenshot](./img/screenshot-matrix.png) 68 | 69 | You need [the matrix client](./src/GraphHopperMatrix.js). 70 | 71 | ### GraphHopper Geocoding API 72 | 73 | ![GraphHopper Geocoding API screenshot](./img/screenshot-geocoding.png) 74 | 75 | You need [the geocoding client](./src/GraphHopperGeocoding.js). 76 | 77 | ### GraphHopper Map Matching API 78 | 79 | ![GraphHopper Map Matching API screenshot](./img/screenshot-map-matching.png) 80 | 81 | You need [the map matching client](./src/GraphHopperMapMatching.js) and the 82 | [togeojson.js](./js/togeojson.js) 83 | 84 | ## Releasing a new Version to NPM 85 | 86 | Set the version you like to publish in the `package.json`. Every version can only be published once and cannot be overwritten. 87 | 88 | Tag the commit you like to publish for example like this: 89 | ``` 90 | git log # get the commit hash of the commit you want to tag 91 | git tag 92 | git push origin --tag 93 | ``` 94 | 95 | GitHub will then build and publish the commit to NPM. 96 | 97 | ## License 98 | 99 | Code stands under Apache License 2.0 100 | -------------------------------------------------------------------------------- /css/images/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/images/layers-2x.png -------------------------------------------------------------------------------- /css/images/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/images/layers.png -------------------------------------------------------------------------------- /css/images/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/images/marker-icon-2x.png -------------------------------------------------------------------------------- /css/images/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/images/marker-icon.png -------------------------------------------------------------------------------- /css/images/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/images/marker-shadow.png -------------------------------------------------------------------------------- /css/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/layers-2x.png -------------------------------------------------------------------------------- /css/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/layers.png -------------------------------------------------------------------------------- /css/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/loading.gif -------------------------------------------------------------------------------- /css/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/marker-icon-2x.png -------------------------------------------------------------------------------- /css/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/marker-icon.png -------------------------------------------------------------------------------- /css/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/css/images/marker-shadow.png -------------------------------------------------------------------------------- /css/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 29 | .leaflet-safari .leaflet-tile { 30 | image-rendering: -webkit-optimize-contrast; 31 | } 32 | /* hack that prevents hw layers "stretching" when loading new tiles */ 33 | .leaflet-safari .leaflet-tile-container { 34 | width: 1600px; 35 | height: 1600px; 36 | -webkit-transform-origin: 0 0; 37 | } 38 | .leaflet-marker-icon, 39 | .leaflet-marker-shadow { 40 | display: block; 41 | } 42 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 43 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 44 | .leaflet-container .leaflet-overlay-pane svg, 45 | .leaflet-container .leaflet-marker-pane img, 46 | .leaflet-container .leaflet-shadow-pane img, 47 | .leaflet-container .leaflet-tile-pane img, 48 | .leaflet-container img.leaflet-image-layer { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | 53 | .leaflet-container.leaflet-touch-zoom { 54 | -ms-touch-action: pan-x pan-y; 55 | touch-action: pan-x pan-y; 56 | } 57 | .leaflet-container.leaflet-touch-drag { 58 | -ms-touch-action: pinch-zoom; 59 | /* Fallback for FF which doesn't support pinch-zoom */ 60 | touch-action: none; 61 | touch-action: pinch-zoom; 62 | } 63 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 64 | -ms-touch-action: none; 65 | touch-action: none; 66 | } 67 | .leaflet-container { 68 | -webkit-tap-highlight-color: transparent; 69 | } 70 | .leaflet-container a { 71 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 72 | } 73 | .leaflet-tile { 74 | filter: inherit; 75 | visibility: hidden; 76 | } 77 | .leaflet-tile-loaded { 78 | visibility: inherit; 79 | } 80 | .leaflet-zoom-box { 81 | width: 0; 82 | height: 0; 83 | -moz-box-sizing: border-box; 84 | box-sizing: border-box; 85 | z-index: 800; 86 | } 87 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 88 | .leaflet-overlay-pane svg { 89 | -moz-user-select: none; 90 | } 91 | 92 | .leaflet-pane { z-index: 400; } 93 | 94 | .leaflet-tile-pane { z-index: 200; } 95 | .leaflet-overlay-pane { z-index: 400; } 96 | .leaflet-shadow-pane { z-index: 500; } 97 | .leaflet-marker-pane { z-index: 600; } 98 | .leaflet-tooltip-pane { z-index: 650; } 99 | .leaflet-popup-pane { z-index: 700; } 100 | 101 | .leaflet-map-pane canvas { z-index: 100; } 102 | .leaflet-map-pane svg { z-index: 200; } 103 | 104 | .leaflet-vml-shape { 105 | width: 1px; 106 | height: 1px; 107 | } 108 | .lvml { 109 | behavior: url(#default#VML); 110 | display: inline-block; 111 | position: absolute; 112 | } 113 | 114 | 115 | /* control positioning */ 116 | 117 | .leaflet-control { 118 | position: relative; 119 | z-index: 800; 120 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 121 | pointer-events: auto; 122 | } 123 | .leaflet-top, 124 | .leaflet-bottom { 125 | position: absolute; 126 | z-index: 1000; 127 | pointer-events: none; 128 | } 129 | .leaflet-top { 130 | top: 0; 131 | } 132 | .leaflet-right { 133 | right: 0; 134 | } 135 | .leaflet-bottom { 136 | bottom: 0; 137 | } 138 | .leaflet-left { 139 | left: 0; 140 | } 141 | .leaflet-control { 142 | float: left; 143 | clear: both; 144 | } 145 | .leaflet-right .leaflet-control { 146 | float: right; 147 | } 148 | .leaflet-top .leaflet-control { 149 | margin-top: 10px; 150 | } 151 | .leaflet-bottom .leaflet-control { 152 | margin-bottom: 10px; 153 | } 154 | .leaflet-left .leaflet-control { 155 | margin-left: 10px; 156 | } 157 | .leaflet-right .leaflet-control { 158 | margin-right: 10px; 159 | } 160 | 161 | 162 | /* zoom and fade animations */ 163 | 164 | .leaflet-fade-anim .leaflet-tile { 165 | will-change: opacity; 166 | } 167 | .leaflet-fade-anim .leaflet-popup { 168 | opacity: 0; 169 | -webkit-transition: opacity 0.2s linear; 170 | -moz-transition: opacity 0.2s linear; 171 | -o-transition: opacity 0.2s linear; 172 | transition: opacity 0.2s linear; 173 | } 174 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 175 | opacity: 1; 176 | } 177 | .leaflet-zoom-animated { 178 | -webkit-transform-origin: 0 0; 179 | -ms-transform-origin: 0 0; 180 | transform-origin: 0 0; 181 | } 182 | .leaflet-zoom-anim .leaflet-zoom-animated { 183 | will-change: transform; 184 | } 185 | .leaflet-zoom-anim .leaflet-zoom-animated { 186 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 187 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 188 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 189 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 190 | } 191 | .leaflet-zoom-anim .leaflet-tile, 192 | .leaflet-pan-anim .leaflet-tile { 193 | -webkit-transition: none; 194 | -moz-transition: none; 195 | -o-transition: none; 196 | transition: none; 197 | } 198 | 199 | .leaflet-zoom-anim .leaflet-zoom-hide { 200 | visibility: hidden; 201 | } 202 | 203 | 204 | /* cursors */ 205 | 206 | .leaflet-interactive { 207 | cursor: pointer; 208 | } 209 | .leaflet-grab { 210 | cursor: -webkit-grab; 211 | cursor: -moz-grab; 212 | } 213 | .leaflet-crosshair, 214 | .leaflet-crosshair .leaflet-interactive { 215 | cursor: crosshair; 216 | } 217 | .leaflet-popup-pane, 218 | .leaflet-control { 219 | cursor: auto; 220 | } 221 | .leaflet-dragging .leaflet-grab, 222 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 223 | .leaflet-dragging .leaflet-marker-draggable { 224 | cursor: move; 225 | cursor: -webkit-grabbing; 226 | cursor: -moz-grabbing; 227 | } 228 | 229 | /* marker & overlays interactivity */ 230 | .leaflet-marker-icon, 231 | .leaflet-marker-shadow, 232 | .leaflet-image-layer, 233 | .leaflet-pane > svg path, 234 | .leaflet-tile-container { 235 | pointer-events: none; 236 | } 237 | 238 | .leaflet-marker-icon.leaflet-interactive, 239 | .leaflet-image-layer.leaflet-interactive, 240 | .leaflet-pane > svg path.leaflet-interactive { 241 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 242 | pointer-events: auto; 243 | } 244 | 245 | /* visual tweaks */ 246 | 247 | .leaflet-container { 248 | background: #ddd; 249 | outline: 0; 250 | } 251 | .leaflet-container a { 252 | color: #0078A8; 253 | } 254 | .leaflet-container a.leaflet-active { 255 | outline: 2px solid orange; 256 | } 257 | .leaflet-zoom-box { 258 | border: 2px dotted #38f; 259 | background: rgba(255,255,255,0.5); 260 | } 261 | 262 | 263 | /* general typography */ 264 | .leaflet-container { 265 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 266 | } 267 | 268 | 269 | /* general toolbar styles */ 270 | 271 | .leaflet-bar { 272 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 273 | border-radius: 4px; 274 | } 275 | .leaflet-bar a, 276 | .leaflet-bar a:hover { 277 | background-color: #fff; 278 | border-bottom: 1px solid #ccc; 279 | width: 26px; 280 | height: 26px; 281 | line-height: 26px; 282 | display: block; 283 | text-align: center; 284 | text-decoration: none; 285 | color: black; 286 | } 287 | .leaflet-bar a, 288 | .leaflet-control-layers-toggle { 289 | background-position: 50% 50%; 290 | background-repeat: no-repeat; 291 | display: block; 292 | } 293 | .leaflet-bar a:hover { 294 | background-color: #f4f4f4; 295 | } 296 | .leaflet-bar a:first-child { 297 | border-top-left-radius: 4px; 298 | border-top-right-radius: 4px; 299 | } 300 | .leaflet-bar a:last-child { 301 | border-bottom-left-radius: 4px; 302 | border-bottom-right-radius: 4px; 303 | border-bottom: none; 304 | } 305 | .leaflet-bar a.leaflet-disabled { 306 | cursor: default; 307 | background-color: #f4f4f4; 308 | color: #bbb; 309 | } 310 | 311 | .leaflet-touch .leaflet-bar a { 312 | width: 30px; 313 | height: 30px; 314 | line-height: 30px; 315 | } 316 | .leaflet-touch .leaflet-bar a:first-child { 317 | border-top-left-radius: 2px; 318 | border-top-right-radius: 2px; 319 | } 320 | .leaflet-touch .leaflet-bar a:last-child { 321 | border-bottom-left-radius: 2px; 322 | border-bottom-right-radius: 2px; 323 | } 324 | 325 | /* zoom control */ 326 | 327 | .leaflet-control-zoom-in, 328 | .leaflet-control-zoom-out { 329 | font: bold 18px 'Lucida Console', Monaco, monospace; 330 | text-indent: 1px; 331 | } 332 | 333 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 334 | font-size: 22px; 335 | } 336 | 337 | 338 | /* layers control */ 339 | 340 | .leaflet-control-layers { 341 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 342 | background: #fff; 343 | border-radius: 5px; 344 | } 345 | .leaflet-control-layers-toggle { 346 | background-image: url(images/layers.png); 347 | width: 36px; 348 | height: 36px; 349 | } 350 | .leaflet-retina .leaflet-control-layers-toggle { 351 | background-image: url(images/layers-2x.png); 352 | background-size: 26px 26px; 353 | } 354 | .leaflet-touch .leaflet-control-layers-toggle { 355 | width: 44px; 356 | height: 44px; 357 | } 358 | .leaflet-control-layers .leaflet-control-layers-list, 359 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 360 | display: none; 361 | } 362 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 363 | display: block; 364 | position: relative; 365 | } 366 | .leaflet-control-layers-expanded { 367 | padding: 6px 10px 6px 6px; 368 | color: #333; 369 | background: #fff; 370 | } 371 | .leaflet-control-layers-scrollbar { 372 | overflow-y: scroll; 373 | overflow-x: hidden; 374 | padding-right: 5px; 375 | } 376 | .leaflet-control-layers-selector { 377 | margin-top: 2px; 378 | position: relative; 379 | top: 1px; 380 | } 381 | .leaflet-control-layers label { 382 | display: block; 383 | } 384 | .leaflet-control-layers-separator { 385 | height: 0; 386 | border-top: 1px solid #ddd; 387 | margin: 5px -10px 5px -6px; 388 | } 389 | 390 | /* Default icon URLs */ 391 | .leaflet-default-icon-path { 392 | background-image: url(images/marker-icon.png); 393 | } 394 | 395 | 396 | /* attribution and scale controls */ 397 | 398 | .leaflet-container .leaflet-control-attribution { 399 | background: #fff; 400 | background: rgba(255, 255, 255, 0.7); 401 | margin: 0; 402 | } 403 | .leaflet-control-attribution, 404 | .leaflet-control-scale-line { 405 | padding: 0 5px; 406 | color: #333; 407 | } 408 | .leaflet-control-attribution a { 409 | text-decoration: none; 410 | } 411 | .leaflet-control-attribution a:hover { 412 | text-decoration: underline; 413 | } 414 | .leaflet-container .leaflet-control-attribution, 415 | .leaflet-container .leaflet-control-scale { 416 | font-size: 11px; 417 | } 418 | .leaflet-left .leaflet-control-scale { 419 | margin-left: 5px; 420 | } 421 | .leaflet-bottom .leaflet-control-scale { 422 | margin-bottom: 5px; 423 | } 424 | .leaflet-control-scale-line { 425 | border: 2px solid #777; 426 | border-top: none; 427 | line-height: 1.1; 428 | padding: 2px 5px 1px; 429 | font-size: 11px; 430 | white-space: nowrap; 431 | overflow: hidden; 432 | -moz-box-sizing: border-box; 433 | box-sizing: border-box; 434 | 435 | background: #fff; 436 | background: rgba(255, 255, 255, 0.5); 437 | } 438 | .leaflet-control-scale-line:not(:first-child) { 439 | border-top: 2px solid #777; 440 | border-bottom: none; 441 | margin-top: -2px; 442 | } 443 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 444 | border-bottom: 2px solid #777; 445 | } 446 | 447 | .leaflet-touch .leaflet-control-attribution, 448 | .leaflet-touch .leaflet-control-layers, 449 | .leaflet-touch .leaflet-bar { 450 | box-shadow: none; 451 | } 452 | .leaflet-touch .leaflet-control-layers, 453 | .leaflet-touch .leaflet-bar { 454 | border: 2px solid rgba(0,0,0,0.2); 455 | background-clip: padding-box; 456 | } 457 | 458 | 459 | /* popup */ 460 | 461 | .leaflet-popup { 462 | position: absolute; 463 | text-align: center; 464 | margin-bottom: 20px; 465 | } 466 | .leaflet-popup-content-wrapper { 467 | padding: 1px; 468 | text-align: left; 469 | border-radius: 12px; 470 | } 471 | .leaflet-popup-content { 472 | margin: 13px 19px; 473 | line-height: 1.4; 474 | } 475 | .leaflet-popup-content p { 476 | margin: 18px 0; 477 | } 478 | .leaflet-popup-tip-container { 479 | width: 40px; 480 | height: 20px; 481 | position: absolute; 482 | left: 50%; 483 | margin-left: -20px; 484 | overflow: hidden; 485 | pointer-events: none; 486 | } 487 | .leaflet-popup-tip { 488 | width: 17px; 489 | height: 17px; 490 | padding: 1px; 491 | 492 | margin: -10px auto 0; 493 | 494 | -webkit-transform: rotate(45deg); 495 | -moz-transform: rotate(45deg); 496 | -ms-transform: rotate(45deg); 497 | -o-transform: rotate(45deg); 498 | transform: rotate(45deg); 499 | } 500 | .leaflet-popup-content-wrapper, 501 | .leaflet-popup-tip { 502 | background: white; 503 | color: #333; 504 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 505 | } 506 | .leaflet-container a.leaflet-popup-close-button { 507 | position: absolute; 508 | top: 0; 509 | right: 0; 510 | padding: 4px 4px 0 0; 511 | border: none; 512 | text-align: center; 513 | width: 18px; 514 | height: 14px; 515 | font: 16px/14px Tahoma, Verdana, sans-serif; 516 | color: #c3c3c3; 517 | text-decoration: none; 518 | font-weight: bold; 519 | background: transparent; 520 | } 521 | .leaflet-container a.leaflet-popup-close-button:hover { 522 | color: #999; 523 | } 524 | .leaflet-popup-scrolled { 525 | overflow: auto; 526 | border-bottom: 1px solid #ddd; 527 | border-top: 1px solid #ddd; 528 | } 529 | 530 | .leaflet-oldie .leaflet-popup-content-wrapper { 531 | zoom: 1; 532 | } 533 | .leaflet-oldie .leaflet-popup-tip { 534 | width: 24px; 535 | margin: 0 auto; 536 | 537 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 538 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 539 | } 540 | .leaflet-oldie .leaflet-popup-tip-container { 541 | margin-top: -1px; 542 | } 543 | 544 | .leaflet-oldie .leaflet-control-zoom, 545 | .leaflet-oldie .leaflet-control-layers, 546 | .leaflet-oldie .leaflet-popup-content-wrapper, 547 | .leaflet-oldie .leaflet-popup-tip { 548 | border: 1px solid #999; 549 | } 550 | 551 | 552 | /* div icon */ 553 | 554 | .leaflet-div-icon { 555 | background: #fff; 556 | border: 1px solid #666; 557 | } 558 | 559 | 560 | /* Tooltip */ 561 | /* Base styles for the element that has a tooltip */ 562 | .leaflet-tooltip { 563 | position: absolute; 564 | padding: 6px; 565 | background-color: #fff; 566 | border: 1px solid #fff; 567 | border-radius: 3px; 568 | color: #222; 569 | white-space: nowrap; 570 | -webkit-user-select: none; 571 | -moz-user-select: none; 572 | -ms-user-select: none; 573 | user-select: none; 574 | pointer-events: none; 575 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 576 | } 577 | .leaflet-tooltip.leaflet-clickable { 578 | cursor: pointer; 579 | pointer-events: auto; 580 | } 581 | .leaflet-tooltip-top:before, 582 | .leaflet-tooltip-bottom:before, 583 | .leaflet-tooltip-left:before, 584 | .leaflet-tooltip-right:before { 585 | position: absolute; 586 | pointer-events: none; 587 | border: 6px solid transparent; 588 | background: transparent; 589 | content: ""; 590 | } 591 | 592 | /* Directions */ 593 | 594 | .leaflet-tooltip-bottom { 595 | margin-top: 6px; 596 | } 597 | .leaflet-tooltip-top { 598 | margin-top: -6px; 599 | } 600 | .leaflet-tooltip-bottom:before, 601 | .leaflet-tooltip-top:before { 602 | left: 50%; 603 | margin-left: -6px; 604 | } 605 | .leaflet-tooltip-top:before { 606 | bottom: 0; 607 | margin-bottom: -12px; 608 | border-top-color: #fff; 609 | } 610 | .leaflet-tooltip-bottom:before { 611 | top: 0; 612 | margin-top: -12px; 613 | margin-left: -6px; 614 | border-bottom-color: #fff; 615 | } 616 | .leaflet-tooltip-left { 617 | margin-left: -6px; 618 | } 619 | .leaflet-tooltip-right { 620 | margin-left: 6px; 621 | } 622 | .leaflet-tooltip-left:before, 623 | .leaflet-tooltip-right:before { 624 | top: 50%; 625 | margin-top: -6px; 626 | } 627 | .leaflet-tooltip-left:before { 628 | right: 0; 629 | margin-right: -12px; 630 | border-left-color: #fff; 631 | } 632 | .leaflet-tooltip-right:before { 633 | left: 0; 634 | margin-left: -12px; 635 | border-right-color: #fff; 636 | } 637 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | } 3 | .lead { 4 | font-size: 20px; 5 | } 6 | 7 | #main, #vrp-map-wrapper { 8 | margin: auto; 9 | width: 1000px; 10 | } 11 | 12 | #main li { 13 | line-height: 22px; 14 | } 15 | 16 | a { 17 | text-decoration: none; 18 | color: #3872f2; 19 | } 20 | a:hover { 21 | color: #3091f7; 22 | } 23 | 24 | #button-list { 25 | padding: 10px; 26 | } 27 | 28 | #button-list button, #example-list button { 29 | margin: 5px; 30 | } 31 | 32 | #example-list { 33 | float: left; 34 | max-width: 190px; 35 | margin-top: 10px; 36 | } 37 | 38 | #vrp-map { 39 | float: right; 40 | } 41 | 42 | .right { 43 | float: right; 44 | } 45 | 46 | .left { 47 | float: left; 48 | } 49 | 50 | .leaflet-div-icon { 51 | background: transparent; 52 | border: none; 53 | } 54 | 55 | .leaflet-marker-icon .number{ 56 | position: relative; 57 | top: -37px; 58 | font-size: 12px; 59 | width: 25px; 60 | text-align: center; 61 | } 62 | 63 | #enter_key { 64 | padding-top: 10px; 65 | float: right 66 | } 67 | #custom_key_input { 68 | width: 340px; 69 | } 70 | #custom_key_enabled { 71 | margin-bottom: -5px; 72 | } 73 | 74 | #vrp-error { 75 | color: red; 76 | font-weight: bold; 77 | } 78 | 79 | #vrp-error, #vrp-response { 80 | padding: 10px; 81 | } 82 | 83 | .clear { 84 | clear: both; 85 | } 86 | 87 | .hide { 88 | display: none; 89 | } 90 | 91 | #custom_key_desc li { 92 | list-style-type: none; 93 | padding-right: 20px; 94 | } 95 | 96 | #logo { 97 | padding-bottom: 20px; 98 | } 99 | 100 | #get_key_link { 101 | padding: 5px; 102 | } 103 | 104 | #instructions-header { 105 | padding: 10px; 106 | } 107 | 108 | #instructions { 109 | font-size: small; 110 | } 111 | 112 | .tab-content { 113 | padding-top: 20px; 114 | padding-bottom: 20px; 115 | } 116 | 117 | .small { 118 | font-size: small; 119 | } 120 | 121 | #mynavigation { 122 | border-top-color: white; 123 | background-color: white; 124 | margin-bottom: 0px; 125 | background-image: linear-gradient(to bottom, white, #e7e7e7); 126 | border-radius: 4px; 127 | font-size: 0; 128 | list-style-type: none; 129 | margin: 5px 0px; 130 | padding: 0; 131 | } 132 | 133 | #mynavigation li a { 134 | color: black; 135 | } 136 | 137 | #mynavigation li:first-child { 138 | border-left: medium none; 139 | border-radius: 4px 0 0 4px; 140 | } 141 | #mynavigation li { 142 | } 143 | #mynavigation li { 144 | border-left: 1px solid #ddd; 145 | border-right: 1px solid #888; 146 | cursor: pointer; 147 | display: inline-block; 148 | font-size: 16px; 149 | padding-left: 1.1em; 150 | padding-right: 1.1em; 151 | padding-bottom: 0.2em; 152 | text-shadow: 0 1px 1px #f7f7f7; 153 | } 154 | #mynavigation { 155 | list-style-type: none; 156 | } 157 | 158 | #mynavigation .current { 159 | border-bottom: 3px inset black; 160 | } 161 | 162 | #geocoding-map { 163 | margin-top: 10px; 164 | } -------------------------------------------------------------------------------- /img/included.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/included.png -------------------------------------------------------------------------------- /img/marker-icon-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/marker-icon-green.png -------------------------------------------------------------------------------- /img/marker-icon-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/marker-icon-red.png -------------------------------------------------------------------------------- /img/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/marker-icon.png -------------------------------------------------------------------------------- /img/screenshot-geocoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/screenshot-geocoding.png -------------------------------------------------------------------------------- /img/screenshot-isochrone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/screenshot-isochrone.png -------------------------------------------------------------------------------- /img/screenshot-map-matching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/screenshot-map-matching.png -------------------------------------------------------------------------------- /img/screenshot-matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/screenshot-matrix.png -------------------------------------------------------------------------------- /img/screenshot-routing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/screenshot-routing.png -------------------------------------------------------------------------------- /img/screenshot-vrp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphhopper/directions-api-js-client/5ee8691aa53c2c97ce5b60bb084eb190192d9028/img/screenshot-vrp.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JavaScript examples for the GraphHopper Directions API 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 | using custom API key 24 | 25 | 26 |
27 | 32 |
33 | 34 |

GraphHopper Directions API Live Examples

35 | 36 |
37 | 47 | 48 |
49 | 50 |
51 | The Routing API calculates the best path between two or more locations. 52 | Calculate your own route via clicking on the map and try 53 | GraphHopper Maps for a more advanced 54 | routing demo. 55 |
56 |
57 | 58 |
59 | 60 |
61 |
62 |
63 | 64 |
65 | 66 |
67 | Click to see instructions for route 68 |
69 |
70 | 71 |
72 | 73 |
74 |
75 | The Route Optimization API gets several locations and vehicles as input and calculates the best 76 | route for every of the vehicles, where several constraints like capacity or time windows can be added. 77 | Click on the map to add locations and then click 'optimize' or just on one of the examples. 78 | Or use our more advanced route editor in the dashboard. 79 |
80 | 81 |
82 | vehicles: 83 | 84 | 85 | 86 |
87 | 88 |
89 |
90 |
91 | 92 |
93 |
94 | 95 |
96 |
97 | 98 |
99 |
100 | 101 |
102 | 110 |
111 |
112 |
113 |
114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 |
122 | The Geocoding API converts address text into coordinates shown on the map or try "reverse geocoding" via clicking on the map. 123 |
124 |
125 | 126 | 127 |
128 | 129 |
130 |
131 |
132 |
133 | 134 |
135 | 136 |
137 |
138 | The Map Matching API snaps measured GPS tracks to the digital road network using 139 | a certain vehicle. Click on the examples and zoom to see the detailed GPX track (black line) 140 | and what the Map Matching API calculated from it (green line). 141 |
142 |
143 | 144 | 145 |
146 |
147 |
148 | 149 |
150 |
151 |
152 | 153 |
154 |
155 | 156 |
157 |
158 | The Matrix API calculates distances or times between many locations E.g. 1 to 5 or 4 to 4 like in the following example. 159 | Click on the produced links to see the actual route on GraphHopper Maps. 160 |
161 |
162 |
163 |
164 | 165 |
166 |
167 | 168 |
169 |
170 | 171 |
172 |
173 | 174 |
175 | 176 | 177 |
178 | 179 |
180 |
181 | 182 |
183 |
184 |
185 | 186 |
187 | 188 |
189 | The Isochrone API calculates the reach of a location. 190 | Calculate it by clicking on the map. 191 | The inner area is reachable within 5 minutes (green) and 192 | the outer area is reachable within 10 minutes (blue). 193 |
194 |
195 | 196 |
197 | 198 |
199 |
200 |
201 | 202 |
203 |
204 |
205 | 206 | 207 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /js/bouncemarker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Maxime Hadjinlian 3 | * All Rights Reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, 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 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | (function () { 29 | 30 | // Retain the value of the original onAdd and onRemove functions 31 | var originalOnAdd = L.Marker.prototype.onAdd; 32 | var originalOnRemove = L.Marker.prototype.onRemove; 33 | 34 | // Add bounceonAdd options 35 | L.Marker.mergeOptions({ 36 | bounceOnAdd: false, 37 | bounceOnAddOptions: { 38 | duration: 1000, 39 | height: -1 40 | }, 41 | bounceOnAddCallback: function() {} 42 | }); 43 | 44 | L.Marker.include({ 45 | 46 | _toPoint: function (latlng) { 47 | return this._map.latLngToContainerPoint(latlng); 48 | }, 49 | _toLatLng: function (point) { 50 | return this._map.containerPointToLatLng(point); 51 | }, 52 | 53 | _motionStep: function (opts) { 54 | var self = this; 55 | 56 | var start = new Date(); 57 | self._intervalId = setInterval(function () { 58 | var timePassed = new Date() - start; 59 | var progress = timePassed / opts.duration; 60 | if (progress > 1) { 61 | progress = 1; 62 | } 63 | var delta = opts.delta(progress); 64 | opts.step(delta); 65 | if (progress === 1) { 66 | opts.end(); 67 | clearInterval(self._intervalId); 68 | } 69 | }, opts.delay || 10); 70 | }, 71 | 72 | _bounceMotion: function (delta, duration, callback) { 73 | var original = L.latLng(this._origLatlng), 74 | start_y = this._dropPoint.y, 75 | start_x = this._dropPoint.x, 76 | distance = this._point.y - start_y; 77 | var self = this; 78 | 79 | this._motionStep({ 80 | delay: 10, 81 | duration: duration || 1000, // 1 sec by default 82 | delta: delta, 83 | step: function (delta) { 84 | self._dropPoint.y = 85 | start_y 86 | + (distance * delta) 87 | - (self._map.project(self._map.getCenter()).y - self._origMapCenter.y); 88 | self._dropPoint.x = 89 | start_x 90 | - (self._map.project(self._map.getCenter()).x - self._origMapCenter.x); 91 | self.setLatLng(self._toLatLng(self._dropPoint)); 92 | }, 93 | end: function () { 94 | self.setLatLng(original); 95 | if (typeof callback === "function") callback(); 96 | } 97 | }); 98 | }, 99 | 100 | // Many thanks to Robert Penner for this function 101 | _easeOutBounce: function (pos) { 102 | if ((pos) < (1 / 2.75)) { 103 | return (7.5625 * pos * pos); 104 | } else if (pos < (2 / 2.75)) { 105 | return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75); 106 | } else if (pos < (2.5 / 2.75)) { 107 | return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375); 108 | } else { 109 | return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375); 110 | } 111 | }, 112 | 113 | // Bounce : if options.height in pixels is not specified, drop from top. 114 | // If options.duration is not specified animation is 1s long. 115 | bounce: function(options, endCallback) { 116 | this._origLatlng = this.getLatLng(); 117 | this._bounce(options, endCallback); 118 | }, 119 | 120 | _bounce: function (options, endCallback) { 121 | if (typeof options === "function") { 122 | endCallback = options; 123 | options = null; 124 | } 125 | options = options || {duration: 1000, height: -1}; 126 | 127 | //backward compatibility 128 | if (typeof options === "number") { 129 | options.duration = arguments[0]; 130 | options.height = arguments[1]; 131 | } 132 | 133 | // Keep original map center 134 | this._origMapCenter = this._map.project(this._map.getCenter()); 135 | this._dropPoint = this._getDropPoint(options.height); 136 | this._bounceMotion(this._easeOutBounce, options.duration, endCallback); 137 | }, 138 | 139 | // This will get you a drop point given a height. 140 | // If no height is given, the top y will be used. 141 | _getDropPoint: function (height) { 142 | // Get current coordidates in pixel 143 | this._point = this._toPoint(this._origLatlng); 144 | var top_y; 145 | if (height === undefined || height < 0) { 146 | top_y = this._toPoint(this._map.getBounds()._northEast).y; 147 | } else { 148 | top_y = this._point.y - height; 149 | } 150 | return new L.Point(this._point.x, top_y); 151 | }, 152 | 153 | onAdd: function (map) { 154 | this._map = map; 155 | // Keep original latitude and longitude 156 | this._origLatlng = this._latlng; 157 | 158 | // We need to have our drop point BEFORE adding the marker to the map 159 | // otherwise, it would create a flicker. (The marker would appear at final 160 | // location then move to its drop location, and you may be able to see it.) 161 | if (this.options.bounceOnAdd === true) { 162 | // backward compatibility 163 | if (typeof this.options.bounceOnAddDuration !== 'undefined') { 164 | this.options.bounceOnAddOptions.duration = this.options.bounceOnAddDuration; 165 | } 166 | 167 | // backward compatibility 168 | if (typeof this.options.bounceOnAddHeight !== 'undefined') { 169 | this.options.bounceOnAddOptions.height = this.options.bounceOnAddHeight; 170 | } 171 | 172 | this._dropPoint = this._getDropPoint(this.options.bounceOnAddOptions.height); 173 | this.setLatLng(this._toLatLng(this._dropPoint)); 174 | } 175 | 176 | // Call leaflet original method to add the Marker to the map. 177 | originalOnAdd.call(this, map); 178 | 179 | if (this.options.bounceOnAdd === true) { 180 | this._bounce(this.options.bounceOnAddOptions, this.options.bounceOnAddCallback); 181 | } 182 | }, 183 | 184 | onRemove: function (map) { 185 | clearInterval(this._intervalId); 186 | originalOnRemove.call(this, map); 187 | } 188 | }); 189 | })(); 190 | -------------------------------------------------------------------------------- /js/demo.js: -------------------------------------------------------------------------------- 1 | let iconObject = L.icon({ 2 | iconUrl: './img/marker-icon.png', 3 | shadowSize: [50, 64], 4 | shadowAnchor: [4, 62], 5 | iconAnchor: [12, 40] 6 | }); 7 | 8 | $(document).ready(function (e) { 9 | jQuery.support.cors = true; 10 | 11 | $(".tab-content").css("display", "none"); 12 | $(".tabs-menu a").click(function (event) { 13 | // event.preventDefault(); 14 | showTab($(this)); 15 | }); 16 | 17 | function showTab(thisDiv) { 18 | thisDiv.parent().addClass("current"); 19 | thisDiv.parent().siblings().removeClass("current"); 20 | let tab = thisDiv.attr("href"); 21 | $(".tab-content").not(tab).css("display", "none"); 22 | $(tab).fadeIn(); 23 | 24 | // a bit hackish to refresh the map 25 | routingMap.invalidateSize(false); 26 | vrpMap.invalidateSize(false); 27 | geocodingMap.invalidateSize(false); 28 | isochroneMap.invalidateSize(false); 29 | mapMatchingMap.invalidateSize(false); 30 | } 31 | 32 | let host;// = "http://localhost:9000/api/1"; 33 | 34 | // 35 | // Sign-up for free and get your own key: https://graphhopper.com/#directions-api 36 | // 37 | let defaultKey = "9db0a28e-4851-433f-86c7-94b8a695fb18"; 38 | 39 | // create a routing client to fetch real routes, elevation.true is only supported for vehicle bike or foot 40 | let ghRouting = new GraphHopper.Routing({key: defaultKey, host: host}, {elevation: false}); 41 | let ghGeocoding = new GraphHopper.Geocoding({key: defaultKey, host: host}, 42 | {limit: 8, locale: "en" /* fr, en, de and it are supported */}); 43 | let ghMatrix = new GraphHopper.Matrix({key: defaultKey, host: host}); 44 | let ghOptimization = new GraphHopper.Optimization({key: defaultKey, host: host}); 45 | let ghIsochrone = new GraphHopper.Isochrone({key: defaultKey, host: host}); 46 | let ghMapMatching = new GraphHopper.MapMatching({key: defaultKey, host: host}); 47 | 48 | // if (location.protocol === "file:") { 49 | // ghOptimization.host = 'http://localhost:9000/api/1'; 50 | // ghOptimization.basePath = '/vrp'; 51 | // } 52 | 53 | let overwriteExistingKey = function () { 54 | let key = $("#custom_key_input").val(); 55 | if (key && key !== defaultKey) { 56 | $("#custom_key_enabled").show(); 57 | 58 | ghRouting.key = key; 59 | ghMatrix.key = key; 60 | ghGeocoding.key = key; 61 | ghOptimization.key = key; 62 | ghIsochrone.key = key; 63 | ghMapMatching.key = key; 64 | } else { 65 | $("#custom_key_enabled").hide(); 66 | } 67 | }; 68 | overwriteExistingKey(); 69 | $("#custom_key_button").click(overwriteExistingKey); 70 | 71 | let routingMap = createMap('routing-map'); 72 | setupRoutingAPI(routingMap, ghRouting); 73 | 74 | let vrpMap = createMap('vrp-map'); 75 | setupRouteOptimizationAPI(vrpMap, ghOptimization, ghRouting); 76 | 77 | let geocodingMap = createMap('geocoding-map'); 78 | setupGeocodingAPI(geocodingMap, ghGeocoding); 79 | 80 | setupMatrixAPI(ghMatrix); 81 | 82 | let isochroneMap = createMap('isochrone-map'); 83 | setupIsochrone(isochroneMap, ghIsochrone); 84 | 85 | let mapMatchingMap = createMap('map-matching-map'); 86 | setupMapMatching(mapMatchingMap, ghMapMatching); 87 | 88 | let tmpTab = window.location.hash; 89 | if (!tmpTab) 90 | tmpTab = "#routing"; 91 | 92 | showTab($(".tabs-menu li > a[href='" + tmpTab + "']")); 93 | }); 94 | 95 | function setupRoutingAPI(map, ghRouting) { 96 | map.setView([52.521235, 13.3992], 12); 97 | 98 | let points = [] 99 | let instructionsDiv = $("#instructions"); 100 | map.on('click', function (e) { 101 | if (points.length > 1) { 102 | points.length = 0; 103 | routingLayer.clearLayers(); 104 | } 105 | 106 | L.marker(e.latlng, {icon: iconObject}).addTo(routingLayer); 107 | 108 | points.push([e.latlng.lng, e.latlng.lat]); 109 | if (points.length > 1) { 110 | // ****************** 111 | // Calculate route! 112 | // ****************** 113 | ghRouting.doRequest({points: points}) 114 | .then(function (json) { 115 | let path = json.paths[0]; 116 | routingLayer.addData({ 117 | "type": "Feature", 118 | "geometry": path.points 119 | }); 120 | let outHtml = "Distance in meter:" + path.distance; 121 | outHtml += "
Times in seconds:" + path.time / 1000; 122 | $("#routing-response").html(outHtml); 123 | 124 | if (path.bbox) { 125 | let minLon = path.bbox[0]; 126 | let minLat = path.bbox[1]; 127 | let maxLon = path.bbox[2]; 128 | let maxLat = path.bbox[3]; 129 | let tmpB = new L.LatLngBounds(new L.LatLng(minLat, minLon), new L.LatLng(maxLat, maxLon)); 130 | map.fitBounds(tmpB); 131 | } 132 | 133 | instructionsDiv.empty(); 134 | if (path.instructions) { 135 | let allPoints = path.points.coordinates; 136 | let listUL = $("
    "); 137 | instructionsDiv.append(listUL); 138 | for (let idx in path.instructions) { 139 | let instr = path.instructions[idx]; 140 | 141 | // use 'interval' to find the geometry (list of points) until the next instruction 142 | let instruction_points = allPoints.slice(instr.interval[0], instr.interval[1]); 143 | 144 | // use 'sign' to display e.g. equally named images 145 | 146 | $("
  1. " + instr.text + " (" + ghRouting.getTurnText(instr.sign) + ")" 147 | + " for " + instr.distance + "m and " + Math.round(instr.time / 1000) + "sec" 148 | + ", geometry points:" + instruction_points.length + "
  2. ").appendTo(listUL); 149 | } 150 | } 151 | 152 | }) 153 | .catch(function (err) { 154 | let str = "An error occured: " + err.message; 155 | $("#routing-response").text(str); 156 | }); 157 | } 158 | }); 159 | 160 | let instructionsHeader = $("#instructions-header"); 161 | instructionsHeader.click(function () { 162 | instructionsDiv.toggle(); 163 | }); 164 | 165 | let routingLayer = L.geoJson().addTo(map); 166 | routingLayer.options = { 167 | style: {color: "#00cc33", "weight": 5, "opacity": 0.6} 168 | }; 169 | } 170 | 171 | function setupRouteOptimizationAPI(map, ghOptimization, ghRouting) { 172 | map.setView([51.505, -0.09], 13); 173 | let optPoints = []; 174 | 175 | L.NumberedDivIcon = L.Icon.extend({ 176 | options: { 177 | iconUrl: './img/marker-icon.png', 178 | number: '', 179 | shadowUrl: null, 180 | iconSize: new L.Point(25, 41), 181 | iconAnchor: new L.Point(13, 41), 182 | popupAnchor: new L.Point(0, -33), 183 | className: 'leaflet-div-icon' 184 | }, 185 | createIcon: function () { 186 | let div = document.createElement('div'); 187 | let img = this._createImg(this.options['iconUrl']); 188 | let numdiv = document.createElement('div'); 189 | numdiv.setAttribute("class", "number"); 190 | numdiv.innerHTML = this.options['number'] || ''; 191 | div.appendChild(img); 192 | div.appendChild(numdiv); 193 | this._setIconStyles(div, 'icon'); 194 | return div; 195 | }, 196 | // you could change this to add a shadow like in the normal marker if you really wanted 197 | createShadow: function () { 198 | return null; 199 | } 200 | }); 201 | 202 | let addPointToMap = function (lat, lng, index) { 203 | index = parseInt(index); 204 | if (index === 0) { 205 | new L.Marker([lat, lng], { 206 | icon: new L.NumberedDivIcon({iconUrl: './img/marker-icon-green.png', number: '1'}), 207 | bounceOnAdd: true, 208 | bounceOnAddOptions: {duration: 800, height: 200} 209 | }).addTo(routingLayer); 210 | } else { 211 | new L.Marker([lat, lng], { 212 | icon: new L.NumberedDivIcon({number: '' + (index + 1)}), 213 | bounceOnAdd: true, 214 | bounceOnAddOptions: {duration: 800, height: 200}, 215 | }).addTo(routingLayer); 216 | } 217 | }; 218 | 219 | map.on('click', function (e) { 220 | addPointToMap(e.latlng.lat, e.latlng.lng, optPoints.length); 221 | optPoints.push([e.latlng.lng, e.latlng.lat]); 222 | }); 223 | 224 | let routingLayer = L.geoJson().addTo(map); 225 | routingLayer.options.style = function (feature) { 226 | return feature.properties && feature.properties.style; 227 | }; 228 | 229 | let routePoints = []; 230 | 231 | let clearMap = function () { 232 | optPoints.length = 0; 233 | routingLayer.clearLayers(); 234 | routePoints.length = 0; 235 | $("#vrp-response").empty(); 236 | $("#vrp-error").empty(); 237 | }; 238 | 239 | let createSignupSteps = function () { 240 | return "
    To test this example
    " 241 | + "1. sign up for free,
    " 242 | + "2. log in and request a free standard package then
    " 243 | + "3. copy the API key to the text field in the upper right corner
    "; 244 | }; 245 | 246 | let getRouteStyle = function (routeIndex) { 247 | let routeStyle; 248 | if (routeIndex === 3) { 249 | routeStyle = {color: "cyan"}; 250 | } else if (routeIndex === 2) { 251 | routeStyle = {color: "black"}; 252 | } else if (routeIndex === 1) { 253 | routeStyle = {color: "green"}; 254 | } else { 255 | routeStyle = {color: "blue"}; 256 | } 257 | 258 | routeStyle.weight = 5; 259 | routeStyle.opacity = 1; 260 | return routeStyle; 261 | }; 262 | 263 | let createGHCallback = function (routeStyle) { 264 | return function (json) { 265 | for (let pathIndex = 0; pathIndex < json.paths.length; pathIndex++) { 266 | let path = json.paths[pathIndex]; 267 | routingLayer.addData({ 268 | "type": "Feature", 269 | "geometry": path.points, 270 | "properties": { 271 | style: routeStyle 272 | } 273 | }); 274 | } 275 | }; 276 | }; 277 | 278 | let optimizeError = function (err) { 279 | $("#vrp-response").text(" "); 280 | 281 | if (err.message.indexOf("Too many locations") >= 0) { 282 | $("#vrp-error").empty(); 283 | $("#vrp-error").append(createSignupSteps()); 284 | } else { 285 | $("#vrp-error").text("An error occured: " + err.message); 286 | } 287 | console.error(err); 288 | }; 289 | 290 | let optimizeResponse = function (json) { 291 | let sol = json.solution; 292 | if (!sol) 293 | return; 294 | 295 | $("#vrp-response").text("Solution found for " + sol.routes.length + " vehicle(s)! " 296 | + "Distance: " + Math.floor(sol.distance / 1000) + "km " 297 | + ", time: " + Math.floor(sol.time / 60) + "min " 298 | + ", costs: " + sol.costs); 299 | 300 | let no_unassigned = sol.unassigned.services.length + sol.unassigned.shipments.length; 301 | if (no_unassigned > 0) 302 | $("#vrp-error").append("
    unassigned jobs: " + no_unassigned); 303 | 304 | routingLayer.clearLayers(); 305 | for (let routeIndex = 0; routeIndex < sol.routes.length; routeIndex++) { 306 | let route = sol.routes[routeIndex]; 307 | 308 | // fetch real routes from graphhopper 309 | routePoints.length = 0; 310 | let firstAdd; 311 | for (let actIndex = 0; actIndex < route.activities.length; actIndex++) { 312 | let add = route.activities[actIndex].address; 313 | routePoints.push([add.lon, add.lat]); 314 | 315 | if (!eqAddress(firstAdd, add)) 316 | addPointToMap(add.lat, add.lon, actIndex); 317 | 318 | if (actIndex === 0) 319 | firstAdd = add; 320 | } 321 | 322 | let ghCallback = createGHCallback(getRouteStyle(routeIndex)); 323 | 324 | ghRouting.doRequest({points: routePoints, instructions: false}) 325 | .then(ghCallback) 326 | .catch(function (err) { 327 | let str = "An error for the routing occurred: " + err.message; 328 | $("#vrp-error").text(str); 329 | }); 330 | } 331 | }; 332 | 333 | let eqAddress = function (add1, add2) { 334 | return add1 && add2 335 | && Math.floor(add1.lat * 1000000) === Math.floor(add2.lat * 1000000) 336 | && Math.floor(add1.lon * 1000000) === Math.floor(add2.lon * 1000000); 337 | }; 338 | 339 | let optimizeRoute = function () { 340 | if (optPoints.length < 3) { 341 | $("#vrp-response").text("At least 3 points required but was: " + optPoints.length); 342 | return; 343 | } 344 | $("#vrp-response").text("Calculating ..."); 345 | ghOptimization.doVRPRequest(optPoints, $("#optimize_vehicles").val()) 346 | .then(optimizeResponse) 347 | .catch(optimizeError); 348 | }; 349 | 350 | $("#vrp_clear_button").click(clearMap); 351 | 352 | // Increase version if one of the examples change, see #2 353 | let exampleVersion = 2; 354 | 355 | $("#set_example_vrp").click(function () { 356 | $.getJSON("route-optimization-examples/vrp_lonlat_new.json?v=" + exampleVersion, function (jsonData) { 357 | 358 | clearMap(); 359 | map.setView([51, 10], 6); 360 | $("#vrp-response").text("Calculating ..."); 361 | ghOptimization.doRequest(jsonData) 362 | .then(optimizeResponse) 363 | .catch(optimizeError); 364 | }); 365 | }); 366 | 367 | $("#set_example_tsp").click(function () { 368 | $.getJSON("route-optimization-examples/tsp_lonlat_new.json?v=" + exampleVersion, function (jsonData) { 369 | 370 | clearMap(); 371 | map.setView([51, 10], 6); 372 | $("#vrp-response").text("Calculating ..."); 373 | ghOptimization.doRequest(jsonData) 374 | .then(optimizeResponse) 375 | .catch(optimizeError); 376 | }); 377 | }); 378 | 379 | $("#set_example_tsp2").click(function () { 380 | $.getJSON("route-optimization-examples/tsp_lonlat_end.json?v=" + exampleVersion, function (jsonData) { 381 | 382 | clearMap(); 383 | map.setView([51, 10], 6); 384 | $("#vrp-response").text("Calculating ..."); 385 | ghOptimization.doRequest(jsonData) 386 | .then(optimizeResponse) 387 | .catch(optimizeError); 388 | }); 389 | }); 390 | 391 | $("#set_example_us_tour").click(function () { 392 | $.getJSON("route-optimization-examples/american_road_trip.json?v=" + exampleVersion, function (jsonData) { 393 | 394 | clearMap(); 395 | map.setView([38.754083, -101.074219], 4); 396 | $("#vrp-response").text("Calculating ..."); 397 | ghOptimization.doRequest(jsonData) 398 | .then(optimizeResponse) 399 | .catch(optimizeError); 400 | }); 401 | }); 402 | 403 | $("#set_example_uk_tour").click(function () { 404 | $.getJSON("route-optimization-examples/uk50.json?v=" + exampleVersion, function (jsonData) { 405 | 406 | clearMap(); 407 | map.setView([54.136696, -4.592285], 6); 408 | $("#vrp-response").text("Calculating ..."); 409 | ghOptimization.doRequest(jsonData) 410 | .then(optimizeResponse) 411 | .catch(optimizeError); 412 | }); 413 | }); 414 | 415 | $("#optimize_button").click(optimizeRoute); 416 | } 417 | 418 | function setupGeocodingAPI(map, ghGeocoding) { 419 | // Find address 420 | map.setView([51.505, -0.09], 13); 421 | let iconObject = L.icon({ 422 | iconUrl: './img/marker-icon.png', 423 | shadowSize: [50, 64], 424 | shadowAnchor: [4, 62], 425 | iconAnchor: [12, 40] 426 | }); 427 | let geocodingLayer = L.geoJson().addTo(map); 428 | geocodingLayer.options = { 429 | style: {color: "#00cc33", "weight": 5, "opacity": 0.6} 430 | }; 431 | 432 | L.NumberedDivIcon = L.Icon.extend({ 433 | options: { 434 | iconUrl: './img/marker-icon.png', 435 | iconSize: new L.Point(25, 41), 436 | iconAnchor: new L.Point(13, 41), 437 | popupAnchor: new L.Point(0, -33), 438 | className: 'leaflet-div-icon' 439 | }, 440 | createIcon: function () { 441 | let div = document.createElement('div'); 442 | let img = this._createImg(this.options['iconUrl']); 443 | let numdiv = document.createElement('div'); 444 | numdiv.setAttribute("class", "number"); 445 | numdiv.innerHTML = this.options['number'] || ''; 446 | div.appendChild(img); 447 | div.appendChild(numdiv); 448 | this._setIconStyles(div, 'icon'); 449 | return div; 450 | } 451 | }); 452 | 453 | let clearGeocoding = function () { 454 | $("#geocoding-results").empty(); 455 | $("#geocoding-error").empty(); 456 | $("#geocoding-response").empty(); 457 | geocodingLayer.clearLayers(); 458 | }; 459 | 460 | let mysubmit = function () { 461 | clearGeocoding(); 462 | 463 | ghGeocoding.doRequest({query: textField.val()}) 464 | .then(function (json) { 465 | let listUL = $("
      "); 466 | $("#geocoding-response").append("Locale:" + ghGeocoding.locale + "
      ").append(listUL); 467 | let minLon, minLat, maxLon, maxLat; 468 | let counter = 0; 469 | for (let hitIdx in json.hits) { 470 | counter++; 471 | let hit = json.hits[hitIdx]; 472 | 473 | let str = counter + ". " + dataToText(hit); 474 | $("
      " + str + "
      ").appendTo(listUL); 475 | new L.Marker(hit.point, { 476 | icon: new L.NumberedDivIcon({iconUrl: './img/marker-icon-green.png', number: '' + counter}) 477 | }).bindPopup("
      " + str + "
      ").addTo(geocodingLayer); 478 | 479 | if (!minLat || minLat > hit.point.lat) 480 | minLat = hit.point.lat; 481 | if (!minLon || minLon > hit.point.lng) 482 | minLon = hit.point.lng; 483 | 484 | if (!maxLat || maxLat < hit.point.lat) 485 | maxLat = hit.point.lat; 486 | if (!maxLon || maxLon < hit.point.lng) 487 | maxLon = hit.point.lng; 488 | } 489 | 490 | if (minLat) { 491 | let tmpB = new L.LatLngBounds(new L.LatLng(minLat, minLon), new L.LatLng(maxLat, maxLon)); 492 | map.fitBounds(tmpB); 493 | } 494 | }) 495 | .catch(function (err) { 496 | $("#geocoding-error").text("An error occured: " + err.message); 497 | }); 498 | }; 499 | 500 | // reverse geocoding 501 | iconObject = L.icon({ 502 | iconUrl: './img/marker-icon.png', 503 | shadowSize: [50, 64], 504 | shadowAnchor: [4, 62], 505 | iconAnchor: [12, 40], 506 | popupAnchor: new L.Point(0, -33), 507 | }); 508 | map.on('click', function (e) { 509 | clearGeocoding(); 510 | 511 | ghGeocoding.doRequest({point: e.latlng.lat + "," + e.latlng.lng}) 512 | .then(function (json) { 513 | let counter = 0; 514 | for (let hitIdx in json.hits) { 515 | counter++; 516 | let hit = json.hits[hitIdx]; 517 | let str = counter + ". " + dataToText(hit); 518 | L.marker(hit.point, {icon: iconObject}).addTo(geocodingLayer).bindPopup(str).openPopup(); 519 | 520 | // only show first result for now 521 | break; 522 | } 523 | }) 524 | .catch(function (err) { 525 | $("#geocoding-error").text("An error occured: " + err.message); 526 | }); 527 | }); 528 | 529 | let textField = $("#geocoding_text_field"); 530 | textField.keypress(function (e) { 531 | if (e.which === 13) { 532 | mysubmit(); 533 | return false; 534 | } 535 | }); 536 | 537 | $("#geocoding_search_button").click(mysubmit); 538 | 539 | function dataToText(data) { 540 | let text = ""; 541 | if (data.name) 542 | text += data.name; 543 | 544 | if (data.postcode) 545 | text = insComma(text, data.postcode); 546 | 547 | // make sure name won't be duplicated 548 | if (data.city && text.indexOf(data.city) < 0) 549 | text = insComma(text, data.city); 550 | 551 | if (data.country && text.indexOf(data.country) < 0) 552 | text = insComma(text, data.country); 553 | return text; 554 | } 555 | 556 | function insComma(textA, textB) { 557 | if (textA.length > 0) 558 | return textA + ", " + textB; 559 | return textB; 560 | } 561 | } 562 | 563 | function setupMatrixAPI(ghMatrix) { 564 | $('#matrix_search_button').click(function () { 565 | 566 | // possible out_array options are: weights, distances, times, paths 567 | let request = {"out_arrays": ["distances", "times"]} 568 | request.points = [] 569 | $('.point').each(function (idx, div) { 570 | // parse the input strings and adds it as from_point and to_point 571 | let str = $(div).val() 572 | var index = str.indexOf(","); 573 | if (index >= 0) { 574 | let lat = parseFloat(str.substr(0, index)), 575 | lng = parseFloat(str.substr(index + 1)); 576 | request.points.push([lng, lat]); 577 | } 578 | 579 | // To create an NxM matrix you can simply use the other methods e.g. 580 | // ghm.addFromPoint(new GHInput(someCoordinateString)) 581 | // or 582 | // ghm.addToPoint(new GHInput(someCoordinateString)) 583 | }); 584 | 585 | $("#matrix-error").empty(); 586 | $("#matrix-response").empty(); 587 | 588 | ghMatrix.doRequest(request) 589 | .then(function (json) { 590 | let outHtml = "Distances in meters:
      " + ghMatrix.toHtmlTable(request, json.distances); 591 | outHtml += "

      Times in seconds:
      " + ghMatrix.toHtmlTable(request, json.times); 592 | $("#matrix-response").html(outHtml); 593 | }) 594 | .catch(function (err) { 595 | let str = "An error occured: " + err.message; 596 | $("#matrix-error").text(str); 597 | }); 598 | 599 | return false; 600 | }); 601 | } 602 | 603 | function setupIsochrone(map, ghIsochrone) { 604 | map.setView([37.44, -122.16], 12); 605 | let isochroneLayer; 606 | let inprogress = false; 607 | 608 | map.on('click', function (e) { 609 | let pointStr = e.latlng.lat + "," + e.latlng.lng; 610 | 611 | if (!inprogress) { 612 | inprogress = true; 613 | $('#isochrone-response').text("Calculating ..."); 614 | ghIsochrone.doRequest({point: pointStr, buckets: 2}) 615 | .then(function (json) { 616 | if (isochroneLayer) 617 | isochroneLayer.clearLayers(); 618 | 619 | isochroneLayer = L.geoJson(json.polygons, { 620 | style: function (feature) { 621 | let num = feature.properties.bucket; 622 | let color = (num % 2 === 0) ? "#00cc33" : "blue"; 623 | return {color: color, "weight": num + 2, "opacity": 0.6}; 624 | } 625 | }); 626 | 627 | map.addLayer(isochroneLayer); 628 | 629 | $('#isochrone-response').text("Calculation done"); 630 | inprogress = false; 631 | }) 632 | .catch(function (err) { 633 | inprogress = false; 634 | $('#isochrone-response').text("An error occured: " + err.message); 635 | }) 636 | ; 637 | } else { 638 | $('#isochrone-response').text("Please wait. Calculation in progress ..."); 639 | } 640 | }); 641 | } 642 | 643 | function createMap(divId) { 644 | let osmAttr = '© OpenStreetMap contributors'; 645 | 646 | let osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 647 | attribution: osmAttr 648 | }); 649 | 650 | let map = L.map(divId, {layers: [osm]}); 651 | L.control.layers({ 652 | "OpenStreetMap": osm 653 | }).addTo(map); 654 | return map; 655 | } 656 | 657 | function setupMapMatching(map, mmClient) { 658 | map.setView([50.9, 13.4], 9); 659 | let routeLayer = L.geoJson().addTo(map); 660 | routeLayer.options = { 661 | // use style provided by the 'properties' entry of the geojson added by addDataToRoutingLayer 662 | style: function (feature) { 663 | return feature.properties && feature.properties.style; 664 | } 665 | }; 666 | 667 | function mybind(key, url, profile) { 668 | $("#" + key).click(event => { 669 | $("#" + key).prop('disabled', true); 670 | $("#map-matching-response").text("downloading file ..."); 671 | fetch(url).then(response => { 672 | response.text().then((content) => { 673 | let dom = (new DOMParser()).parseFromString(content, 'text/xml'); 674 | let pathOriginal = toGeoJSON.gpx(dom); 675 | routeLayer.clearLayers(); 676 | pathOriginal.features[0].properties = {style: {color: "black", weight: 2, opacity: 0.9}}; 677 | routeLayer.addData(pathOriginal); 678 | $("#map-matching-response").text("send file ..."); 679 | $("#map-matching-error").text(""); 680 | mmClient.doRequest(content, {profile: profile}) 681 | .then(function (json) { 682 | $("#map-matching-response").text("calculated map matching for " + profile); 683 | let matchedPath = json.paths[0]; 684 | let geojsonFeature = { 685 | type: "Feature", 686 | geometry: matchedPath.points, 687 | properties: {style: {color: "#00cc33", weight: 6, opacity: 0.4}} 688 | }; 689 | routeLayer.addData(geojsonFeature); 690 | if (matchedPath.bbox) { 691 | let minLon = matchedPath.bbox[0]; 692 | let minLat = matchedPath.bbox[1]; 693 | let maxLon = matchedPath.bbox[2]; 694 | let maxLat = matchedPath.bbox[3]; 695 | let tmpB = new L.LatLngBounds(new L.LatLng(minLat, minLon), new L.LatLng(maxLat, maxLon)); 696 | map.fitBounds(tmpB); 697 | } 698 | $("#" + key).prop('disabled', false); 699 | }) 700 | .catch(function (err) { 701 | $("#map-matching-response").text(""); 702 | $("#map-matching-error").text(err.message); 703 | $("#" + key).prop('disabled', false); 704 | });//doRequest 705 | })// get 706 | }) 707 | });//click 708 | } 709 | 710 | mybind("bike_example1", "/map-matching-examples/bike.gpx", "bike"); 711 | mybind("car_example1", "/map-matching-examples/car.gpx", "car"); 712 | } 713 | -------------------------------------------------------------------------------- /js/togeojson.js: -------------------------------------------------------------------------------- 1 | var toGeoJSON = (function() { 2 | 'use strict'; 3 | 4 | var removeSpace = /\s*/g, 5 | trimSpace = /^\s*|\s*$/g, 6 | splitSpace = /\s+/; 7 | // generate a short, numeric hash of a string 8 | function okhash(x) { 9 | if (!x || !x.length) return 0; 10 | for (var i = 0, h = 0; i < x.length; i++) { 11 | h = ((h << 5) - h) + x.charCodeAt(i) | 0; 12 | } return h; 13 | } 14 | // all Y children of X 15 | function get(x, y) { return x.getElementsByTagName(y); } 16 | function attr(x, y) { return x.getAttribute(y); } 17 | function attrf(x, y) { return parseFloat(attr(x, y)); } 18 | // one Y child of X, if any, otherwise null 19 | function get1(x, y) { var n = get(x, y); return n.length ? n[0] : null; } 20 | // https://developer.mozilla.org/en-US/docs/Web/API/Node.normalize 21 | function norm(el) { if (el.normalize) { el.normalize(); } return el; } 22 | // cast array x into numbers 23 | function numarray(x) { 24 | for (var j = 0, o = []; j < x.length; j++) { o[j] = parseFloat(x[j]); } 25 | return o; 26 | } 27 | // get the content of a text node, if any 28 | function nodeVal(x) { 29 | if (x) { norm(x); } 30 | return (x && x.textContent) || ''; 31 | } 32 | // get the contents of multiple text nodes, if present 33 | function getMulti(x, ys) { 34 | var o = {}, n, k; 35 | for (k = 0; k < ys.length; k++) { 36 | n = get1(x, ys[k]); 37 | if (n) o[ys[k]] = nodeVal(n); 38 | } 39 | return o; 40 | } 41 | // add properties of Y to X, overwriting if present in both 42 | function extend(x, y) { for (var k in y) x[k] = y[k]; } 43 | // get one coordinate from a coordinate array, if any 44 | function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); } 45 | // get all coordinates from a coordinate array as [[],[]] 46 | function coord(v) { 47 | var coords = v.replace(trimSpace, '').split(splitSpace), 48 | o = []; 49 | for (var i = 0; i < coords.length; i++) { 50 | o.push(coord1(coords[i])); 51 | } 52 | return o; 53 | } 54 | function coordPair(x) { 55 | var ll = [attrf(x, 'lon'), attrf(x, 'lat')], 56 | ele = get1(x, 'ele'), 57 | // handle namespaced attribute in browser 58 | heartRate = get1(x, 'gpxtpx:hr') || get1(x, 'hr'), 59 | time = get1(x, 'time'), 60 | e; 61 | if (ele) { 62 | e = parseFloat(nodeVal(ele)); 63 | if (!isNaN(e)) { 64 | ll.push(e); 65 | } 66 | } 67 | return { 68 | coordinates: ll, 69 | time: time ? nodeVal(time) : null, 70 | heartRate: heartRate ? parseFloat(nodeVal(heartRate)) : null 71 | }; 72 | } 73 | 74 | // create a new feature collection parent object 75 | function fc() { 76 | return { 77 | type: 'FeatureCollection', 78 | features: [] 79 | }; 80 | } 81 | 82 | var serializer; 83 | if (typeof XMLSerializer !== 'undefined') { 84 | /* istanbul ignore next */ 85 | serializer = new XMLSerializer(); 86 | // only require xmldom in a node environment 87 | } else if (typeof exports === 'object' && typeof process === 'object' && !process.browser) { 88 | serializer = new (require('xmldom').XMLSerializer)(); 89 | } 90 | function xml2str(str) { 91 | // IE9 will create a new XMLSerializer but it'll crash immediately. 92 | // This line is ignored because we don't run coverage tests in IE9 93 | /* istanbul ignore next */ 94 | if (str.xml !== undefined) return str.xml; 95 | return serializer.serializeToString(str); 96 | } 97 | 98 | var t = { 99 | kml: function(doc) { 100 | 101 | var gj = fc(), 102 | // styleindex keeps track of hashed styles in order to match features 103 | styleIndex = {}, styleByHash = {}, 104 | // stylemapindex keeps track of style maps to expose in properties 105 | styleMapIndex = {}, 106 | // atomic geospatial types supported by KML - MultiGeometry is 107 | // handled separately 108 | geotypes = ['Polygon', 'LineString', 'Point', 'Track', 'gx:Track'], 109 | // all root placemarks in the file 110 | placemarks = get(doc, 'Placemark'), 111 | styles = get(doc, 'Style'), 112 | styleMaps = get(doc, 'StyleMap'); 113 | 114 | for (var k = 0; k < styles.length; k++) { 115 | var hash = okhash(xml2str(styles[k])).toString(16); 116 | styleIndex['#' + attr(styles[k], 'id')] = hash; 117 | styleByHash[hash] = styles[k]; 118 | } 119 | for (var l = 0; l < styleMaps.length; l++) { 120 | styleIndex['#' + attr(styleMaps[l], 'id')] = okhash(xml2str(styleMaps[l])).toString(16); 121 | var pairs = get(styleMaps[l], 'Pair'); 122 | var pairsMap = {}; 123 | for (var m = 0; m < pairs.length; m++) { 124 | pairsMap[nodeVal(get1(pairs[m], 'key'))] = nodeVal(get1(pairs[m], 'styleUrl')); 125 | } 126 | styleMapIndex['#' + attr(styleMaps[l], 'id')] = pairsMap; 127 | 128 | } 129 | for (var j = 0; j < placemarks.length; j++) { 130 | gj.features = gj.features.concat(getPlacemark(placemarks[j])); 131 | } 132 | function kmlColor(v) { 133 | var color, opacity; 134 | v = v || ''; 135 | if (v.substr(0, 1) === '#') { v = v.substr(1); } 136 | if (v.length === 6 || v.length === 3) { color = v; } 137 | if (v.length === 8) { 138 | opacity = parseInt(v.substr(0, 2), 16) / 255; 139 | color = '#' + v.substr(6, 2) + 140 | v.substr(4, 2) + 141 | v.substr(2, 2); 142 | } 143 | return [color, isNaN(opacity) ? undefined : opacity]; 144 | } 145 | function gxCoord(v) { return numarray(v.split(' ')); } 146 | function gxCoords(root) { 147 | var elems = get(root, 'coord', 'gx'), coords = [], times = []; 148 | if (elems.length === 0) elems = get(root, 'gx:coord'); 149 | for (var i = 0; i < elems.length; i++) coords.push(gxCoord(nodeVal(elems[i]))); 150 | var timeElems = get(root, 'when'); 151 | for (var j = 0; j < timeElems.length; j++) times.push(nodeVal(timeElems[j])); 152 | return { 153 | coords: coords, 154 | times: times 155 | }; 156 | } 157 | function getGeometry(root) { 158 | var geomNode, geomNodes, i, j, k, geoms = [], coordTimes = []; 159 | if (get1(root, 'MultiGeometry')) { return getGeometry(get1(root, 'MultiGeometry')); } 160 | if (get1(root, 'MultiTrack')) { return getGeometry(get1(root, 'MultiTrack')); } 161 | if (get1(root, 'gx:MultiTrack')) { return getGeometry(get1(root, 'gx:MultiTrack')); } 162 | for (i = 0; i < geotypes.length; i++) { 163 | geomNodes = get(root, geotypes[i]); 164 | if (geomNodes) { 165 | for (j = 0; j < geomNodes.length; j++) { 166 | geomNode = geomNodes[j]; 167 | if (geotypes[i] === 'Point') { 168 | geoms.push({ 169 | type: 'Point', 170 | coordinates: coord1(nodeVal(get1(geomNode, 'coordinates'))) 171 | }); 172 | } else if (geotypes[i] === 'LineString') { 173 | geoms.push({ 174 | type: 'LineString', 175 | coordinates: coord(nodeVal(get1(geomNode, 'coordinates'))) 176 | }); 177 | } else if (geotypes[i] === 'Polygon') { 178 | var rings = get(geomNode, 'LinearRing'), 179 | coords = []; 180 | for (k = 0; k < rings.length; k++) { 181 | coords.push(coord(nodeVal(get1(rings[k], 'coordinates')))); 182 | } 183 | geoms.push({ 184 | type: 'Polygon', 185 | coordinates: coords 186 | }); 187 | } else if (geotypes[i] === 'Track' || 188 | geotypes[i] === 'gx:Track') { 189 | var track = gxCoords(geomNode); 190 | geoms.push({ 191 | type: 'LineString', 192 | coordinates: track.coords 193 | }); 194 | if (track.times.length) coordTimes.push(track.times); 195 | } 196 | } 197 | } 198 | } 199 | return { 200 | geoms: geoms, 201 | coordTimes: coordTimes 202 | }; 203 | } 204 | function getPlacemark(root) { 205 | var geomsAndTimes = getGeometry(root), i, properties = {}, 206 | name = nodeVal(get1(root, 'name')), 207 | address = nodeVal(get1(root, 'address')), 208 | styleUrl = nodeVal(get1(root, 'styleUrl')), 209 | description = nodeVal(get1(root, 'description')), 210 | timeSpan = get1(root, 'TimeSpan'), 211 | timeStamp = get1(root, 'TimeStamp'), 212 | extendedData = get1(root, 'ExtendedData'), 213 | lineStyle = get1(root, 'LineStyle'), 214 | polyStyle = get1(root, 'PolyStyle'), 215 | visibility = get1(root, 'visibility'); 216 | 217 | if (!geomsAndTimes.geoms.length) return []; 218 | if (name) properties.name = name; 219 | if (address) properties.address = address; 220 | if (styleUrl) { 221 | if (styleUrl[0] !== '#') { 222 | styleUrl = '#' + styleUrl; 223 | } 224 | 225 | properties.styleUrl = styleUrl; 226 | if (styleIndex[styleUrl]) { 227 | properties.styleHash = styleIndex[styleUrl]; 228 | } 229 | if (styleMapIndex[styleUrl]) { 230 | properties.styleMapHash = styleMapIndex[styleUrl]; 231 | properties.styleHash = styleIndex[styleMapIndex[styleUrl].normal]; 232 | } 233 | // Try to populate the lineStyle or polyStyle since we got the style hash 234 | var style = styleByHash[properties.styleHash]; 235 | if (style) { 236 | if (!lineStyle) lineStyle = get1(style, 'LineStyle'); 237 | if (!polyStyle) polyStyle = get1(style, 'PolyStyle'); 238 | } 239 | } 240 | if (description) properties.description = description; 241 | if (timeSpan) { 242 | var begin = nodeVal(get1(timeSpan, 'begin')); 243 | var end = nodeVal(get1(timeSpan, 'end')); 244 | properties.timespan = { begin: begin, end: end }; 245 | } 246 | if (timeStamp) { 247 | properties.timestamp = nodeVal(get1(timeStamp, 'when')); 248 | } 249 | if (lineStyle) { 250 | var linestyles = kmlColor(nodeVal(get1(lineStyle, 'color'))), 251 | color = linestyles[0], 252 | opacity = linestyles[1], 253 | width = parseFloat(nodeVal(get1(lineStyle, 'width'))); 254 | if (color) properties.stroke = color; 255 | if (!isNaN(opacity)) properties['stroke-opacity'] = opacity; 256 | if (!isNaN(width)) properties['stroke-width'] = width; 257 | } 258 | if (polyStyle) { 259 | var polystyles = kmlColor(nodeVal(get1(polyStyle, 'color'))), 260 | pcolor = polystyles[0], 261 | popacity = polystyles[1], 262 | fill = nodeVal(get1(polyStyle, 'fill')), 263 | outline = nodeVal(get1(polyStyle, 'outline')); 264 | if (pcolor) properties.fill = pcolor; 265 | if (!isNaN(popacity)) properties['fill-opacity'] = popacity; 266 | if (fill) properties['fill-opacity'] = fill === '1' ? properties['fill-opacity'] || 1 : 0; 267 | if (outline) properties['stroke-opacity'] = outline === '1' ? properties['stroke-opacity'] || 1 : 0; 268 | } 269 | if (extendedData) { 270 | var datas = get(extendedData, 'Data'), 271 | simpleDatas = get(extendedData, 'SimpleData'); 272 | 273 | for (i = 0; i < datas.length; i++) { 274 | properties[datas[i].getAttribute('name')] = nodeVal(get1(datas[i], 'value')); 275 | } 276 | for (i = 0; i < simpleDatas.length; i++) { 277 | properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]); 278 | } 279 | } 280 | if (visibility) { 281 | properties.visibility = nodeVal(visibility); 282 | } 283 | if (geomsAndTimes.coordTimes.length) { 284 | properties.coordTimes = (geomsAndTimes.coordTimes.length === 1) ? 285 | geomsAndTimes.coordTimes[0] : geomsAndTimes.coordTimes; 286 | } 287 | var feature = { 288 | type: 'Feature', 289 | geometry: (geomsAndTimes.geoms.length === 1) ? geomsAndTimes.geoms[0] : { 290 | type: 'GeometryCollection', 291 | geometries: geomsAndTimes.geoms 292 | }, 293 | properties: properties 294 | }; 295 | if (attr(root, 'id')) feature.id = attr(root, 'id'); 296 | return [feature]; 297 | } 298 | return gj; 299 | }, 300 | gpx: function(doc) { 301 | var i, 302 | tracks = get(doc, 'trk'), 303 | routes = get(doc, 'rte'), 304 | waypoints = get(doc, 'wpt'), 305 | // a feature collection 306 | gj = fc(), 307 | feature; 308 | for (i = 0; i < tracks.length; i++) { 309 | feature = getTrack(tracks[i]); 310 | if (feature) gj.features.push(feature); 311 | } 312 | for (i = 0; i < routes.length; i++) { 313 | feature = getRoute(routes[i]); 314 | if (feature) gj.features.push(feature); 315 | } 316 | for (i = 0; i < waypoints.length; i++) { 317 | gj.features.push(getPoint(waypoints[i])); 318 | } 319 | function getPoints(node, pointname) { 320 | var pts = get(node, pointname), 321 | line = [], 322 | times = [], 323 | heartRates = [], 324 | l = pts.length; 325 | if (l < 2) return {}; // Invalid line in GeoJSON 326 | for (var i = 0; i < l; i++) { 327 | var c = coordPair(pts[i]); 328 | line.push(c.coordinates); 329 | if (c.time) times.push(c.time); 330 | if (c.heartRate) heartRates.push(c.heartRate); 331 | } 332 | return { 333 | line: line, 334 | times: times, 335 | heartRates: heartRates 336 | }; 337 | } 338 | function getTrack(node) { 339 | var segments = get(node, 'trkseg'), 340 | track = [], 341 | times = [], 342 | heartRates = [], 343 | line; 344 | for (var i = 0; i < segments.length; i++) { 345 | line = getPoints(segments[i], 'trkpt'); 346 | if (line) { 347 | if (line.line) track.push(line.line); 348 | if (line.times && line.times.length) times.push(line.times); 349 | if (line.heartRates && line.heartRates.length) heartRates.push(line.heartRates); 350 | } 351 | } 352 | if (track.length === 0) return; 353 | var properties = getProperties(node); 354 | extend(properties, getLineStyle(get1(node, 'extensions'))); 355 | if (times.length) properties.coordTimes = track.length === 1 ? times[0] : times; 356 | if (heartRates.length) properties.heartRates = track.length === 1 ? heartRates[0] : heartRates; 357 | return { 358 | type: 'Feature', 359 | properties: properties, 360 | geometry: { 361 | type: track.length === 1 ? 'LineString' : 'MultiLineString', 362 | coordinates: track.length === 1 ? track[0] : track 363 | } 364 | }; 365 | } 366 | function getRoute(node) { 367 | var line = getPoints(node, 'rtept'); 368 | if (!line.line) return; 369 | var prop = getProperties(node); 370 | extend(prop, getLineStyle(get1(node, 'extensions'))); 371 | var routeObj = { 372 | type: 'Feature', 373 | properties: prop, 374 | geometry: { 375 | type: 'LineString', 376 | coordinates: line.line 377 | } 378 | }; 379 | return routeObj; 380 | } 381 | function getPoint(node) { 382 | var prop = getProperties(node); 383 | extend(prop, getMulti(node, ['sym'])); 384 | return { 385 | type: 'Feature', 386 | properties: prop, 387 | geometry: { 388 | type: 'Point', 389 | coordinates: coordPair(node).coordinates 390 | } 391 | }; 392 | } 393 | function getLineStyle(extensions) { 394 | var style = {}; 395 | if (extensions) { 396 | var lineStyle = get1(extensions, 'line'); 397 | if (lineStyle) { 398 | var color = nodeVal(get1(lineStyle, 'color')), 399 | opacity = parseFloat(nodeVal(get1(lineStyle, 'opacity'))), 400 | width = parseFloat(nodeVal(get1(lineStyle, 'width'))); 401 | if (color) style.stroke = color; 402 | if (!isNaN(opacity)) style['stroke-opacity'] = opacity; 403 | // GPX width is in mm, convert to px with 96 px per inch 404 | if (!isNaN(width)) style['stroke-width'] = width * 96 / 25.4; 405 | } 406 | } 407 | return style; 408 | } 409 | function getProperties(node) { 410 | var prop = getMulti(node, ['name', 'cmt', 'desc', 'type', 'time', 'keywords']), 411 | links = get(node, 'link'); 412 | if (links.length) prop.links = []; 413 | for (var i = 0, link; i < links.length; i++) { 414 | link = { href: attr(links[i], 'href') }; 415 | extend(link, getMulti(links[i], ['text', 'type'])); 416 | prop.links.push(link); 417 | } 418 | return prop; 419 | } 420 | return gj; 421 | } 422 | }; 423 | return t; 424 | })(); 425 | 426 | if (typeof module !== 'undefined') module.exports = toGeoJSON; -------------------------------------------------------------------------------- /map-matching-examples/car.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sendl.-O'sch-heim 5 | 6 | Sendl.-O'sch-heim on GPSies.com 7 | trackOnWeb 8 | 9 | 10 | 11 | one-way trip 12 | 20491.0304651953 13 | 74.0 14 | 120.0 15 | 481.0 16 | 540.0 17 | 18 | 19 | 20 | Sendl.-O'sch-heim on GPSies.com 21 | 22 | trackOnWeb 23 | 24 | 25 | elevationChartUrlMap 26 | 27 | 28 | elevationChartUrlMapThumb 29 | 30 | 31 | elevationChartUrlTab 32 | 33 | 34 | 35 | 528.00000 36 | 37 | 38 | 39 | 526.00000 40 | 41 | 42 | 43 | 527.00000 44 | 45 | 46 | 47 | 531.00000 48 | 49 | 50 | 51 | 532.00000 52 | 53 | 54 | 55 | 530.00000 56 | 57 | 58 | 59 | 534.00000 60 | 61 | 62 | 63 | 533.00000 64 | 65 | 66 | 67 | 537.00000 68 | 69 | 70 | 71 | 537.00000 72 | 73 | 74 | 75 | 537.00000 76 | 77 | 78 | 79 | 537.00000 80 | 81 | 82 | 83 | 537.00000 84 | 85 | 86 | 87 | 537.00000 88 | 89 | 90 | 91 | 539.00000 92 | 93 | 94 | 95 | 539.00000 96 | 97 | 98 | 99 | 540.00000 100 | 101 | 102 | 103 | 538.00000 104 | 105 | 106 | 107 | 537.00000 108 | 109 | 110 | 111 | 534.00000 112 | 113 | 114 | 115 | 534.00000 116 | 117 | 118 | 119 | 534.00000 120 | 121 | 122 | 123 | 533.00000 124 | 125 | 126 | 127 | 533.00000 128 | 129 | 130 | 131 | 532.00000 132 | 133 | 134 | 135 | 532.00000 136 | 137 | 138 | 139 | 530.00000 140 | 141 | 142 | 143 | 531.00000 144 | 145 | 146 | 147 | 527.00000 148 | 149 | 150 | 151 | 527.00000 152 | 153 | 154 | 155 | 527.00000 156 | 157 | 158 | 159 | 524.00000 160 | 161 | 162 | 163 | 525.00000 164 | 165 | 166 | 167 | 527.00000 168 | 169 | 170 | 171 | 528.00000 172 | 173 | 174 | 175 | 530.00000 176 | 177 | 178 | 179 | 533.00000 180 | 181 | 182 | 183 | 532.00000 184 | 185 | 186 | 187 | 531.00000 188 | 189 | 190 | 191 | 533.00000 192 | 193 | 194 | 195 | 527.00000 196 | 197 | 198 | 199 | 523.00000 200 | 201 | 202 | 203 | 524.00000 204 | 205 | 206 | 207 | 524.00000 208 | 209 | 210 | 211 | 525.00000 212 | 213 | 214 | 215 | 523.00000 216 | 217 | 218 | 219 | 523.00000 220 | 221 | 222 | 223 | 525.00000 224 | 225 | 226 | 227 | 522.00000 228 | 229 | 230 | 231 | 521.00000 232 | 233 | 234 | 235 | 523.00000 236 | 237 | 238 | 239 | 520.00000 240 | 241 | 242 | 243 | 526.00000 244 | 245 | 246 | 247 | 514.00000 248 | 249 | 250 | 251 | 511.00000 252 | 253 | 254 | 255 | 512.00000 256 | 257 | 258 | 259 | 515.00000 260 | 261 | 262 | 263 | 516.00000 264 | 265 | 266 | 267 | 513.00000 268 | 269 | 270 | 271 | 510.00000 272 | 273 | 274 | 275 | 513.00000 276 | 277 | 278 | 279 | 515.00000 280 | 281 | 282 | 283 | 513.00000 284 | 285 | 286 | 287 | 512.00000 288 | 289 | 290 | 291 | 509.00000 292 | 293 | 294 | 295 | 507.00000 296 | 297 | 298 | 299 | 506.00000 300 | 301 | 302 | 303 | 508.00000 304 | 305 | 306 | 307 | 506.00000 308 | 309 | 310 | 311 | 504.00000 312 | 313 | 314 | 315 | 505.00000 316 | 317 | 318 | 319 | 504.00000 320 | 321 | 322 | 323 | 503.00000 324 | 325 | 326 | 327 | 503.00000 328 | 329 | 330 | 331 | 503.00000 332 | 333 | 334 | 335 | 503.00000 336 | 337 | 338 | 339 | 504.00000 340 | 341 | 342 | 343 | 504.00000 344 | 345 | 346 | 347 | 504.00000 348 | 349 | 350 | 351 | 503.00000 352 | 353 | 354 | 355 | 506.00000 356 | 357 | 358 | 359 | 505.00000 360 | 361 | 362 | 363 | 504.00000 364 | 365 | 366 | 367 | 504.00000 368 | 369 | 370 | 371 | 503.00000 372 | 373 | 374 | 375 | 501.00000 376 | 377 | 378 | 379 | 500.00000 380 | 381 | 382 | 383 | 500.00000 384 | 385 | 386 | 387 | 500.00000 388 | 389 | 390 | 391 | 500.00000 392 | 393 | 394 | 395 | 500.00000 396 | 397 | 398 | 399 | 500.00000 400 | 401 | 402 | 403 | 500.00000 404 | 405 | 406 | 407 | 500.00000 408 | 409 | 410 | 411 | 499.00000 412 | 413 | 414 | 415 | 498.00000 416 | 417 | 418 | 419 | 496.00000 420 | 421 | 422 | 423 | 494.00000 424 | 425 | 426 | 427 | 493.00000 428 | 429 | 430 | 431 | 494.00000 432 | 433 | 434 | 435 | 494.00000 436 | 437 | 438 | 439 | 495.00000 440 | 441 | 442 | 443 | 494.00000 444 | 445 | 446 | 447 | 492.00000 448 | 449 | 450 | 451 | 493.00000 452 | 453 | 454 | 455 | 493.00000 456 | 457 | 458 | 459 | 492.00000 460 | 461 | 462 | 463 | 491.00000 464 | 465 | 466 | 467 | 493.00000 468 | 469 | 470 | 471 | 492.00000 472 | 473 | 474 | 475 | 490.00000 476 | 477 | 478 | 479 | 490.00000 480 | 481 | 482 | 483 | 491.00000 484 | 485 | 486 | 487 | 491.00000 488 | 489 | 490 | 491 | 489.00000 492 | 493 | 494 | 495 | 492.00000 496 | 497 | 498 | 499 | 490.00000 500 | 501 | 502 | 503 | 486.00000 504 | 505 | 506 | 507 | 487.00000 508 | 509 | 510 | 511 | 490.00000 512 | 513 | 514 | 515 | 487.00000 516 | 517 | 518 | 519 | 486.00000 520 | 521 | 522 | 523 | 486.00000 524 | 525 | 526 | 527 | 487.00000 528 | 529 | 530 | 531 | 487.00000 532 | 533 | 534 | 535 | 487.00000 536 | 537 | 538 | 539 | 484.00000 540 | 541 | 542 | 543 | 485.00000 544 | 545 | 546 | 547 | 482.00000 548 | 549 | 550 | 551 | 482.00000 552 | 553 | 554 | 555 | 482.00000 556 | 557 | 558 | 559 | 482.00000 560 | 561 | 562 | 563 | 482.00000 564 | 565 | 566 | 567 | 481.00000 568 | 569 | 570 | 571 | 482.00000 572 | 573 | 574 | 575 | 482.00000 576 | 577 | 578 | 579 | 483.00000 580 | 581 | 582 | 583 | 481.00000 584 | 585 | 586 | 587 | 482.00000 588 | 589 | 590 | 591 | 482.00000 592 | 593 | 594 | 595 | 485.00000 596 | 597 | 598 | 599 | 483.00000 600 | 601 | 602 | 603 | 483.00000 604 | 605 | 606 | 607 | 482.00000 608 | 609 | 610 | 611 | 482.00000 612 | 613 | 614 | 615 | 481.00000 616 | 617 | 618 | 619 | 482.00000 620 | 621 | 622 | 623 | 482.00000 624 | 625 | 626 | 627 | 482.00000 628 | 629 | 630 | 631 | 632 | 633 | 634 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphhopper-js-api-client", 3 | "version": "7.6.1", 4 | "description": "A Javascript Client for GraphHopper", 5 | "author": "GraphHopper Community", 6 | "license": "Apache-2.0", 7 | "main": "dist/graphhopper-client.js", 8 | "scripts": { 9 | "serve": "webpack serve --mode development", 10 | "bundle": "webpack --mode development", 11 | "bundleProduction": "webpack --mode production", 12 | "test": "JASMINE_CONFIG_PATH=spec/jasmine.json jasmine" 13 | }, 14 | "dependencies": { 15 | "axios": "^1.7.4" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.24.7", 19 | "@babel/preset-env": "^7.24.7", 20 | "babel-loader": "^9.1.3", 21 | "html-webpack-plugin": "^5.6.0", 22 | "jasmine": "5.1.0", 23 | "webpack": "^5.91.0", 24 | "webpack-cli": "^5.1.4", 25 | "webpack-dev-server": "^5.0.4" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/graphhopper/directions-api-js-client.git" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /route-optimization-examples/american_road_trip.json: -------------------------------------------------------------------------------- 1 | { 2 | "vehicles" : [ { 3 | "vehicle_id" : "traveling_salesman", 4 | "start_address" : { 5 | "location_id" : "0", 6 | "lon" : -88.014426, 7 | "lat" : 30.681803 8 | }, 9 | "type_id" : "default", 10 | "return_to_depot":true 11 | } ], 12 | "vehicle_types" : [ { 13 | "type_id" : "default", 14 | "profile" : "car" 15 | } ], 16 | "services" : [ { 17 | "id" : "44", 18 | "name" : "no-name", 19 | "address" : { 20 | "location_id" : "44", 21 | "lon" : -71.298265, 22 | "lat" : 41.469858 23 | } 24 | }, { 25 | "id" : "45", 26 | "name" : "no-name", 27 | "address" : { 28 | "location_id" : "45", 29 | "lon" : -79.874692, 30 | "lat" : 32.752348 31 | } 32 | }, { 33 | "id" : "46", 34 | "name" : "no-name", 35 | "address" : { 36 | "location_id" : "46", 37 | "lon" : -103.459067, 38 | "lat" : 43.879102 39 | } 40 | }, { 41 | "id" : "47", 42 | "name" : "no-name", 43 | "address" : { 44 | "location_id" : "47", 45 | "lon" : -90.026049, 46 | "lat" : 35.047691 47 | } 48 | }, { 49 | "id" : "48", 50 | "name" : "no-name", 51 | "address" : { 52 | "location_id" : "48", 53 | "lon" : -98.486142, 54 | "lat" : 29.425967 55 | } 56 | }, { 57 | "id" : "49", 58 | "name" : "no-name", 59 | "address" : { 60 | "location_id" : "49", 61 | "lon" : -112.18709, 62 | "lat" : 37.593038 63 | } 64 | }, { 65 | "id" : "10", 66 | "name" : "no-name", 67 | "address" : { 68 | "location_id" : "10", 69 | "lon" : -86.617524, 70 | "lat" : 38.566697 71 | } 72 | }, { 73 | "id" : "11", 74 | "name" : "no-name", 75 | "address" : { 76 | "location_id" : "11", 77 | "lon" : -93.648542, 78 | "lat" : 41.583218 79 | } 80 | }, { 81 | "id" : "12", 82 | "name" : "no-name", 83 | "address" : { 84 | "location_id" : "12", 85 | "lon" : -94.909536, 86 | "lat" : 39.317245 87 | } 88 | }, { 89 | "id" : "13", 90 | "name" : "no-name", 91 | "address" : { 92 | "location_id" : "13", 93 | "lon" : -86.100528, 94 | "lat" : 37.186998 95 | } 96 | }, { 97 | "id" : "14", 98 | "name" : "no-name", 99 | "address" : { 100 | "location_id" : "14", 101 | "lon" : -90.064411, 102 | "lat" : 29.958443 103 | } 104 | }, { 105 | "id" : "15", 106 | "name" : "no-name", 107 | "address" : { 108 | "location_id" : "15", 109 | "lon" : -68.273335, 110 | "lat" : 44.338556 111 | } 112 | }, { 113 | "id" : "16", 114 | "name" : "no-name", 115 | "address" : { 116 | "location_id" : "16", 117 | "lon" : -76.490974, 118 | "lat" : 38.978828 119 | } 120 | }, { 121 | "id" : "17", 122 | "name" : "no-name", 123 | "address" : { 124 | "location_id" : "17", 125 | "lon" : -111.939697, 126 | "lat" : 36.004673 127 | } 128 | }, { 129 | "id" : "18", 130 | "name" : "no-name", 131 | "address" : { 132 | "location_id" : "18", 133 | "lon" : -92.065143, 134 | "lat" : 34.647037 135 | } 136 | }, { 137 | "id" : "19", 138 | "name" : "no-name", 139 | "address" : { 140 | "location_id" : "19", 141 | "lon" : -120.987632, 142 | "lat" : 36.576088 143 | } 144 | }, { 145 | "id" : "1", 146 | "name" : "no-name", 147 | "address" : { 148 | "location_id" : "1", 149 | "lon" : -73.247227, 150 | "lat" : 44.408948 151 | } 152 | }, { 153 | "id" : "2", 154 | "name" : "no-name", 155 | "address" : { 156 | "location_id" : "2", 157 | "lon" : -77.107386, 158 | "lat" : 38.729314 159 | } 160 | }, { 161 | "id" : "3", 162 | "name" : "no-name", 163 | "address" : { 164 | "location_id" : "3", 165 | "lon" : -119.488974, 166 | "lat" : 46.550684 167 | } 168 | }, { 169 | "id" : "4", 170 | "name" : "no-name", 171 | "address" : { 172 | "location_id" : "4", 173 | "lon" : -80.44563, 174 | "lat" : 37.801788 175 | } 176 | }, { 177 | "id" : "5", 178 | "name" : "no-name", 179 | "address" : { 180 | "location_id" : "5", 181 | "lon" : -90.070467, 182 | "lat" : 43.141031 183 | } 184 | }, { 185 | "id" : "6", 186 | "name" : "no-name", 187 | "address" : { 188 | "location_id" : "6", 189 | "lon" : -110.647602, 190 | "lat" : 44.433412 191 | } 192 | }, { 193 | "id" : "7", 194 | "name" : "no-name", 195 | "address" : { 196 | "location_id" : "7", 197 | "lon" : -82.272327, 198 | "lat" : 31.056794 199 | } 200 | }, { 201 | "id" : "8", 202 | "name" : "no-name", 203 | "address" : { 204 | "location_id" : "8", 205 | "lon" : -113.563131, 206 | "lat" : 43.461858 207 | } 208 | }, { 209 | "id" : "9", 210 | "name" : "no-name", 211 | "address" : { 212 | "location_id" : "9", 213 | "lon" : -89.646184, 214 | "lat" : 39.797519 215 | } 216 | }, { 217 | "id" : "20", 218 | "name" : "no-name", 219 | "address" : { 220 | "location_id" : "20", 221 | "lon" : -122.411715, 222 | "lat" : 37.794781 223 | } 224 | }, { 225 | "id" : "21", 226 | "name" : "no-name", 227 | "address" : { 228 | "location_id" : "21", 229 | "lon" : -105.04226, 230 | "lat" : 38.840871 231 | } 232 | }, { 233 | "id" : "22", 234 | "name" : "no-name", 235 | "address" : { 236 | "location_id" : "22", 237 | "lon" : -72.701173, 238 | "lat" : 41.766759 239 | } 240 | }, { 241 | "id" : "23", 242 | "name" : "no-name", 243 | "address" : { 244 | "location_id" : "23", 245 | "lon" : -75.52767, 246 | "lat" : 38.910832 247 | } 248 | }, { 249 | "id" : "24", 250 | "name" : "no-name", 251 | "address" : { 252 | "location_id" : "24", 253 | "lon" : -77.03653, 254 | "lat" : 38.897676 255 | } 256 | }, { 257 | "id" : "25", 258 | "name" : "no-name", 259 | "address" : { 260 | "location_id" : "25", 261 | "lon" : -80.603611, 262 | "lat" : 28.388333 263 | } 264 | }, { 265 | "id" : "26", 266 | "name" : "no-name", 267 | "address" : { 268 | "location_id" : "26", 269 | "lon" : -71.056575, 270 | "lat" : 42.37247 271 | } 272 | }, { 273 | "id" : "27", 274 | "name" : "no-name", 275 | "address" : { 276 | "location_id" : "27", 277 | "lon" : -83.084943, 278 | "lat" : 42.387579 279 | } 280 | }, { 281 | "id" : "28", 282 | "name" : "no-name", 283 | "address" : { 284 | "location_id" : "28", 285 | "lon" : -93.180627, 286 | "lat" : 44.89285 287 | } 288 | }, { 289 | "id" : "29", 290 | "name" : "no-name", 291 | "address" : { 292 | "location_id" : "29", 293 | "lon" : -90.84985, 294 | "lat" : 32.34655 295 | } 296 | }, { 297 | "id" : "30", 298 | "name" : "no-name", 299 | "address" : { 300 | "location_id" : "30", 301 | "lon" : -90.184776, 302 | "lat" : 38.624691 303 | } 304 | }, { 305 | "id" : "31", 306 | "name" : "no-name", 307 | "address" : { 308 | "location_id" : "31", 309 | "lon" : -113.787023, 310 | "lat" : 48.759613 311 | } 312 | }, { 313 | "id" : "32", 314 | "name" : "no-name", 315 | "address" : { 316 | "location_id" : "32", 317 | "lon" : -98.158611, 318 | "lat" : 42.425 319 | } 320 | }, { 321 | "id" : "33", 322 | "name" : "no-name", 323 | "address" : { 324 | "location_id" : "33", 325 | "lon" : -114.737732, 326 | "lat" : 36.016066 327 | } 328 | }, { 329 | "id" : "34", 330 | "name" : "no-name", 331 | "address" : { 332 | "location_id" : "34", 333 | "lon" : -71.441189, 334 | "lat" : 44.25812 335 | } 336 | }, { 337 | "id" : "35", 338 | "name" : "no-name", 339 | "address" : { 340 | "location_id" : "35", 341 | "lon" : -74.924184, 342 | "lat" : 38.931843 343 | } 344 | }, { 345 | "id" : "36", 346 | "name" : "no-name", 347 | "address" : { 348 | "location_id" : "36", 349 | "lon" : -104.58745, 350 | "lat" : 32.123169 351 | } 352 | }, { 353 | "id" : "37", 354 | "name" : "no-name", 355 | "address" : { 356 | "location_id" : "37", 357 | "lon" : -74.0445, 358 | "lat" : 40.689249 359 | } 360 | }, { 361 | "id" : "38", 362 | "name" : "no-name", 363 | "address" : { 364 | "location_id" : "38", 365 | "lon" : -75.67573, 366 | "lat" : 35.908226 367 | } 368 | }, { 369 | "id" : "39", 370 | "name" : "no-name", 371 | "address" : { 372 | "location_id" : "39", 373 | "lon" : -104.041483, 374 | "lat" : 48.00016 375 | } 376 | }, { 377 | "id" : "40", 378 | "name" : "no-name", 379 | "address" : { 380 | "location_id" : "40", 381 | "lon" : -84.524997, 382 | "lat" : 39.174331 383 | } 384 | }, { 385 | "id" : "41", 386 | "name" : "no-name", 387 | "address" : { 388 | "location_id" : "41", 389 | "lon" : -97.012213, 390 | "lat" : 34.457043 391 | } 392 | }, { 393 | "id" : "42", 394 | "name" : "no-name", 395 | "address" : { 396 | "location_id" : "42", 397 | "lon" : -121.519633, 398 | "lat" : 45.711564 399 | } 400 | }, { 401 | "id" : "43", 402 | "name" : "no-name", 403 | "address" : { 404 | "location_id" : "43", 405 | "lon" : -75.150282, 406 | "lat" : 39.94961 407 | } 408 | } ] 409 | } -------------------------------------------------------------------------------- /route-optimization-examples/tsp_lonlat_end.json: -------------------------------------------------------------------------------- 1 | { 2 | "vehicles" : [{ 3 | "vehicle_id" : "vehicle1", 4 | "start_address" : { 5 | "location_id" : "v1_gera", 6 | "lon": 12.076721, 7 | "lat": 50.877044 8 | }, 9 | "end_address" : { 10 | "location_id" : "v1_muenchen", 11 | "lon": 11.58, 12 | "lat": 48.135 13 | }, 14 | "type_id" : "vehicle_type_1", 15 | "return_to_depot" : true 16 | }], 17 | "vehicle_types" : [{ 18 | "type_id" : "vehicle_type_1", 19 | "profile" : "car" 20 | }], 21 | "services" : [ 22 | { 23 | "id": "deliver_bionade_in_rostock", 24 | "name": "deliver_bionade_in_rostock", 25 | "address": { 26 | "location_id": "loc_b1", 27 | "lon": 12.1333333, 28 | "lat": 54.0833333 29 | } 30 | }, 31 | { 32 | "id": "deliver_cola_in_berlin", 33 | "name": "deliver_cola_in_berlin", 34 | "address": { 35 | "location_id": "loc_b2", 36 | "lon": 13.354568, 37 | "lat": 52.514549 38 | } 39 | }, 40 | { 41 | "id": "deliver_juice_in_ulm", 42 | "name": "deliver_juice_in_ulm", 43 | "address": { 44 | "location_id": "loc_b3", 45 | "lon": 9.990692, 46 | "lat": 48.398209 47 | } 48 | }, 49 | { 50 | "id": "deliver_beer_in_dresden", 51 | "name": "deliver_beer_in_dresden", 52 | "address": { 53 | "location_id": "loc_b4", 54 | "lon": 13.738403, 55 | "lat": 51.050028 56 | } 57 | }, 58 | { 59 | "id": "deliver_wine_in_kassel", 60 | "name": "deliver_wine_in_kassel", 61 | "address": { 62 | "location_id": "loc_b5", 63 | "lon": 9.486694, 64 | "lat": 51.31173 65 | } 66 | }, 67 | { 68 | "id": "deliver_beer_in_dortmund", 69 | "name": "deliver_beer_in_dortmund", 70 | "address": { 71 | "location_id": "loc_b6", 72 | "lon": 7.500916, 73 | "lat": 51.508742 74 | } 75 | }, 76 | { 77 | "id": "deliver_water_in_karlsruhe", 78 | "name": "deliver_water_in_karlsruhe", 79 | "address": { 80 | "location_id": "loc_b7", 81 | "lon" : 8.3858333, 82 | "lat" : 49.0047222 83 | } 84 | }, 85 | { 86 | "id": "deliver_fish_in_bremen", 87 | "name": "deliver_fish_in_bremen", 88 | "address": { 89 | "location_id": "loc_b8", 90 | "lon": 8.822021, 91 | "lat": 53.041213 92 | } 93 | }, 94 | { 95 | "id": "deliver_somethingelse_in_hof", 96 | "name": "deliver_somethingelse_in_hof", 97 | "address": { 98 | "location_id": "loc_b9", 99 | "lon": 11.8927, 100 | "lat": 50.310392 101 | } 102 | } 103 | ] 104 | } -------------------------------------------------------------------------------- /route-optimization-examples/tsp_lonlat_new.json: -------------------------------------------------------------------------------- 1 | { 2 | "vehicles" : [{ 3 | "vehicle_id" : "vehicle1", 4 | "start_address" : { 5 | "location_id" : "v1_startLoc", 6 | "lon": 9.797058, 7 | "lat": 52.375599 8 | }, 9 | "type_id" : "vehicle_type_1" 10 | }], 11 | "vehicle_types" : [{ 12 | "type_id" : "vehicle_type_1", 13 | "profile" : "car" 14 | }], 15 | "services" : [ 16 | { 17 | "id": "b1", 18 | "name": "drink_bionade_in_rostock", 19 | "address": { 20 | "location_id": "loc_b1", 21 | "lon": 12.1333333, 22 | "lat": 54.0833333 23 | } 24 | }, 25 | { 26 | "id": "b2", 27 | "name": "drink_cola_in_berlin", 28 | "address": { 29 | "location_id": "loc_b2", 30 | "lon": 13.354568, 31 | "lat": 52.514549 32 | } 33 | }, 34 | { 35 | "id": "b3", 36 | "name": "drink_juice_in_ulm", 37 | "address": { 38 | "location_id": "loc_b3", 39 | "lon": 9.990692, 40 | "lat": 48.398209 41 | } 42 | }, 43 | { 44 | "id": "b4", 45 | "name": "drink_nothing_in_dresden", 46 | "address": { 47 | "location_id": "loc_b4", 48 | "lon": 13.738403, 49 | "lat": 51.050028 50 | } 51 | }, 52 | { 53 | "id": "b5", 54 | "name": "drink_wine_in_kassel", 55 | "address": { 56 | "location_id": "loc_b5", 57 | "lon": 9.486694, 58 | "lat": 51.31173 59 | } 60 | }, 61 | { 62 | "id": "b6", 63 | "name": "drink_beer_in_dortmund", 64 | "address": { 65 | "location_id": "loc_b6", 66 | "lon": 7.500916, 67 | "lat": 51.508742 68 | } 69 | }, 70 | { 71 | "id": "b7", 72 | "name": "drink_water_in_karlsruhe", 73 | "address": { 74 | "location_id": "loc_b7", 75 | "lon" : 8.3858333, 76 | "lat" : 49.0047222 77 | } 78 | }, 79 | { 80 | "id": "b8", 81 | "name": "drink_fish_in_bremen", 82 | "address": { 83 | "location_id": "loc_b8", 84 | "lon": 8.822021, 85 | "lat": 53.041213 86 | } 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /route-optimization-examples/uk50.json: -------------------------------------------------------------------------------- 1 | { 2 | "vehicles" : [ { 3 | "vehicle_id" : "traveling_salesman", 4 | "start_address" : { 5 | "location_id" : "1", 6 | "lon" : -0.08921, 7 | "lat" : 51.5054092 8 | }, 9 | "type_id" : "default" 10 | } ], 11 | "vehicle_types" : [ { 12 | "type_id" : "default", 13 | "profile" : "car" 14 | } ], 15 | "services" : [ { 16 | "id" : "44", 17 | "name" : "no-name", 18 | "address" : { 19 | "location_id" : "44", 20 | "lon" : -1.78205, 21 | "lat" : 51.5584793 22 | } 23 | }, { 24 | "id" : "45", 25 | "name" : "no-name", 26 | "address" : { 27 | "location_id" : "45", 28 | "lon" : -0.73815, 29 | "lat" : 51.9989319 30 | } 31 | }, { 32 | "id" : "46", 33 | "name" : "no-name", 34 | "address" : { 35 | "location_id" : "46", 36 | "lon" : -0.07226, 37 | "lat" : 52.3230095 38 | } 39 | }, { 40 | "id" : "47", 41 | "name" : "no-name", 42 | "address" : { 43 | "location_id" : "47", 44 | "lon" : -0.20299, 45 | "lat" : 51.9011993 46 | } 47 | }, { 48 | "id" : "48", 49 | "name" : "no-name", 50 | "address" : { 51 | "location_id" : "48", 52 | "lon" : -0.40412, 53 | "lat" : 51.6598015 54 | } 55 | }, { 56 | "id" : "49", 57 | "name" : "no-name", 58 | "address" : { 59 | "location_id" : "49", 60 | "lon" : -0.3206, 61 | "lat" : 51.6287994 62 | } 63 | }, { 64 | "id" : "50", 65 | "name" : "no-name", 66 | "address" : { 67 | "location_id" : "50", 68 | "lon" : -0.30551, 69 | "lat" : 51.5110397 70 | } 71 | }, { 72 | "id" : "10", 73 | "name" : "no-name", 74 | "address" : { 75 | "location_id" : "10", 76 | "lon" : -0.08146, 77 | "lat" : 53.5651398 78 | } 79 | }, { 80 | "id" : "11", 81 | "name" : "no-name", 82 | "address" : { 83 | "location_id" : "11", 84 | "lon" : -0.43936, 85 | "lat" : 53.685595 86 | } 87 | }, { 88 | "id" : "12", 89 | "name" : "no-name", 90 | "address" : { 91 | "location_id" : "12", 92 | "lon" : -1.5400175, 93 | "lat" : 53.4048617 94 | } 95 | }, { 96 | "id" : "13", 97 | "name" : "no-name", 98 | "address" : { 99 | "location_id" : "13", 100 | "lon" : -1.92636, 101 | "lat" : 53.6005783 102 | } 103 | }, { 104 | "id" : "14", 105 | "name" : "no-name", 106 | "address" : { 107 | "location_id" : "14", 108 | "lon" : -2.2077999, 109 | "lat" : 53.6936684 110 | } 111 | }, { 112 | "id" : "15", 113 | "name" : "no-name", 114 | "address" : { 115 | "location_id" : "15", 116 | "lon" : -2.17769, 117 | "lat" : 53.8553696 118 | } 119 | }, { 120 | "id" : "16", 121 | "name" : "no-name", 122 | "address" : { 123 | "location_id" : "16", 124 | "lon" : -1.67989, 125 | "lat" : 53.8069801 126 | } 127 | }, { 128 | "id" : "17", 129 | "name" : "no-name", 130 | "address" : { 131 | "location_id" : "17", 132 | "lon" : -1.3433, 133 | "lat" : 54.5714111 134 | } 135 | }, { 136 | "id" : "18", 137 | "name" : "no-name", 138 | "address" : { 139 | "location_id" : "18", 140 | "lon" : -2.0041001, 141 | "lat" : 55.7700691 142 | } 143 | }, { 144 | "id" : "19", 145 | "name" : "no-name", 146 | "address" : { 147 | "location_id" : "19", 148 | "lon" : -2.7867586, 149 | "lat" : 55.4227586 150 | } 151 | }, { 152 | "id" : "2", 153 | "name" : "no-name", 154 | "address" : { 155 | "location_id" : "2", 156 | "lon" : -0.05895690000000001, 157 | "lat" : 50.8039108 158 | } 159 | }, { 160 | "id" : "3", 161 | "name" : "no-name", 162 | "address" : { 163 | "location_id" : "3", 164 | "lon" : 1.3110267, 165 | "lat" : 51.1269357 166 | } 167 | }, { 168 | "id" : "4", 169 | "name" : "no-name", 170 | "address" : { 171 | "location_id" : "4", 172 | "lon" : 1.33866, 173 | "lat" : 51.274929 174 | } 175 | }, { 176 | "id" : "5", 177 | "name" : "no-name", 178 | "address" : { 179 | "location_id" : "5", 180 | "lon" : 1.38274, 181 | "lat" : 51.3917999 182 | } 183 | }, { 184 | "id" : "6", 185 | "name" : "no-name", 186 | "address" : { 187 | "location_id" : "6", 188 | "lon" : 0.5941, 189 | "lat" : 51.5231514 190 | } 191 | }, { 192 | "id" : "7", 193 | "name" : "no-name", 194 | "address" : { 195 | "location_id" : "7", 196 | "lon" : 1.72535, 197 | "lat" : 52.6060486 198 | } 199 | }, { 200 | "id" : "8", 201 | "name" : "no-name", 202 | "address" : { 203 | "location_id" : "8", 204 | "lon" : 1.2979, 205 | "lat" : 52.6488419 206 | } 207 | }, { 208 | "id" : "9", 209 | "name" : "no-name", 210 | "address" : { 211 | "location_id" : "9", 212 | "lon" : -0.08474, 213 | "lat" : 52.9091492 214 | } 215 | }, { 216 | "id" : "20", 217 | "name" : "no-name", 218 | "address" : { 219 | "location_id" : "20", 220 | "lon" : -3.17061, 221 | "lat" : 56.1968994 222 | } 223 | }, { 224 | "id" : "21", 225 | "name" : "no-name", 226 | "address" : { 227 | "location_id" : "21", 228 | "lon" : -5.5134602, 229 | "lat" : 57.2769203 230 | } 231 | }, { 232 | "id" : "22", 233 | "name" : "no-name", 234 | "address" : { 235 | "location_id" : "22", 236 | "lon" : -4.756129, 237 | "lat" : 55.9471344 238 | } 239 | }, { 240 | "id" : "23", 241 | "name" : "no-name", 242 | "address" : { 243 | "location_id" : "23", 244 | "lon" : -4.3119189, 245 | "lat" : 55.852812 246 | } 247 | }, { 248 | "id" : "24", 249 | "name" : "no-name", 250 | "address" : { 251 | "location_id" : "24", 252 | "lon" : -4.2140624, 253 | "lat" : 55.82852459999999 254 | } 255 | }, { 256 | "id" : "25", 257 | "name" : "no-name", 258 | "address" : { 259 | "location_id" : "25", 260 | "lon" : -4.0426408, 261 | "lat" : 55.8689133 262 | } 263 | }, { 264 | "id" : "26", 265 | "name" : "no-name", 266 | "address" : { 267 | "location_id" : "26", 268 | "lon" : -4.33915, 269 | "lat" : 55.76891 270 | } 271 | }, { 272 | "id" : "27", 273 | "name" : "no-name", 274 | "address" : { 275 | "location_id" : "27", 276 | "lon" : -4.1657901, 277 | "lat" : 55.1071587 278 | } 279 | }, { 280 | "id" : "28", 281 | "name" : "no-name", 282 | "address" : { 283 | "location_id" : "28", 284 | "lon" : -3.355446, 285 | "lat" : 55.1215189 286 | } 287 | }, { 288 | "id" : "29", 289 | "name" : "no-name", 290 | "address" : { 291 | "location_id" : "29", 292 | "lon" : -2.88047, 293 | "lat" : 54.0665703 294 | } 295 | }, { 296 | "id" : "30", 297 | "name" : "no-name", 298 | "address" : { 299 | "location_id" : "30", 300 | "lon" : -3.174976, 301 | "lat" : 53.39854 302 | } 303 | }, { 304 | "id" : "31", 305 | "name" : "no-name", 306 | "address" : { 307 | "location_id" : "31", 308 | "lon" : -2.1643955, 309 | "lat" : 53.3629916 310 | } 311 | }, { 312 | "id" : "32", 313 | "name" : "no-name", 314 | "address" : { 315 | "location_id" : "32", 316 | "lon" : -1.91392, 317 | "lat" : 53.2569084 318 | } 319 | }, { 320 | "id" : "33", 321 | "name" : "no-name", 322 | "address" : { 323 | "location_id" : "33", 324 | "lon" : -1.40799, 325 | "lat" : 53.050499 326 | } 327 | }, { 328 | "id" : "34", 329 | "name" : "no-name", 330 | "address" : { 331 | "location_id" : "34", 332 | "lon" : -1.8140399, 333 | "lat" : 52.4739609 334 | } 335 | }, { 336 | "id" : "35", 337 | "name" : "no-name", 338 | "address" : { 339 | "location_id" : "35", 340 | "lon" : -2.05071, 341 | "lat" : 52.4484215 342 | } 343 | }, { 344 | "id" : "36", 345 | "name" : "no-name", 346 | "address" : { 347 | "location_id" : "36", 348 | "lon" : -2.1633999, 349 | "lat" : 52.4976807 350 | } 351 | }, { 352 | "id" : "37", 353 | "name" : "no-name", 354 | "address" : { 355 | "location_id" : "37", 356 | "lon" : -2.2206399, 357 | "lat" : 52.1913109 358 | } 359 | }, { 360 | "id" : "38", 361 | "name" : "no-name", 362 | "address" : { 363 | "location_id" : "38", 364 | "lon" : -3.39148, 365 | "lat" : 51.9482994 366 | } 367 | }, { 368 | "id" : "39", 369 | "name" : "no-name", 370 | "address" : { 371 | "location_id" : "39", 372 | "lon" : -3.53159, 373 | "lat" : 50.4689598 374 | } 375 | }, { 376 | "id" : "40", 377 | "name" : "no-name", 378 | "address" : { 379 | "location_id" : "40", 380 | "lon" : -4.9367199, 381 | "lat" : 50.5391617 382 | } 383 | }, { 384 | "id" : "41", 385 | "name" : "no-name", 386 | "address" : { 387 | "location_id" : "41", 388 | "lon" : -2.48386, 389 | "lat" : 51.2852707 390 | } 391 | }, { 392 | "id" : "42", 393 | "name" : "no-name", 394 | "address" : { 395 | "location_id" : "42", 396 | "lon" : -2.6004901, 397 | "lat" : 51.4519806 398 | } 399 | }, { 400 | "id" : "43", 401 | "name" : "no-name", 402 | "address" : { 403 | "location_id" : "43", 404 | "lon" : -2.243834, 405 | "lat" : 51.864107 406 | } 407 | } ] 408 | } -------------------------------------------------------------------------------- /route-optimization-examples/vrp_lonlat_new.json: -------------------------------------------------------------------------------- 1 | { 2 | "vehicles": [{ 3 | "vehicle_id": "vehicle1", 4 | "start_address": { 5 | "location_id": "v1_erfurt", 6 | "lon": 11.028771, 7 | "lat": 50.977723 8 | }, 9 | "type_id": "vehicle_type_1" 10 | }, 11 | { 12 | "vehicle_id": "vehicle2", 13 | "start_address": { 14 | "location_id": "v2_erfurt", 15 | "lon": 11.028771, 16 | "lat": 50.977723 17 | }, 18 | "type_id": "vehicle_type_1" 19 | }, { 20 | "vehicle_id": "vehicle3", 21 | "start_address": { 22 | "location_id": "v3_kassel", 23 | "lon": 9.492188, 24 | "lat": 51.296276 25 | }, 26 | "type_id": "vehicle_type_1" 27 | }, { 28 | "vehicle_id": "vehicle4", 29 | "start_address": { 30 | "location_id": "v4_erfurt", 31 | "lon": 11.028771, 32 | "lat": 50.977723 33 | }, 34 | "type_id": "vehicle_type_1" 35 | }], 36 | "vehicle_types": [{ 37 | "type_id": "vehicle_type_1", 38 | "profile": "car", 39 | "capacity": [3] 40 | }], 41 | "services": [ 42 | { 43 | "id": "b1", 44 | "name": "drink_bionade_in_magdeburg", 45 | "address": { 46 | "location_id": "loc_b1", 47 | "lon": 11.645508, 48 | "lat": 52.126744 49 | }, 50 | "size": [1] 51 | }, 52 | { 53 | "id": "b2", 54 | "name": "drink_cola_in_berlin", 55 | "address": { 56 | "location_id": "loc_b2", 57 | "lon": 13.354568, 58 | "lat": 52.514549 59 | }, 60 | "size": [1] 61 | }, 62 | { 63 | "id": "b3", 64 | "name": "drink_juice_in_ulm", 65 | "address": { 66 | "location_id": "loc_b3", 67 | "lon": 9.990692, 68 | "lat": 48.398209 69 | }, 70 | "size": [1] 71 | }, 72 | { 73 | "id": "b4", 74 | "name": "drink_nothing_in_dresden", 75 | "address": { 76 | "location_id": "loc_b4", 77 | "lon": 13.738403, 78 | "lat": 51.050028 79 | }, 80 | "size": [1] 81 | }, 82 | { 83 | "id": "b5", 84 | "name": "drink_wine_in_kassel", 85 | "address": { 86 | "location_id": "loc_b5", 87 | "lon": 9.486694, 88 | "lat": 51.31173 89 | }, 90 | "size": [1] 91 | }, 92 | { 93 | "id": "b6", 94 | "name": "drink_beer_in_dortmund", 95 | "address": { 96 | "location_id": "loc_b6", 97 | "lon": 7.500916, 98 | "lat": 51.508742 99 | }, 100 | "size": [1] 101 | }, 102 | { 103 | "id": "b7", 104 | "name": "drink_water_in_karlsruhe", 105 | "address": { 106 | "location_id": "loc_b7", 107 | "lon": 8.3858333, 108 | "lat": 49.0047222 109 | }, 110 | "size": [1] 111 | }, 112 | { 113 | "id": "b8", 114 | "name": "drink_fish_in_bremen", 115 | "address": { 116 | "location_id": "loc_b8", 117 | "lon": 8.822021, 118 | "lat": 53.041213 119 | }, 120 | "size": [1] 121 | }, 122 | { 123 | "id": "b9", 124 | "name": "drink_somethingelse_in_hof", 125 | "address": { 126 | "location_id": "loc_b9", 127 | "lon": 11.8927, 128 | "lat": 50.310392 129 | }, 130 | "size": [1] 131 | }, { 132 | "id": "b10", 133 | "name": "drink_mainz", 134 | "address": { 135 | "location_id": "loc_b10", 136 | "lon": 8.278198, 137 | "lat": 50.00067 138 | }, 139 | "size": [1] 140 | }, { 141 | "id": "b11", 142 | "name": "drink_schwerin", 143 | "address": { 144 | "location_id": "loc_b11", 145 | "lon": 11.447754, 146 | "lat": 53.618579 147 | }, 148 | "size": [1] 149 | } 150 | ] 151 | } 152 | -------------------------------------------------------------------------------- /spec/GraphHopperGeocodingSpec.js: -------------------------------------------------------------------------------- 1 | let GraphHopperGeocoding = require('../src/GraphHopperGeocoding'); 2 | let ghGeocoding = new GraphHopperGeocoding({key: key}); 3 | 4 | 5 | describe("Geocoding Test", function () { 6 | describe("Forward Geocoding", function () { 7 | it("Get results", function (done) { 8 | ghGeocoding.doRequest({query: "München"}) 9 | .then(function (json) { 10 | expect(json.hits.length).toBeGreaterThan(4); 11 | done(); 12 | }) 13 | .catch(function (err) { 14 | done.fail(err.message); 15 | }); 16 | }); 17 | }); 18 | describe("Reverse Geocoding", function () { 19 | it("Get results", function (done) { 20 | ghGeocoding.doRequest({point: "52.547966,13.349419"}) 21 | .then(function (json) { 22 | // Expect at least one result for reverse 23 | expect(json.hits.length).toBeGreaterThan(0); 24 | done(); 25 | }) 26 | .catch(function (err) { 27 | done.fail(err.message); 28 | }); 29 | }); 30 | }); 31 | describe("Create Exception", function () { 32 | it("Empty Request", function (done) { 33 | ghGeocoding.doRequest() 34 | .then(function () { 35 | done.fail("Shouldn't succeed"); 36 | }) 37 | .catch(function (err) { 38 | expect(err.message.length).toBeGreaterThan(0); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | }); -------------------------------------------------------------------------------- /spec/GraphHopperIsochroneSpec.js: -------------------------------------------------------------------------------- 1 | let GraphHopperIsochrone = require('../src/GraphHopperIsochrone'); 2 | let ghIsochrone = new GraphHopperIsochrone({key: key}, {profile: profile}); 3 | 4 | 5 | describe("Isochrone Test", function () { 6 | it("Get results", function (done) { 7 | ghIsochrone.doRequest({point: "52.547966,13.349419", buckets: 2}) 8 | .then(function (json) { 9 | expect(json.polygons.length).toBeGreaterThan(0); 10 | expect(json.polygons[0].geometry.type).toEqual('Polygon'); 11 | done(); 12 | }) 13 | .catch(function (json) { 14 | done.fail("Shouldn't fail"+json); 15 | }) 16 | ; 17 | }); 18 | }); -------------------------------------------------------------------------------- /spec/GraphHopperMatrixSpec.js: -------------------------------------------------------------------------------- 1 | var GraphHopperMatrix = require('../src/GraphHopperMatrix'); 2 | var GHInput = require('../src/GHInput'); 3 | var ghMatrix = new GraphHopperMatrix({key: key}, {profile: profile}); 4 | 5 | 6 | describe("Matrix Test", function () { 7 | it("Get results", function (done) { 8 | let req = { 9 | out_arrays: ["distances"], 10 | points: [[13.15567, 52.651395], [13.143539, 52.432572], [13.461571, 52.43299], [13.381233, 52.622226]] 11 | }; 12 | 13 | ghMatrix.doRequest(req) 14 | .then(function (json) { 15 | expect(json.distances.length).toBe(4); 16 | // Always 0 by definition 17 | expect(json.distances[0][0]).toBe(0); 18 | expect(json.distances[0][1]).toBeGreaterThan(40000); 19 | expect(json.distances[0][1]).toBeLessThan(52000); 20 | done(); 21 | }) 22 | .catch(function (err) { 23 | done.fail(err.message); 24 | }) 25 | ; 26 | } 27 | ); 28 | }); -------------------------------------------------------------------------------- /spec/GraphHopperOptimizationSpec.js: -------------------------------------------------------------------------------- 1 | let GraphHopperOptimization = require('../src/GraphHopperOptimization'); 2 | let ghOptimization = new GraphHopperOptimization({key: key}); 3 | 4 | describe("Optimization Test", function () { 5 | it("Get results", function (done) { 6 | 7 | var jsonInput = { 8 | "vehicles": [{ 9 | "vehicle_id": "vehicle1", 10 | "start_address": {"location_id": "v1_startLoc", "lon": 9.797058, "lat": 52.375599} 11 | }], 12 | "services": [{ 13 | "id": "b1", 14 | "name": "drink_bionade_in_rostock", 15 | "address": {"location_id": "loc_b1", "lon": 12.1333333, "lat": 54.0833333} 16 | }, { 17 | "id": "b2", 18 | "name": "drink_cola_in_berlin", 19 | "address": {"location_id": "loc_b2", "lon": 13.354568, "lat": 52.514549} 20 | }] 21 | }; 22 | 23 | ghOptimization.doRequest(jsonInput, {}) 24 | .then(function (json) { 25 | expect(json.message).not.toBeDefined(); 26 | expect(json.solution.time).toBeGreaterThan(27000); 27 | expect(json.solution.time).toBeLessThan(30000); 28 | expect(json.solution.no_vehicles).toBe(1); 29 | expect(json.raw_solution.time).toBeGreaterThan(27000); 30 | expect(json.raw_solution.time).toBeLessThan(30000); 31 | done(); 32 | }) 33 | .catch(function (err) { 34 | done.fail(err.message); 35 | }); 36 | 37 | 38 | }); 39 | }); -------------------------------------------------------------------------------- /spec/GraphHopperRoutingSpec.js: -------------------------------------------------------------------------------- 1 | let GraphHopperRouting = require('../src/GraphHopperRouting'); 2 | let ghRouting = new GraphHopperRouting({key: key}, {profile: profile, elevation: false}); 3 | 4 | describe("Simple Route", function () { 5 | it("Get results", function (done) { 6 | ghRouting.doRequest({points: [[13.368988, 52.488634], [13.40332, 52.50034]]}) 7 | .then(function (json) { 8 | expect(json.paths.length).toBeGreaterThan(0); 9 | expect(json.paths[0].distance).toBeGreaterThan(3000); 10 | expect(json.paths[0].distance).toBeLessThan(4000); 11 | expect(json.paths[0].instructions[0].points.length).toBeGreaterThan(1); 12 | expect(json.paths[0].instructions[0].points[0][0]).toEqual(json.paths[0].points.coordinates[0][0]); 13 | expect(json.paths[0].instructions[0].points[0][1]).toBeGreaterThan(52.4); 14 | expect(json.paths[0].instructions[0].points[0][1]).toBeLessThan(52.6); 15 | done(); 16 | }) 17 | .catch(function (err) { 18 | done.fail(err.message); 19 | }); 20 | }); 21 | 22 | it("Compare Fastest vs. Shortest", function (done) { 23 | ghRouting.doRequest({points: [[13.207455, 52.303545], [13.28599, 52.314093]]}) 24 | .then(function (json) { 25 | var fastestTime = json.paths[0].time; 26 | var fastestDistance = json.paths[0].distance; 27 | // Shortest is not prepared with CH 28 | ghRouting.doRequest({ 29 | points: [[13.208442,52.290501], [13.283243,52.3153]], "ch.disable": true, 30 | "custom_model": {distance_influence: 500} 31 | }) 32 | .then(function (json2) { 33 | expect(json2.paths[0].time).toBeGreaterThan(fastestTime); 34 | expect(json2.paths[0].distance).toBeLessThan(fastestDistance); 35 | done(); 36 | }) 37 | .catch(function (err) { 38 | done.fail(err.message); 39 | }); 40 | }) 41 | .catch(function (err) { 42 | done.fail(err.message); 43 | }); 44 | }); 45 | 46 | it("Get Path Details", function (done) { 47 | ghRouting.doRequest({ 48 | points: [[13.368988, 52.488634], [13.40332, 52.50034]], 49 | details: ["average_speed", "edge_id"] 50 | }) 51 | .then(function (json) { 52 | expect(json.paths.length).toBeGreaterThan(0); 53 | var details = json.paths[0].details; 54 | expect(details).toBeDefined(); 55 | var edgeId = details.edge_id; 56 | var averageSpeed = details.average_speed; 57 | expect(edgeId.length).toBeGreaterThan(50); 58 | expect(edgeId.length).toBeLessThan(95); 59 | expect(averageSpeed.length).toBeGreaterThan(5); 60 | expect(averageSpeed.length).toBeLessThan(15); 61 | done(); 62 | }) 63 | .catch(function (err) { 64 | done.fail(err.message); 65 | }); 66 | }); 67 | 68 | it("Use PointHint", function (done) { 69 | ghRouting.doRequest({points: [[9.20878, 48.482242], [9.208463, 48.482886]], point_hints: ["Geranienweg", ""]}) 70 | .then(function (json) { 71 | expect(json.paths.length).toBeGreaterThan(0); 72 | // Due to PointHints, we match a different coordinate 73 | // These coordinates might change over time 74 | var snappedGeranienWeg = json.paths[0].snapped_waypoints.coordinates[0]; 75 | expect(snappedGeranienWeg[0]).toBeCloseTo(9.20869, 4); 76 | expect(snappedGeranienWeg[1]).toBeCloseTo(48.48235, 4); 77 | done(); 78 | }) 79 | .catch(function (err) { 80 | done.fail(err.message); 81 | }); 82 | }); 83 | it("Disable CH and Use Turn Restrictions", function (done) { 84 | ghRouting.doRequest({points: [[13.265026, 52.29811], [13.264967, 52.298018]], "ch.disable": true}) 85 | .then(function (json) { 86 | // With ch this will be only 12 m due to ignored turn restriction 87 | expect(json.paths[0].distance).toBeGreaterThan(300); 88 | done(); 89 | }) 90 | .catch(function (err) { 91 | done.fail(err.message); 92 | }); 93 | }); 94 | it("Disable CH to use Heading", function (done) { 95 | ghRouting.doRequest({ 96 | points: [[9.078012, 48.871028], [9.077958, 48.870925]], 97 | "ch.disable": true, 98 | headings: [0, "NaN"] 99 | }) 100 | .then(function (json) { 101 | // With ch this will be only 12 m due to ignored turn restriction 102 | expect(json.paths[0].distance).toBeGreaterThan(150); 103 | done(); 104 | }) 105 | .catch(function (err) { 106 | done.fail(err.message); 107 | }); 108 | }); 109 | 110 | it("Custom Model", function (done) { 111 | 112 | ghRouting.doRequest({ 113 | points: [[2.3522, 48.8566], [2.7016, 48.4047]], "ch.disable": true, 114 | custom_model: {priority: [{"if": "road_class == MOTORWAY", "multiply_by": 0}]} 115 | }) 116 | .then(function (json) { 117 | expect(json.paths[0].time).toBeGreaterThan(3900000); 118 | done(); 119 | }) 120 | .catch(function (err) { 121 | done.fail(err.message); 122 | }); 123 | }); 124 | it("Test getting hint on error", function (done) { 125 | // Some random point in the ocean 126 | ghRouting.doRequest({points: [[-10.283203, 47.457809], [-10.283203, 47.457809]]}) 127 | .then(function (json) { 128 | done.fail("No error received"); 129 | }) 130 | .catch(function (err) { 131 | // This is the hint message describing the error 132 | expect(err.message).toContain("Cannot find point 0: 47.457809,-10.283203"); 133 | done(); 134 | }); 135 | }); 136 | }); 137 | 138 | describe("Info Test", function () { 139 | it("Get Info", function (done) { 140 | 141 | ghRouting.info() 142 | .then(function (json) { 143 | expect(json.version.length).toBeGreaterThan(0); 144 | done(); 145 | }) 146 | .catch(function (err) { 147 | done.fail(err.message); 148 | }); 149 | }); 150 | }); 151 | 152 | describe("i18n Test", function () { 153 | it("Get EN", function (done) { 154 | 155 | ghRouting.i18n() 156 | .then(function (json) { 157 | expect(json.en['web.hike']).toEqual('Hike'); 158 | done(); 159 | }) 160 | .catch(function (err) { 161 | done.fail(err.message); 162 | }); 163 | }); 164 | it("Get DE", function (done) { 165 | 166 | ghRouting.i18n({locale: 'de'}) 167 | .then(function (json) { 168 | expect(json.default['web.hike']).toEqual('Wandern'); 169 | done(); 170 | }) 171 | .catch(function (err) { 172 | done.fail(err.message); 173 | }); 174 | }); 175 | }); -------------------------------------------------------------------------------- /spec/helpers/config.js: -------------------------------------------------------------------------------- 1 | // Sign-up for free key on: https://graphhopper.com/#directions-api 2 | global.key = process.env.GHKEY; 3 | global.profile = "car"; 4 | 5 | // Some tests take longer than the default 5000ms of Jasmine 6 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; -------------------------------------------------------------------------------- /spec/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } -------------------------------------------------------------------------------- /src/GHInput.js: -------------------------------------------------------------------------------- 1 | GHInput = function (input, input2) { 2 | this.set(input, input2); 3 | }; 4 | 5 | GHInput.prototype.round = function (val, precision) { 6 | if (precision === undefined) 7 | precision = 1e6; 8 | return Math.round(val * precision) / precision; 9 | }; 10 | 11 | GHInput.prototype.setCoord = function (lat, lng) { 12 | this.lat = this.round(lat); 13 | this.lng = this.round(lng); 14 | this.input = this.toString(); 15 | }; 16 | 17 | GHInput.isObject = function (value) { 18 | var stringValue = Object.prototype.toString.call(value); 19 | return (stringValue.toLowerCase() === "[object object]"); 20 | }; 21 | 22 | GHInput.isString = function (value) { 23 | var stringValue = Object.prototype.toString.call(value); 24 | return (stringValue.toLowerCase() === "[object string]"); 25 | }; 26 | 27 | GHInput.prototype.set = function (strOrObject, input2) { 28 | if (input2) { 29 | this.setCoord(strOrObject, input2); 30 | return; 31 | } 32 | 33 | // either text or coordinates or object 34 | this.input = strOrObject; 35 | 36 | if (GHInput.isObject(strOrObject)) { 37 | this.setCoord(strOrObject.lat, strOrObject.lng); 38 | } else if (GHInput.isString(strOrObject)) { 39 | var index = strOrObject.indexOf(","); 40 | if (index >= 0) { 41 | this.lat = this.round(parseFloat(strOrObject.substr(0, index))); 42 | this.lng = this.round(parseFloat(strOrObject.substr(index + 1))); 43 | } 44 | } 45 | }; 46 | 47 | GHInput.prototype.toString = function () { 48 | if (this.lat !== undefined && this.lng !== undefined) 49 | return this.lat + "," + this.lng; 50 | return undefined; 51 | }; 52 | 53 | module.exports = GHInput; -------------------------------------------------------------------------------- /src/GHUtil.js: -------------------------------------------------------------------------------- 1 | let GHUtil = function () { 2 | }; 3 | 4 | GHUtil.prototype.clone = function (obj) { 5 | let newObj = {}; 6 | for (let prop in obj) { 7 | if (obj.hasOwnProperty(prop)) { 8 | newObj[prop] = obj[prop]; 9 | } 10 | } 11 | return newObj; 12 | }; 13 | 14 | GHUtil.prototype.decodePath = function (encoded, is3D) { 15 | let len = encoded.length; 16 | let index = 0; 17 | let array = []; 18 | let lat = 0; 19 | let lng = 0; 20 | let ele = 0; 21 | 22 | while (index < len) { 23 | let b; 24 | let shift = 0; 25 | let result = 0; 26 | do { 27 | b = encoded.charCodeAt(index++) - 63; 28 | result |= (b & 0x1f) << shift; 29 | shift += 5; 30 | } while (b >= 0x20); 31 | let deltaLat = ((result & 1) ? ~(result >> 1) : (result >> 1)); 32 | lat += deltaLat; 33 | 34 | shift = 0; 35 | result = 0; 36 | do { 37 | b = encoded.charCodeAt(index++) - 63; 38 | result |= (b & 0x1f) << shift; 39 | shift += 5; 40 | } while (b >= 0x20); 41 | let deltaLon = ((result & 1) ? ~(result >> 1) : (result >> 1)); 42 | lng += deltaLon; 43 | 44 | if (is3D) { 45 | // elevation 46 | shift = 0; 47 | result = 0; 48 | do { 49 | b = encoded.charCodeAt(index++) - 63; 50 | result |= (b & 0x1f) << shift; 51 | shift += 5; 52 | } while (b >= 0x20); 53 | let deltaEle = ((result & 1) ? ~(result >> 1) : (result >> 1)); 54 | ele += deltaEle; 55 | array.push([lng * 1e-5, lat * 1e-5, ele / 100]); 56 | } else 57 | array.push([lng * 1e-5, lat * 1e-5]); 58 | } 59 | // let end = new Date().getTime(); 60 | // console.log("decoded " + len + " coordinates in " + ((end - start) / 1000) + "s"); 61 | return array; 62 | }; 63 | 64 | GHUtil.prototype.extractError = function (res, url) { 65 | let msg; 66 | 67 | if (res && res.data) { 68 | msg = res.data; 69 | if (msg.hints && msg.hints[0] && msg.hints[0].message) 70 | msg = msg.hints[0].message; 71 | else if (msg.message) 72 | msg = msg.message; 73 | } else { 74 | msg = res; 75 | } 76 | 77 | return new Error(msg + " - for url " + url); 78 | }; 79 | 80 | GHUtil.prototype.isArray = function (value) { 81 | let stringValue = Object.prototype.toString.call(value); 82 | return (stringValue.toLowerCase() === "[object array]"); 83 | }; 84 | 85 | GHUtil.prototype.isObject = function (value) { 86 | let stringValue = Object.prototype.toString.call(value); 87 | return (stringValue.toLowerCase() === "[object object]"); 88 | }; 89 | 90 | GHUtil.prototype.isString = function (value) { 91 | return (typeof value === 'string'); 92 | }; 93 | 94 | module.exports = GHUtil; -------------------------------------------------------------------------------- /src/GraphHopperGeocoding.js: -------------------------------------------------------------------------------- 1 | let request = require('axios'); 2 | 3 | let GHUtil = require("./GHUtil"); 4 | let ghUtil = new GHUtil(); 5 | 6 | GraphHopperGeocoding = function (args, requestDefaults) { 7 | this.defaults = { 8 | debug: false, 9 | locale: "en" 10 | } 11 | if (requestDefaults) 12 | Object.keys(requestDefaults).forEach(key => { 13 | this.defaults[key] = requestDefaults[key]; 14 | }); 15 | 16 | this.key = args.key; 17 | this.host = args.host ? args.host : "https://graphhopper.com/api/1"; 18 | this.endpoint = args.endpoint ? args.endpoint : '/geocode'; 19 | this.timeout = args.timeout ? args.timeout : 10000; 20 | }; 21 | 22 | GraphHopperGeocoding.prototype.getParametersAsQueryString = function (args) { 23 | let qString = "locale=" + args.locale; 24 | 25 | if (args.query) { 26 | qString += "&q=" + encodeURIComponent(args.query); 27 | if (args.location_bias) 28 | qString += "&point=" + encodeURIComponent(args.location_bias.toString()); 29 | else if (args.point) 30 | qString += "&point=" + encodeURIComponent(args.point.toString()); 31 | } else { 32 | qString += "&reverse=true"; 33 | if (args.point) 34 | qString += "&point=" + encodeURIComponent(args.point.toString()); 35 | } 36 | 37 | if (args.debug) 38 | qString += "&debug=true"; 39 | 40 | if (args.limit) 41 | qString += "&limit=" + args.limit; 42 | 43 | return qString; 44 | }; 45 | 46 | GraphHopperGeocoding.prototype.doRequest = function (reqArgs) { 47 | if (!reqArgs) reqArgs = {} 48 | Object.keys(this.defaults).forEach(key => { 49 | if (!reqArgs[key]) reqArgs[key] = this.defaults[key]; 50 | }); 51 | let url = this.host + this.endpoint + "?" + this.getParametersAsQueryString(reqArgs) + "&key=" + this.key; 52 | let that = this; 53 | 54 | return new Promise(function (resolve, reject) { 55 | request 56 | .get(url, {timeout: that.timeout}) 57 | .then(res => { 58 | if (res.status !== 200) { 59 | reject(ghUtil.extractError(res, url)); 60 | } else if (res) { 61 | resolve(res.data); 62 | } 63 | }) 64 | .catch(err => { 65 | reject(ghUtil.extractError(err.response, url)); 66 | }); 67 | }); 68 | }; 69 | 70 | module.exports = GraphHopperGeocoding; -------------------------------------------------------------------------------- /src/GraphHopperIsochrone.js: -------------------------------------------------------------------------------- 1 | let request = require('axios'); 2 | 3 | let GHUtil = require("./GHUtil"); 4 | let ghUtil = new GHUtil(); 5 | 6 | GraphHopperIsochrone = function (args, requestDefaults) { 7 | this.defaults = { 8 | time_limit: 600, 9 | distance_limit: 0, 10 | buckets: 3, 11 | profile: "car", 12 | debug: false, 13 | reverse_flow: false 14 | }; 15 | if (requestDefaults) 16 | Object.keys(requestDefaults).forEach(key => { 17 | this.defaults[key] = requestDefaults[key]; 18 | }); 19 | 20 | this.key = args.key; 21 | this.host = args.host ? args.host : "https://graphhopper.com/api/1"; 22 | this.endpoint = args.endpoint ? args.endpoint : '/isochrone'; 23 | this.timeout = args.timeout ? args.timeout : 30000; 24 | }; 25 | 26 | GraphHopperIsochrone.prototype.getParametersAsQueryString = function (args) { 27 | let qString = "point=" + args.point; 28 | qString += "&time_limit=" + args.time_limit; 29 | qString += "&distance_limit=" + args.distance_limit; 30 | qString += "&buckets=" + args.buckets; 31 | qString += "&profile=" + args.profile; 32 | qString += "&reverse_flow=" + args.reverse_flow; 33 | 34 | if (args.debug) 35 | qString += "&debug=true"; 36 | 37 | return qString; 38 | }; 39 | 40 | GraphHopperIsochrone.prototype.doRequest = function (reqArgs) { 41 | Object.keys(this.defaults).forEach(key => { 42 | if (!reqArgs[key]) reqArgs[key] = this.defaults[key]; 43 | }); 44 | 45 | let url = this.host + this.endpoint + "?" + this.getParametersAsQueryString(reqArgs) + "&key=" + this.key; 46 | let that = this; 47 | 48 | return new Promise(function (resolve, reject) { 49 | 50 | request 51 | .get(url, {timeout: that.timeout}) 52 | .then(res => { 53 | if (res.status !== 200) { 54 | reject(ghUtil.extractError(res, url)); 55 | } else if (res) { 56 | resolve(res.data); 57 | } 58 | }) 59 | .catch(err => { 60 | reject(ghUtil.extractError(err.response, url)); 61 | }) 62 | }); 63 | }; 64 | 65 | module.exports = GraphHopperIsochrone; -------------------------------------------------------------------------------- /src/GraphHopperMapMatching.js: -------------------------------------------------------------------------------- 1 | let request = require('axios'); 2 | 3 | let GHUtil = require("./GHUtil"); 4 | let ghUtil = new GHUtil(); 5 | 6 | GraphHopperMapMatching = function (args, requestDefaults) { 7 | this.defaults = { 8 | profile: "car", 9 | gps_accuracy: 20, 10 | debug: false, 11 | max_visited_nodes: 3000, 12 | locale: "en", 13 | points_encoded: true, 14 | instructions: true, 15 | elevation: true, 16 | data_type: "json" 17 | } 18 | if (requestDefaults) 19 | Object.keys(requestDefaults).forEach(key => { 20 | this.defaults[key] = requestDefaults[key]; 21 | }); 22 | 23 | this.key = args.key; 24 | this.host = args.host ? args.host : "https://graphhopper.com/api/1"; 25 | this.endpoint = args.endpoint ? args.endpoint : '/match'; 26 | this.timeout = args.timeout ? args.timeout : 100000; 27 | }; 28 | 29 | GraphHopperMapMatching.prototype.getParametersAsQueryString = function (args) { 30 | let qString = "locale=" + args.locale; 31 | // TODO NOW: profile not yet supported in API 32 | qString += "&vehicle=" + args.profile; 33 | qString += "&gps_accuracy=" + args.gps_accuracy; 34 | qString += "&max_visited_nodes=" + args.max_visited_nodes; 35 | qString += "&type=" + args.data_type; 36 | qString += "&instructions=" + args.instructions; 37 | qString += "&points_encoded=" + args.points_encoded; 38 | qString += "&elevation=" + args.elevation; 39 | 40 | if (args.debug) 41 | qString += "&debug=true"; 42 | return qString; 43 | }; 44 | 45 | GraphHopperMapMatching.prototype.doRequest = function (content, reqArgs) { 46 | if (!reqArgs) reqArgs = {} 47 | Object.keys(this.defaults).forEach(key => { 48 | if (!reqArgs[key]) reqArgs[key] = this.defaults[key]; 49 | }); 50 | 51 | let url = this.host + this.endpoint + "?" + this.getParametersAsQueryString(reqArgs) + "&key=" + this.key; 52 | let timeout = this.timeout; 53 | 54 | return new Promise(function (resolve, reject) { 55 | request.post(url, content, { 56 | timeout: timeout, 57 | headers: {'Content-Type': 'application/xml'} 58 | }) 59 | .then(res => { 60 | if (res.status !== 200) { 61 | reject(ghUtil.extractError(res, url)); 62 | } else if (res) { 63 | 64 | if (res.data.paths) { 65 | for (let i = 0; i < res.data.paths.length; i++) { 66 | let path = res.data.paths[i]; 67 | // convert encoded polyline to geo json 68 | if (path.points_encoded) { 69 | let tmpArray = ghUtil.decodePath(path.points, reqArgs.elevation); 70 | path.points = { 71 | "type": "LineString", 72 | "coordinates": tmpArray 73 | }; 74 | 75 | // for now delete this 76 | delete path.snapped_waypoints; 77 | } 78 | } 79 | } 80 | resolve(res.data); 81 | } 82 | }) 83 | .catch(err => { 84 | reject(ghUtil.extractError(err.response, url)); 85 | }); 86 | }); 87 | }; 88 | 89 | module.exports = GraphHopperMapMatching; -------------------------------------------------------------------------------- /src/GraphHopperMatrix.js: -------------------------------------------------------------------------------- 1 | let request = require('axios'); 2 | 3 | let GHUtil = require("./GHUtil"); 4 | let ghUtil = new GHUtil(); 5 | 6 | GraphHopperMatrix = function (args, requestDefaults) { 7 | this.defaults = { 8 | profile: "car", 9 | debug: false, 10 | out_arrays: ["times"], 11 | 12 | } 13 | if (requestDefaults) 14 | Object.keys(requestDefaults).forEach(key => { 15 | this.defaults[key] = requestDefaults[key]; 16 | }); 17 | 18 | this.key = args.key; 19 | this.host = args.host ? args.host : "https://graphhopper.com/api/1"; 20 | this.endpoint = args.endpoint ? args.endpoint : '/matrix'; 21 | this.timeout = args.timeout ? args.timeout : 30000; 22 | }; 23 | 24 | GraphHopperMatrix.prototype.doRequest = function (reqArgs) { 25 | Object.keys(this.defaults).forEach(key => { 26 | if (!reqArgs[key]) reqArgs[key] = this.defaults[key]; 27 | }); 28 | 29 | if (!reqArgs.from_points && !reqArgs.to_points) { 30 | reqArgs.from_points = reqArgs.points; 31 | reqArgs.to_points = reqArgs.points; 32 | delete reqArgs["points"]; 33 | } 34 | 35 | let url = this.host + this.endpoint + "?key=" + this.key; 36 | let that = this; 37 | 38 | return new Promise(function (resolve, reject) { 39 | request.post(url, reqArgs, { 40 | timeout: that.timeout, 41 | headers: {'Content-Type': 'application/json'} 42 | }) 43 | .then(res => { 44 | if (res.status !== 200) { 45 | reject(ghUtil.extractError(res, url)); 46 | } else if (res) { 47 | resolve(res.data); 48 | } 49 | }) 50 | .catch(err => { 51 | reject(ghUtil.extractError(err.response, url)); 52 | }); 53 | }); 54 | }; 55 | 56 | GraphHopperMatrix.prototype.toHtmlTable = function (request, doubleArray) { 57 | let to_points = request.to_points, from_points = request.from_points; 58 | let htmlOut = ""; 59 | 60 | // header => to points 61 | htmlOut += ""; 62 | 63 | // upper left corner: 64 | htmlOut += ""; 65 | 66 | // header with to points 67 | for (let idxTo in to_points) { 68 | htmlOut += ""; 69 | } 70 | htmlOut += ""; 71 | 72 | for (let idxFrom in doubleArray) { 73 | htmlOut += ""; 74 | htmlOut += ""; 75 | let res = doubleArray[idxFrom]; 76 | for (let idxTo in res) { 77 | let mapsURL = "https://graphhopper.com/maps?" 78 | + "point=" + encodeURIComponent(from_points[idxFrom][1] + "," + from_points[idxFrom][0]) 79 | + "&point=" + encodeURIComponent(to_points[idxTo][1] + "," + to_points[idxTo][0]) 80 | + "&profile=" + request.profile; 81 | 82 | htmlOut += ""; 83 | } 84 | htmlOut += "\n"; 85 | } 86 | htmlOut += "
      ↓ from \ to →" + to_points[idxTo][1] + "," + to_points[idxTo][0] + "
      " + from_points[idxFrom][1] + "," + from_points[idxFrom][0] + " " + res[idxTo] + "
      "; 87 | return htmlOut; 88 | }; 89 | 90 | module.exports = GraphHopperMatrix; -------------------------------------------------------------------------------- /src/GraphHopperOptimization.js: -------------------------------------------------------------------------------- 1 | let request = require('axios'); 2 | 3 | let GHUtil = require("./GHUtil"); 4 | let ghUtil = new GHUtil(); 5 | 6 | GraphHopperOptimization = function (args) { 7 | this.key = args.key; 8 | this.host = args.host ? args.host : "https://graphhopper.com/api/1"; 9 | this.endpoint = args.endpoint ? args.endpoint : '/vrp'; 10 | this.timeout = args.timeout ? args.timeout : 10000; 11 | this.waitInMillis = args.waitInMillis ? args.waitInMillis : 1000; 12 | this.postTimeout = args.postTimeout ? args.postTimeout : 10000; 13 | }; 14 | 15 | GraphHopperOptimization.prototype.doVRPRequest = function (points, vehicles) { 16 | let that = this; 17 | 18 | let firstPoint = points[0]; 19 | let servicesArray = []; 20 | for (let pointIndex in points) { 21 | if (pointIndex < 1) 22 | continue; 23 | let point = points[pointIndex]; 24 | let obj = { 25 | "id": "_" + pointIndex, 26 | "type": "pickup", 27 | "name": "maintenance " + pointIndex, 28 | "address": { 29 | "location_id": "_location_" + pointIndex, 30 | "lon": point[0], 31 | "lat": point[1] 32 | } 33 | }; 34 | servicesArray.push(obj); 35 | } 36 | 37 | let list = []; 38 | for (let i = 0; i < vehicles; i++) { 39 | list.push({ 40 | "vehicle_id": "_vehicle_" + i, 41 | "start_address": { 42 | "location_id": "_start_location", 43 | "lon": firstPoint[0], 44 | "lat": firstPoint[1] 45 | }, 46 | "type_id": "_vtype_1" 47 | }); 48 | } 49 | 50 | let jsonInput = { 51 | "algorithm": { 52 | "problem_type": "min-max" 53 | }, 54 | "vehicles": list, 55 | "vehicle_types": [{ 56 | "type_id": "_vtype_1", 57 | "profile": "car" 58 | }], 59 | "services": servicesArray 60 | 61 | }; 62 | //console.log(jsonInput); 63 | 64 | return that.doRequest(jsonInput); 65 | }; 66 | 67 | GraphHopperOptimization.prototype.doRawRequest = function (jsonInput) { 68 | let that = this; 69 | 70 | return new Promise(function (resolve, reject) { 71 | let postURL = that.host + that.endpoint + "/optimize?key=" + that.key; 72 | request.post(postURL, jsonInput, { 73 | timeout: that.postTimeout, 74 | headers: {'Content-Type': 'application/json'} 75 | }) 76 | .then(res => { 77 | if (res.status !== 200) { 78 | reject(ghUtil.extractError(res, postURL)); 79 | } else if (res) { 80 | let solutionUrl = that.host + that.endpoint + "/solution/" + res.data.job_id + "?key=" + that.key; 81 | let timerRet; 82 | 83 | let pollTrigger = function () { 84 | // console.log("poll solution " + solutionUrl); 85 | request.get(solutionUrl, {timeout: that.timeout}) 86 | .then(res => { 87 | if (res.status !== 200 || res.data === undefined) { 88 | clearInterval(timerRet); 89 | reject(ghUtil.extractError(res, solutionUrl)); 90 | } else if (res) { 91 | //console.log(res.body); 92 | if (res.data.status === "finished") { 93 | //console.log("finished"); 94 | clearInterval(timerRet); 95 | resolve(res.data); 96 | } else if (res.data.message) { 97 | clearInterval(timerRet); 98 | resolve(res.data); 99 | } 100 | } 101 | }); 102 | }; 103 | 104 | if (that.waitInMillis > 0) 105 | timerRet = setInterval(pollTrigger, that.waitInMillis); 106 | else 107 | pollTrigger(); 108 | 109 | } 110 | }) 111 | .catch(err => { 112 | reject(ghUtil.extractError(err.response, postURL)); 113 | }); 114 | }); 115 | }; 116 | 117 | GraphHopperOptimization.prototype.doRequest = function (jsonInput) { 118 | let vehicleTypeProfileMap = {}; 119 | let vehicleTypeMap = {}; 120 | let vehicleProfileMap = {}; 121 | let serviceMap = {}; 122 | let shipmentMap = {}; 123 | let locationMap = {}; 124 | let hasGeometries = false; 125 | let hasCustomCostMatrix = false; 126 | 127 | if (jsonInput.cost_matrices && jsonInput.cost_matrices.length > 0) 128 | hasCustomCostMatrix = true; 129 | 130 | if (jsonInput.configuration && !hasCustomCostMatrix) 131 | if (jsonInput.configuration.routing.calc_points === true) 132 | hasGeometries = true; 133 | 134 | if (!hasGeometries) { 135 | if (!jsonInput.configuration && !hasCustomCostMatrix) { 136 | jsonInput.configuration = {"routing": {"calc_points": true}}; 137 | } 138 | } 139 | 140 | if (jsonInput.vehicle_types) { 141 | for (let typeIndex = 0; typeIndex < jsonInput.vehicle_types.length; typeIndex++) { 142 | let type = jsonInput.vehicle_types[typeIndex]; 143 | vehicleTypeProfileMap[type.type_id] = type.profile; 144 | vehicleTypeMap[type.type_id] = type; 145 | } 146 | } 147 | 148 | if (jsonInput.services) { 149 | for (let serviceIndex = 0; serviceIndex < jsonInput.services.length; serviceIndex++) { 150 | let service = jsonInput.services[serviceIndex]; 151 | locationMap[service.address.location_id] = service.address; 152 | serviceMap[service.id] = service; 153 | } 154 | } 155 | 156 | if (jsonInput.shipments) { 157 | for (let shipmentIndex = 0; shipmentIndex < jsonInput.shipments.length; shipmentIndex++) { 158 | let shipment = jsonInput.shipments[shipmentIndex]; 159 | locationMap[shipment.pickup.address.location_id] = shipment.pickup.address; 160 | locationMap[shipment.delivery.address.location_id] = shipment.delivery.address; 161 | shipmentMap[shipment.id] = shipment; 162 | } 163 | } 164 | 165 | let breakMap = {}; 166 | let vehicleMap = {}; 167 | if (jsonInput.vehicles) { 168 | for (let vehicleIndex = 0; vehicleIndex < jsonInput.vehicles.length; vehicleIndex++) { 169 | let vehicle = jsonInput.vehicles[vehicleIndex]; 170 | vehicleMap[vehicle.vehicle_id] = vehicle; 171 | let profile = null; 172 | if (vehicle.type_id !== null) { 173 | profile = vehicleTypeProfileMap[vehicle.type_id]; 174 | if (profile !== null) { 175 | vehicleProfileMap[vehicle.vehicle_id] = profile; 176 | } else { 177 | vehicleProfileMap[vehicle.vehicle_id] = "car"; 178 | } 179 | } else 180 | vehicleProfileMap[vehicle.vehicle_id] = "car"; 181 | 182 | if (vehicle.start_address) { 183 | locationMap[vehicle.start_address.location_id] = vehicle.start_address; 184 | } 185 | if (vehicle.end_address) { 186 | locationMap[vehicle.end_address.location_id] = vehicle.end_address; 187 | } 188 | if (vehicle.break) { 189 | let break_id = vehicle.vehicle_id + "_break"; 190 | breakMap[break_id] = vehicle.break; 191 | } 192 | } 193 | } 194 | 195 | let promise = this.doRawRequest(jsonInput); 196 | promise.then(function (json) { 197 | if (json.solution) { 198 | let sol = json.solution; 199 | json.raw_solution = JSON.parse(JSON.stringify(sol)); 200 | sol["calc_points"] = hasGeometries; 201 | for (let routeIndex = 0; routeIndex < sol.routes.length; routeIndex++) { 202 | let route = sol.routes[routeIndex]; 203 | let vehicleId = route.vehicle_id; 204 | let profile = vehicleProfileMap[vehicleId]; 205 | route["profile"] = profile; 206 | for (let actIndex = 0; actIndex < route.activities.length; actIndex++) { 207 | let act = route.activities[actIndex]; 208 | act["address"] = locationMap[act.location_id]; 209 | if (act.id) { 210 | let driverBreak = breakMap[act.id]; 211 | // console.log(act.id + " " + driverBreak); 212 | if (driverBreak) { 213 | act["break"] = breakMap[act.id]; 214 | } else if (serviceMap[act.id]) { 215 | act["service"] = serviceMap[act.id]; 216 | } else if (shipmentMap[act.id]) { 217 | act["shipment"] = shipmentMap[act.id]; 218 | } 219 | } else { 220 | let vehicle = vehicleMap[vehicleId]; 221 | act["vehicle"] = vehicle; 222 | act["vehicle_type"] = vehicleTypeMap[vehicle.type_id]; 223 | } 224 | } 225 | } 226 | let unassignedServices = new Array(); 227 | for (let i = 0; i < sol.unassigned.services.length; i++) { 228 | let serviceId = sol.unassigned.services[i]; 229 | unassignedServices.push(serviceMap[serviceId]); 230 | unassignedServices.push(serviceMap[serviceId]); 231 | } 232 | sol["unassigned_services"] = unassignedServices; 233 | 234 | let unassignedShipments = new Array(); 235 | for (let i = 0; i < sol.unassigned.shipments.length; i++) { 236 | let shipmentId = sol.unassigned.shipments[i]; 237 | unassignedShipments.push(shipmentMap[shipmentId]); 238 | } 239 | sol["unassigned_shipments"] = unassignedShipments; 240 | } 241 | return json; 242 | }); 243 | return promise; 244 | 245 | }; 246 | 247 | module.exports = GraphHopperOptimization; -------------------------------------------------------------------------------- /src/GraphHopperRouting.js: -------------------------------------------------------------------------------- 1 | let request = require('axios'); 2 | var GHUtil = require("./GHUtil"); 3 | let ghUtil = new GHUtil(); 4 | 5 | GraphHopperRouting = function (args, requestDefaults) { 6 | this.defaults = { 7 | profile: "car", 8 | debug: false, 9 | locale: "en", 10 | points_encoded: true, 11 | instructions: true, 12 | elevation: true, 13 | optimize: "false" 14 | } 15 | if (requestDefaults) 16 | Object.keys(requestDefaults).forEach(key => { 17 | this.defaults[key] = requestDefaults[key]; 18 | }); 19 | 20 | // required API key 21 | this.key = args.key; 22 | this.host = args.host ? args.host : "https://graphhopper.com/api/1"; 23 | this.endpoint = args.endpoint ? args.endpoint : '/route'; 24 | this.timeout = args.timeout ? args.timeout : 10000; 25 | this.turn_sign_map = args.turn_sign_map ? args.turn_sign_map : { 26 | "-6": "leave roundabout", 27 | "-3": "turn sharp left", 28 | "-2": "turn left", 29 | "-1": "turn slight left", 30 | 0: "continue", 31 | 1: "turn slight right", 32 | 2: "turn right", 33 | 3: "turn sharp right", 34 | 4: "finish", 35 | 5: "reached via point", 36 | 6: "enter roundabout" 37 | }; 38 | }; 39 | 40 | /** 41 | * Execute the routing request using the provided args. 42 | */ 43 | GraphHopperRouting.prototype.doRequest = function (reqArgs) { 44 | Object.keys(this.defaults).forEach(key => { 45 | if (!reqArgs[key]) reqArgs[key] = this.defaults[key]; 46 | }); 47 | let url = this.host + this.endpoint + "?key=" + this.key; 48 | let that = this; 49 | 50 | return new Promise((resolve, reject) => { 51 | request.post(url, reqArgs, { 52 | timeout: that.timeout, 53 | headers: {'Content-Type': 'application/json'} 54 | }) 55 | .then(res => { 56 | if (res.status !== 200) { 57 | reject(ghUtil.extractError(res, url)); 58 | return; 59 | } 60 | if (res.data.paths) { 61 | for (let i = 0; i < res.data.paths.length; i++) { 62 | let path = res.data.paths[i]; 63 | // convert encoded polyline to geo json 64 | if (path.points_encoded) { 65 | let tmpArray = ghUtil.decodePath(path.points, reqArgs.elevation); 66 | path.points = { 67 | "type": "LineString", "coordinates": tmpArray 68 | }; 69 | 70 | let tmpSnappedArray = ghUtil.decodePath(path.snapped_waypoints, reqArgs.elevation); 71 | path.snapped_waypoints = { 72 | "type": "LineString", "coordinates": tmpSnappedArray 73 | }; 74 | } 75 | if (path.instructions) { 76 | for (let j = 0; j < path.instructions.length; j++) { 77 | // Add a LngLat to every instruction 78 | let interval = path.instructions[j].interval; 79 | // The second parameter of slice is non inclusive, therefore we have to add +1 80 | path.instructions[j].points = path.points.coordinates.slice(interval[0], interval[1] + 1); 81 | } 82 | } 83 | } 84 | } 85 | resolve(res.data); 86 | }) 87 | .catch(err => { 88 | reject(ghUtil.extractError(err.response, url)); 89 | }); 90 | }); 91 | }; 92 | 93 | GraphHopperRouting.prototype.info = function () { 94 | let that = this; 95 | 96 | return new Promise((resolve, reject) => { 97 | let url = that.host + "/info?key=" + that.key; 98 | 99 | request.get(url, {timeout: that.timeout, headers: {'Content-Type': 'application/json'}}) 100 | .then(res => { 101 | if (res.status !== 200) { 102 | reject(ghUtil.extractError(res, url)); 103 | return; 104 | } 105 | resolve(res.data); 106 | }) 107 | .catch(err => { 108 | console.log(err) 109 | reject(ghUtil.extractError(err.response, url)); 110 | }); 111 | }); 112 | }; 113 | 114 | GraphHopperRouting.prototype.i18n = function (args) { 115 | let locale = args && args.locale ? args.locale : this.defaults.locale; 116 | let that = this; 117 | 118 | return new Promise((resolve, reject) => { 119 | let url = that.host + "/i18n/" + locale + "?key=" + that.key; 120 | 121 | request.get(url, {timeout: that.timeout, headers: {'Content-Type': 'application/json'}}) 122 | .then(res => { 123 | if (res.status !== 200) { 124 | reject(ghUtil.extractError(res, url)); 125 | return; 126 | } 127 | resolve(res.data); 128 | }) 129 | .catch(err => { 130 | reject(ghUtil.extractError(err.response, url)); 131 | }); 132 | }); 133 | }; 134 | 135 | GraphHopperRouting.prototype.getTurnText = function (sign) { 136 | return this.turn_sign_map[sign]; 137 | }; 138 | 139 | module.exports = GraphHopperRouting; -------------------------------------------------------------------------------- /src/main-template.js: -------------------------------------------------------------------------------- 1 | var GHUtil = require('./GHUtil.js'); 2 | var GHInput = require('./GHInput.js'); 3 | var GraphHopperGeocoding = require('./GraphHopperGeocoding.js'); 4 | var GraphHopperIsochrone = require('./GraphHopperIsochrone.js'); 5 | var GraphHopperMapMatching = require('./GraphHopperMapMatching.js'); 6 | var GraphHopperMatrix = require('./GraphHopperMatrix.js'); 7 | var GraphHopperOptimization = require('./GraphHopperOptimization.js'); 8 | var GraphHopperRouting = require('./GraphHopperRouting.js'); 9 | 10 | var GraphHopper = { 11 | "Util": GHUtil, 12 | "Input": GHInput, 13 | "Geocoding": GraphHopperGeocoding, 14 | "Isochrone": GraphHopperIsochrone, 15 | "MapMatching": GraphHopperMapMatching, 16 | "Optimization": GraphHopperOptimization, 17 | "Routing": GraphHopperRouting, 18 | "Matrix": GraphHopperMatrix 19 | }; 20 | 21 | 22 | // define GraphHopper for Node module pattern loaders, including Browserify 23 | if (typeof module === 'object' && typeof module.exports === 'object') { 24 | module.exports.GraphHopper = GraphHopper; 25 | 26 | // define GraphHopper as an AMD module 27 | } else if (typeof define === 'function' && define.amd) { 28 | define(GraphHopper); 29 | } 30 | 31 | if (typeof window !== 'undefined') { 32 | window.GraphHopper = GraphHopper; 33 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = (env, argv) => { 5 | const isProduction = argv.mode === 'production'; 6 | 7 | return { 8 | entry: './src/main-template.js', 9 | output: { 10 | filename: 'graphhopper-client.js', 11 | path: path.resolve(__dirname, 'dist'), 12 | clean: true, 13 | }, 14 | mode: isProduction ? 'production' : 'development', 15 | plugins: [ 16 | new HtmlWebpackPlugin({ 17 | template: './index.html', 18 | }), 19 | ], 20 | devServer: { 21 | static: [ 22 | { 23 | directory: path.join(__dirname, 'dist'), 24 | }, 25 | { 26 | directory: path.join(__dirname, 'js'), 27 | publicPath: '/js', 28 | }, 29 | { 30 | directory: path.join(__dirname, 'css'), 31 | publicPath: '/css', 32 | }, 33 | { 34 | directory: path.join(__dirname, 'img'), 35 | publicPath: '/img', 36 | }, 37 | { 38 | directory: path.join(__dirname, 'route-optimization-examples'), 39 | publicPath: '/route-optimization-examples', 40 | }, 41 | { 42 | directory: path.join(__dirname, 'map-matching-examples'), 43 | publicPath: '/map-matching-examples', 44 | }, 45 | ], 46 | compress: true, 47 | port: 3000, 48 | }, 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.js$/, 53 | exclude: /node_modules/, 54 | use: { 55 | loader: 'babel-loader', 56 | }, 57 | }, 58 | ], 59 | }, 60 | }}; --------------------------------------------------------------------------------