├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ ├── ci-tests.yml │ ├── deploy-docs.yml │ └── publish.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.cjs ├── docker-compose.yml ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── 00_quickstart.md │ └── api │ │ ├── _category_.yml │ │ ├── classes │ │ └── _category_.yml │ │ ├── index.md │ │ ├── interfaces │ │ └── _category_.yml │ │ └── modules.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ └── img │ │ ├── braces.svg │ │ ├── docusaurus.png │ │ ├── settings-off.svg │ │ └── switch-3.svg └── tsconfig.json ├── jest.config.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── core │ ├── BaseRouter.ts │ ├── Client.ts │ ├── Direction.ts │ ├── Isochrone.ts │ ├── LICENSE │ ├── Matrix.ts │ ├── README.md │ ├── error.ts │ ├── index.ts │ ├── json.ts │ ├── options.ts │ ├── package.json │ └── tsconfig.json ├── graphhopper │ ├── LICENSE │ ├── README.md │ ├── graphhopper.test.ts │ ├── index.ts │ ├── package.json │ ├── parameters.ts │ └── tsconfig.json ├── ors │ ├── LICENSE │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── parameters.ts │ └── tsconfig.json ├── osrm │ ├── LICENSE │ ├── README.md │ ├── index.ts │ ├── osrm.test.ts │ ├── package.json │ ├── parameters.ts │ └── tsconfig.json └── valhalla │ ├── LICENSE │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── parameters.ts │ ├── tsconfig.json │ └── valhalla.test.ts ├── test_data └── andorra-latest.osm.pbf ├── tsconfig.doc.json ├── tsconfig.eslint.json ├── tsconfig.json └── util └── error.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | tab_width = 4 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | 4 | overrides: [ 5 | { 6 | files: ["*.ts"], 7 | parserOptions: { 8 | project: "./tsconfig.eslint.json", 9 | ecmaVersion: 2020, 10 | tsconfigRootDir: __dirname, 11 | sourceType: "module", 12 | }, 13 | rules: { 14 | "tsdoc/syntax": "warn", 15 | "@typescript-eslint/no-extra-semi": "off", 16 | }, 17 | extends: [ 18 | "eslint:recommended", 19 | "plugin:@typescript-eslint/eslint-recommended", 20 | "plugin:@typescript-eslint/recommended", 21 | ], 22 | }, 23 | ], 24 | env: { 25 | node: true, 26 | es6: true, 27 | }, 28 | plugins: ["@typescript-eslint", "eslint-plugin-tsdoc"], 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/ci-tests.yml: -------------------------------------------------------------------------------- 1 | name: Test lib 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - "*.md" 9 | workflow_dispatch: 10 | pull_request: 11 | branches: 12 | - main 13 | paths-ignore: 14 | - ".gitignore" 15 | - "**.md" 16 | 17 | jobs: 18 | tests: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | docker_tags: [latest] 23 | node_version: [16, 18] 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node_version }} 29 | - name: Check formatting 30 | run: | 31 | npm ci 32 | npm run format-check 33 | - name: Docker run containers and tests 34 | env: 35 | GRAPHHOPPER_API_KEY: ${{ secrets.GRAPHHOPPER_API_KEY }} 36 | run: | 37 | docker compose up -d 38 | sleep 10 39 | npm test 40 | docker compose down 41 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs to GH Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - "/test_data/" 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | docker_tags: [latest] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v3 19 | with: 20 | node-version: "16.x" 21 | - name: Build Docusaurus website 22 | run: | 23 | npm ci 24 | cd docs 25 | npm ci 26 | npm run build 27 | - name: Deploy to GitHub Pages 28 | if: success() 29 | uses: crazy-max/ghaction-github-pages@v2 30 | with: 31 | target_branch: gh-pages 32 | build_dir: docs/build 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches-ignore: 5 | - "*" 6 | tags: 7 | - "*" 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* 18 | 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: "16.x" 22 | registry-url: "https://registry.npmjs.org" 23 | 24 | - name: Install package and publish via lerna 25 | run: | 26 | npm ci 27 | npm run build 28 | npx lerna publish from-git --no-push --force-publish=* --yes 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .vscode/ 107 | 108 | .idea/ 109 | test_data 110 | !test_data/andorra-latest.osm.pbf 111 | !test_data/docker-compose.yml 112 | docs/docs/ 113 | .secrets 114 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit ${1} 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run format -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | .vscode/ 106 | /test_data/ 107 | .secrets 108 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { "trailingComma": "es5", "tabWidth": 4, "semi": false } 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | **Unreleased** is available in Github's `main` branch, but not on npm. 9 | 10 | ## **Unreleased** 11 | 12 | ### Added 13 | 14 | - Expose parse\* methods as static methods for Valhalla ([#35](https://github.com/gis-ops/routingjs/pull/35)) 15 | - This Changelog ;-) ([#35](https://github.com/gis-ops/routingjs/pull/35)) 16 | - `/trace_route` support for Valhalla [#34](https://github.com/gis-ops/routingjs/pull/34) 17 | - Basic waypoint support (mainly for Valhalla) [#40](https://github.com/gis-ops/routingjs/pull/40) 18 | 19 | ### Fixed 20 | 21 | - Remove `directionsOpts` as top level options in `ValhallaTraceRouteOpts` ([#35](https://github.com/gis-ops/routingjs/pull/35)) 22 | - Remove User-Agent header ([#33](https://github.com/gis-ops/routingjs/pull/33)) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GIS • OPS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RoutingJS 2 | 3 | ![example workflow](https://github.com/gis-ops/routingjs/actions/workflows/ci-tests.yml/badge.svg) 4 | 5 | **One lib to route them all** : _RoutingJS_ is a promise-based web client for accessing different 6 | routing web services, analogous to our Python client [routingpy](https://github.com/gis-ops/routing-py). Get unified access to several routing services with the power of modern asynchronous JavaScript fully typed thanks to the power of TypeScript! 7 | 8 | We currently support the following routers: 9 | 10 | - [Valhalla](https://github.com/valhalla/valhalla) 11 | - [OSRM](http://project-osrm.org) 12 | - [OpenRouteService](https://openrouteservice.org) 13 | - [GraphHopper](https://graphhopper.com) 14 | 15 | Future support for other service (like the ones _routingpy_ is supporting) might come at a later stage. 16 | 17 | ## Installation 18 | 19 | Router clients are installable individually: 20 | 21 | ``` 22 | npm install @routingjs/valhalla 23 | ``` 24 | 25 | ## Examples 26 | 27 | RoutingJS offers `directions`, `matrix` and `reachability` methods where available (e.g. OSRM does not support reachability/isochrones). 28 | 29 | ```js 30 | import { Valhalla } from "@routingjs/valhalla" 31 | 32 | const v = new Valhalla() // URL defaults to http://valhalla1.openstreetmap.de 33 | v.directions( 34 | [ 35 | [47.380742, 8.512516], // pass as [lat, lon] 36 | [47.359467, 8.557835], 37 | ], 38 | "auto" 39 | ).then((d) => { 40 | // do stuff with the directions response 41 | d.directions.forEach((direction) => { 42 | console.log(direction.feature) // get the route as GeoJSON feature 43 | console.log(direction.feature.properties.distance) // get the route distance 44 | console.log(direction.feature.properties.duration) // get the route duration 45 | }) 46 | }) 47 | 48 | v.reachability([47.380742, 8.512516], "pedestrian", [30, 90]).then((i) => { 49 | i.isochrones.forEach((isochrone) => { 50 | console.log(i.interval) // 30/90 51 | console.log(i.intervalType) // "time" 52 | console.log(i.feature) // GeoJSON feature 53 | }) 54 | }) 55 | 56 | v.matrix( 57 | [ 58 | [47.380742, 8.512516], // pass as [lat, lon] 59 | [47.359467, 8.557835], 60 | ], 61 | "auto" 62 | ).then((m) => { 63 | console.log(m.durations) // get the durations array for each location 64 | console.log(m.distances) // get the distances array for each location 65 | }) 66 | ``` 67 | 68 | ## Documentation 69 | 70 | See [here](https://gis-ops.github.io/routingjs/) for the full documentation. 71 | 72 | ## Contributing 73 | 74 | Contributions are welcome! We are open to improvements of existing clients, as well as additions of 75 | new ones :-) 76 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']} 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | ors: 4 | container_name: ors-tests 5 | ports: 6 | - "8080:8080" 7 | image: ghcr.io/gis-ops/openrouteservice:latest 8 | volumes: 9 | - ./test_data/andorra-latest.osm.pbf:/ors-core/data/osm_file.pbf 10 | environment: 11 | - "JAVA_OPTS=-Djava.awt.headless=true -server -XX:TargetSurvivorRatio=75 -XX:SurvivorRatio=64 -XX:MaxTenuringThreshold=3 -XX:+UseG1GC -XX:+ScavengeBeforeFullGC -XX:ParallelGCThreads=4 -Xms1g -Xmx2g" 12 | - "CATALINA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9001 -Dcom.sun.management.jmxremote.rmi.port=9001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost" 13 | valhalla: 14 | container_name: valhalla-tests 15 | image: ghcr.io/gis-ops/docker-valhalla/valhalla:latest 16 | ports: 17 | - "8002:8002" 18 | volumes: 19 | - ./test_data/:/custom_files 20 | osrm: 21 | container_name: osrm-tests 22 | image: osrm/osrm-backend:latest 23 | ports: 24 | - "5000:5000" 25 | volumes: 26 | - ./test_data/:/data 27 | command: '/bin/bash -c "osrm-extract -p /opt/car.lua /data/andorra-latest.osm.pbf && osrm-partition /data/andorra-latest.osrm && osrm-customize /data/andorra-latest.osrm && osrm-routed --algorithm=MLD /data/andorra-latest.osrm"' 28 | graphhopper: 29 | container_name: graphhopper-tests 30 | image: israelhikingmap/graphhopper:6.2 31 | ports: 32 | - "8989:8989" 33 | volumes: 34 | - ./test_data/:/graphhopper/data 35 | command: "-i data/andorra-latest.osm.pbf --host 0.0.0.0" 36 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/00_quickstart.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | ## Installation 4 | 5 | Install single client classes: 6 | 7 | ```sh 8 | npm i @routingjs/valhalla @routingjs/osrm @routingjs/ors @routingjs/graphhopper 9 | ``` 10 | 11 | ## Directions 12 | 13 | All clients support the `.directions()` method: 14 | 15 | ```js 16 | import { Valhalla } from "@routingjs/valhalla" 17 | 18 | const v = new Valhalla() // URL defaults to http://valhalla1.openstreetmap.de 19 | v.directions( 20 | [ 21 | [8.512516, 47.380742], 22 | [8.557835, 47.359467], 23 | ], 24 | "auto" 25 | ).then((d) => { 26 | // do stuff with the directions response 27 | d.directions.forEach((direction) => { 28 | console.log(direction.feature) 29 | }) 30 | }) 31 | ``` 32 | 33 | The result's `directions` property is an array of at least one route as GeoJSON LineString feature, and more if alternatives have been requested. 34 | 35 | If you want to access the response as sent by the routing server, you can always access it using the `raw` property. 36 | 37 | ## Matrix 38 | 39 | > Hint: GraphHopper only supports the `/matrix` endpoint in their own hosted version. 40 | 41 | ```js 42 | import { Valhalla } from "@routingjs/valhalla" 43 | 44 | const v = new Valhalla() // URL defaults to http://valhalla1.openstreetmap.de 45 | 46 | v.matrix( 47 | [ 48 | [1.51886, 42.5063], 49 | [1.53789, 42.51007], 50 | ], 51 | "auto" 52 | ).then((m) => { 53 | console.log(m.durations) 54 | }) 55 | ``` 56 | 57 | The returned `Matrix` has `durations` and `distances` properties, and you can access the response via its `raw` property. 58 | 59 | ## Reachability 60 | 61 | The `.reachability()` method is analogous to isochrone/isodistance analyses. 62 | 63 | > Note: OSRM does not support this method. 64 | 65 | ```js 66 | import { Valhalla } from "@routingjs/valhalla" 67 | 68 | const v = new Valhalla() // URL defaults to http://valhalla1.openstreetmap.de 69 | 70 | v.reachability([[1.51886, 42.5063]], "auto").then((i) => { 71 | i.isochrones.forEach((isochrone) => console.log(isochrone.feature)) 72 | }) 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/docs/api/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "API" -------------------------------------------------------------------------------- /docs/docs/api/classes/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Classes" 2 | position: 3 -------------------------------------------------------------------------------- /docs/docs/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "index" 3 | title: "routingjs" 4 | sidebar_label: "Readme" 5 | sidebar_position: 0 6 | custom_edit_url: null 7 | --- 8 | 9 | # RoutingJS 10 | 11 | **One lib to route them all** : _RoutingJS_ is a promise-based web client for accessing different 12 | routing web services, analogous to our Python client [routingpy](https://github.com/gis-ops/routing-py). Get unified access to several routing services with the power of modern asynchronous JavaScript fully typed thanks to the power of TypeScript! 13 | 14 | We currently support the following routers: 15 | 16 | - [Valhalla](https://github.com/valhalla/valhalla) 17 | - [OSRM](http://project-osrm.org) 18 | - [OpenRouteService](https://openrouteservice.org) 19 | - [GraphHopper](https://graphhopper.com) 20 | 21 | Future support for other service (like the ones _routingpy_ is supporting) might come at a later stage. 22 | 23 | ## Installation 24 | 25 | Router clients are installable individually: 26 | 27 | ``` 28 | npm install @routingjs/valhalla 29 | ``` 30 | 31 | ## Examples 32 | 33 | ```js 34 | import { Valhalla } from "@routingjs/valhalla" 35 | 36 | const v = new Valhalla() // URL defaults to http://valhalla1.openstreetmap.de 37 | v.directions( 38 | [ 39 | [8.512516, 47.380742], 40 | [8.557835, 47.359467], 41 | ], 42 | "auto" 43 | ).then((d) => { 44 | // do stuff with the directions response 45 | d.directions.forEach((direction) => { 46 | console.log(direction.feature) 47 | }) 48 | }) 49 | ``` 50 | 51 | ## Contributing 52 | 53 | Contributions are welcome! We are open to improvements of existing clients, as well as additions of 54 | new ones :-) 55 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Interfaces" 2 | position: 4 -------------------------------------------------------------------------------- /docs/docs/api/modules.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "modules" 3 | title: "routingjs" 4 | sidebar_label: "Table of Contents" 5 | sidebar_position: 0.5 6 | hide_table_of_contents: true 7 | custom_edit_url: null 8 | --- 9 | 10 | ## Modules 11 | 12 | - [core](modules/core.md) 13 | - [graphhopper](modules/graphhopper.md) 14 | - [ors](modules/ors.md) 15 | - [osrm](modules/osrm.md) 16 | - [valhalla](modules/valhalla.md) 17 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const lightCodeTheme = require("prism-react-renderer/themes/github") 5 | const darkCodeTheme = require("prism-react-renderer/themes/dracula") 6 | 7 | /** @type {import('@docusaurus/types').Config} */ 8 | const config = { 9 | title: "RoutingJS Documentation", 10 | tagline: "Consistent access to Open Source Routing Engines", 11 | url: "https://gis-ops.github.io/", 12 | trailingSlash: false, 13 | baseUrl: "/routingjs", 14 | onBrokenLinks: "throw", 15 | onBrokenMarkdownLinks: "warn", 16 | 17 | // GitHub pages deployment config. 18 | // If you aren't using GitHub pages, you don't need these. 19 | organizationName: "gis-ops", // Usually your GitHub org/user name. 20 | projectName: "routingjs", // Usually your repo name. 21 | 22 | // Even if you don't use internalization, you can use this field to set useful 23 | // metadata like html lang. For example, if your site is Chinese, you may want 24 | // to replace "en" with "zh-Hans". 25 | i18n: { 26 | defaultLocale: "en", 27 | locales: ["en"], 28 | }, 29 | 30 | presets: [ 31 | [ 32 | "classic", 33 | /** @type {import('@docusaurus/preset-classic').Options} */ 34 | ({ 35 | docs: { 36 | sidebarPath: require.resolve("./sidebars.js"), 37 | // Please change this to your repo. 38 | // Remove this to remove the "edit this page" links. 39 | editUrl: "https://github.com/gis-ops/routingjs/", 40 | }, 41 | blog: false, 42 | theme: { 43 | customCss: require.resolve("./src/css/custom.css"), 44 | }, 45 | }), 46 | ], 47 | ], 48 | 49 | themeConfig: 50 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 51 | ({ 52 | navbar: { 53 | title: "RoutingJS", 54 | 55 | items: [ 56 | { to: "/docs/api", label: "docs" }, 57 | { 58 | href: "https://github.com/gis-ops/routingjs", 59 | label: "GitHub", 60 | position: "right", 61 | }, 62 | ], 63 | }, 64 | footer: { 65 | style: "dark", 66 | links: [ 67 | { 68 | title: "Docs", 69 | items: [ 70 | { 71 | label: "Quickstart", 72 | to: "/docs/quickstart", 73 | }, 74 | { 75 | label: "Docs", 76 | to: "/docs/api/", 77 | }, 78 | ], 79 | }, 80 | { 81 | title: "Community", 82 | items: [ 83 | { 84 | label: "Twitter", 85 | href: "https://twitter.com/gis_ops", 86 | }, 87 | { 88 | label: "LinkedIn", 89 | href: "https://linkedin.com/gis-ops", 90 | }, 91 | ], 92 | }, 93 | { 94 | title: "More", 95 | items: [ 96 | { 97 | label: "GIS-OPS", 98 | href: "https://gis-ops.com", 99 | }, 100 | { 101 | label: "GitHub", 102 | href: "https://github.com/gis-ops/routingjs", 103 | }, 104 | ], 105 | }, 106 | ], 107 | copyright: `Copyright © ${new Date().getFullYear()} GIS-OPS UG. Icons by tabler.`, 108 | }, 109 | prism: { 110 | theme: lightCodeTheme, 111 | darkTheme: darkCodeTheme, 112 | }, 113 | }), 114 | plugins: [ 115 | [ 116 | "docusaurus-plugin-typedoc", 117 | 118 | { 119 | entryPoints: [ 120 | "../packages/core/index.ts", 121 | "../packages/valhalla/index.ts", 122 | "../packages/osrm/index.ts", 123 | "../packages/ors/index.ts", 124 | "../packages/graphhopper/index.ts", 125 | ], 126 | tsconfig: "../tsconfig.doc.json", 127 | }, 128 | ], 129 | ], 130 | } 131 | 132 | module.exports = config 133 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "clear-typedoc": "rimraf \"docs/api/\"", 13 | "serve": "docusaurus serve", 14 | "write-translations": "docusaurus write-translations", 15 | "write-heading-ids": "docusaurus write-heading-ids", 16 | "typecheck": "tsc" 17 | }, 18 | "dependencies": { 19 | "@docusaurus/core": "2.2.0", 20 | "@docusaurus/preset-classic": "2.2.0", 21 | "@mdx-js/react": "^1.6.22", 22 | "clsx": "^1.2.1", 23 | "prism-react-renderer": "^1.3.5", 24 | "react": "^17.0.2", 25 | "react-dom": "^17.0.2" 26 | }, 27 | "devDependencies": { 28 | "@docusaurus/module-type-aliases": "2.2.0", 29 | "@tsconfig/docusaurus": "^1.0.5", 30 | "rimraf": "^3.0.2", 31 | "typescript": "^4.7.4" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.5%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "engines": { 46 | "node": ">=16.14" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | 'intro', 23 | 'hello', 24 | { 25 | type: 'category', 26 | label: 'Tutorial', 27 | items: ['tutorial-basics/create-a-document'], 28 | }, 29 | ], 30 | */ 31 | }; 32 | 33 | module.exports = sidebars; 34 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import clsx from "clsx" 3 | import styles from "./styles.module.css" 4 | 5 | type FeatureItem = { 6 | title: string 7 | Svg: React.ComponentType> 8 | description: JSX.Element 9 | } 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: "Quick setup", 14 | Svg: require("@site/static/img/settings-off.svg").default, 15 | description: ( 16 | <> 17 | Hook into public APIs of Valhalla, OSRM, GraphHopper* and 18 | OpenRouteService* with no setup. 19 | 20 | ), 21 | }, 22 | { 23 | title: "A unified API", 24 | Svg: require("@site/static/img/switch-3.svg").default, 25 | description: ( 26 | <> 27 | With routing-js it has never been easier to switch between 28 | different routing engines. 29 | 30 | ), 31 | }, 32 | { 33 | title: "The Power of TypeScript", 34 | Svg: require("@site/static/img/braces.svg").default, 35 | description: ( 36 | <> 37 | Full code completion in your IDE for all API's parameters, 38 | thanks to TypeScript, even if you use JavaScript. 39 | 40 | ), 41 | }, 42 | ] 43 | 44 | function Feature({ title, Svg, description }: FeatureItem) { 45 | return ( 46 |
47 |
48 | 49 |
50 |
51 |

{title}

52 |

{description}

53 |
54 |
55 | ) 56 | } 57 | 58 | export default function HomepageFeatures(): JSX.Element { 59 | return ( 60 | <> 61 |
62 |
63 |
64 | {FeatureList.map((props, idx) => ( 65 | 66 | ))} 67 |
68 |
69 |
70 |
* API keys required
71 | 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 65px; 10 | width: 65px; 11 | } 12 | 13 | .smallprint { 14 | font-size: 0.85em; 15 | display: flex; 16 | justify-content: center; 17 | } 18 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #852e7e; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #a64aa6; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme="dark"] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import clsx from "clsx" 3 | import Link from "@docusaurus/Link" 4 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext" 5 | import Layout from "@theme/Layout" 6 | import HomepageFeatures from "@site/src/components/HomepageFeatures" 7 | 8 | import styles from "./index.module.css" 9 | 10 | function HomepageHeader() { 11 | const { siteConfig } = useDocusaurusContext() 12 | return ( 13 |
14 |
15 |

{siteConfig.title}

16 |

{siteConfig.tagline}

17 |
18 | 22 | Quickstart 23 | 24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default function Home(): JSX.Element { 31 | const { siteConfig } = useDocusaurusContext() 32 | return ( 33 | 37 | 38 |
39 | 40 |
41 |
42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilsnolde/routingjs/b19e7dc655b2d540f661c1a935e74c45ab67be36/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/braces.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilsnolde/routingjs/b19e7dc655b2d540f661c1a935e74c45ab67be36/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/settings-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/static/img/switch-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | transform: { 5 | "^.+\\.ts?$": "ts-jest", 6 | }, 7 | transformIgnorePatterns: ["/node_modules/"], 8 | modulePaths: ["/packages/"], 9 | } 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "useWorkspaces": true, 3 | "version": "0.3.0", 4 | "command": { 5 | "publish": { 6 | "registry": "https://registry.npmjs.org" 7 | } 8 | }, 9 | "$schema": "node_modules/lerna/schemas/lerna-schema.json" 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "routingjs", 3 | "private": "true", 4 | "description": "Library for consistent access to various vehicle routing APIs", 5 | "module": "dist/index.js", 6 | "main": "dist/index.cjs", 7 | "files": [ 8 | "dist" 9 | ], 10 | "type": "module", 11 | "typings": "dist/index.d.ts", 12 | "scripts": { 13 | "test": "jest", 14 | "test:valhalla": "jest packages/valhalla", 15 | "test:osrm": "jest packages/osrm", 16 | "test:graphhopper": "jest packages/graphhopper", 17 | "test:ors": "jest packages/ors", 18 | "format": "prettier --write packages/**/*.ts", 19 | "format-check": "prettier --check packages/**/*.ts", 20 | "build": "lerna run build", 21 | "clear": "rimraf \"packages/**/dist/\"", 22 | "up": "docker compose up -d", 23 | "postinstall": "npx husky install && chmod +x .husky/pre-commit", 24 | "down": "docker compose down" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/gis-ops/routingjs.git" 29 | }, 30 | "keywords": [ 31 | "routing", 32 | "vehicle routing", 33 | "navigation", 34 | "api", 35 | "geo", 36 | "geospatial" 37 | ], 38 | "author": "Christian Beiwinkel ", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/gis-ops/routingjs/issues" 42 | }, 43 | "homepage": "https://github.com/gis-ops/routingjs#readme", 44 | "devDependencies": { 45 | "@commitlint/cli": "^17.1.2", 46 | "@commitlint/config-conventional": "^17.1.0", 47 | "@rollup/plugin-typescript": "^9.0.2", 48 | "@types/geojson": "^7946.0.10", 49 | "@types/glob": "^8.0.0", 50 | "@types/jest": "^29.1.2", 51 | "@types/node": "^18.11.0", 52 | "@typescript-eslint/eslint-plugin": "^5.40.0", 53 | "@typescript-eslint/parser": "^5.40.0", 54 | "docusaurus-plugin-typedoc": "^0.18.0", 55 | "dotenv": "^16.0.3", 56 | "eslint": "^8.25.0", 57 | "eslint-plugin-tsdoc": "^0.2.17", 58 | "glob": "^8.0.3", 59 | "husky": "^8.0.1", 60 | "jest": "^29.2.0", 61 | "lerna": "^6.1.0", 62 | "prettier": "^2.7.1", 63 | "ts-jest": "^29.0.3", 64 | "typedoc": "^0.23.21", 65 | "typedoc-plugin-markdown": "^3.14.0", 66 | "typescript": "^4.8.4" 67 | }, 68 | "dependencies": { 69 | "@googlemaps/polyline-codec": "^1.0.28", 70 | "axios": "^1.1.3", 71 | "axios-retry": "^3.3.1" 72 | }, 73 | "workspaces": [ 74 | "packages/*" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /packages/core/BaseRouter.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig } from "axios" 2 | import { JSONObject, JSONValue } from "core/json" 3 | import Client from "./Client" 4 | import { Directions } from "./Direction" 5 | import { Isochrones } from "./Isochrone" 6 | import Matrix from "./Matrix" 7 | 8 | export interface ClientConstructorArgs { 9 | /** 10 | * Base URL that all requests are directed to. If not specified, 11 | * the router's default public API at is used (API key may be required) 12 | */ 13 | readonly baseUrl?: string 14 | 15 | /** 16 | * Optional API key that is passed as part of the URL params 17 | */ 18 | readonly apiKey?: string 19 | 20 | /** 21 | * overwrites the default user agent header. 22 | */ 23 | readonly userAgent?: string 24 | 25 | /** 26 | * additional headers passed to be passed in the request 27 | */ 28 | readonly headers?: { [k: string]: string } 29 | 30 | /** 31 | * Custom request timeout 32 | */ 33 | readonly timeout?: number 34 | 35 | /** 36 | * Whether requests should be retried on status code 429 responses 37 | */ 38 | readonly retryOverQueryLimit?: boolean 39 | 40 | /** 41 | * maximum number of retries performed by axios-retry 42 | */ 43 | readonly maxRetries?: number 44 | 45 | /** 46 | * other options passed to the axios instance 47 | */ 48 | readonly axiosOpts?: AxiosRequestConfig 49 | } 50 | 51 | export interface Waypoint { 52 | lat: number 53 | lon: number 54 | } 55 | 56 | export interface BaseRouter { 57 | client: Client< 58 | Record, 59 | Record | undefined, 60 | Record | undefined 61 | > 62 | 63 | directions: ( 64 | locations: ([number, number] | T)[], 65 | profile: string, 66 | directionsOpts?: Record, 67 | dryRun?: boolean 68 | ) => Promise | string> 69 | 70 | matrix: ( 71 | locations: ([number, number] | T)[], 72 | profile: string, 73 | matrixOpts?: JSONObject, 74 | dryRun?: boolean 75 | ) => Promise | string> 76 | 77 | reachability?: ( 78 | location: [number, number] | T, 79 | profile: string, 80 | intervals: number[], 81 | isochronesOpts?: JSONObject, 82 | dryRun?: boolean 83 | ) => Promise | string> 84 | 85 | mapMatch?: ( 86 | locations: ([number, number] | T)[], 87 | profile: string, 88 | mapMatchOpts?: JSONObject, 89 | dryRun?: boolean 90 | ) => Promise | string> 91 | } 92 | -------------------------------------------------------------------------------- /packages/core/Client.ts: -------------------------------------------------------------------------------- 1 | import axios, { 2 | Axios, 3 | AxiosProxyConfig, 4 | AxiosRequestConfig, 5 | AxiosInstance, 6 | AxiosResponse, 7 | } from "axios" 8 | import axiosRetry, { 9 | IAxiosRetryConfig, 10 | isNetworkOrIdempotentRequestError, 11 | } from "axios-retry" 12 | 13 | import options from "./options" 14 | 15 | interface ClientInterface { 16 | readonly baseURL: string 17 | readonly userAgent: string 18 | readonly timeout: number 19 | readonly retryOverQueryLimit: boolean 20 | readonly proxy?: false | AxiosProxyConfig 21 | readonly maxRetries?: number 22 | } 23 | 24 | /** 25 | * Arguments passed to the client's request method. 26 | */ 27 | interface requestArgs< 28 | GetParams extends Record | undefined = undefined, 29 | PostParams extends Record | undefined = undefined 30 | > { 31 | /** 32 | * @param endpoint - the endpoint the request is directed to. Is concatenated to the base URL 33 | */ 34 | endpoint: string 35 | /** 36 | * @param getParams - parameters passed with a GET request 37 | */ 38 | getParams?: GetParams 39 | /** 40 | * @param postParams - parameters passed with a POST request */ 41 | postParams?: PostParams 42 | /** 43 | * @param dryRun - if true, the actual request is not made, and instead returns a string 44 | * containing information about the request to be made (including URL andparameters) 45 | */ 46 | dryRun?: boolean 47 | } 48 | 49 | /** 50 | * The client class from which all underlying requests to the routing eninges' servers are made. 51 | * 52 | * @public 53 | * 54 | */ 55 | class Client< 56 | ResponseType, 57 | GetParams extends Record | undefined = undefined, 58 | PostParams extends Record | undefined = undefined 59 | > implements ClientInterface 60 | { 61 | protected axiosInstance: Axios 62 | protected axiosOptions: AxiosRequestConfig 63 | public readonly proxy?: false | AxiosProxyConfig 64 | 65 | /** 66 | * Create a new client instance 67 | * @param baseURL - the base URL that requests will be made to 68 | * @param userAgent - define a custom user agent to be passed in each request header 69 | * @param timeout - the time to await a response 70 | * @param retryOverQueryLimit - whether or not requests should be retried when 71 | * receiving a status 429 response 72 | * @param headers - additional headers to be passed with each request 73 | * @param maxRetries - the maximum number of retries made by axios-retry 74 | * @param additionalAxiosOpts - any additional options that are passed to the axios instance 75 | * 76 | */ 77 | constructor( 78 | public baseURL: string, 79 | public userAgent: string = options.defaultUserAgent, 80 | public readonly timeout = options.defaultTimeout, 81 | public retryOverQueryLimit: boolean = false, 82 | public readonly headers?: { [k: string]: string | number }, 83 | public maxRetries: number = options.defaultMaxRetries, 84 | public additionalAxiosOpts?: AxiosRequestConfig 85 | ) { 86 | this.headers = { 87 | ...this.headers, 88 | } 89 | this.axiosOptions = { 90 | headers: this.headers, 91 | timeout, 92 | ...additionalAxiosOpts, 93 | } 94 | 95 | this.axiosInstance = axios.create(this.axiosOptions) 96 | this.proxy = additionalAxiosOpts?.proxy 97 | 98 | const retryOpts: IAxiosRetryConfig = { 99 | retries: maxRetries, 100 | retryCondition: retryOverQueryLimit 101 | ? (error) => 102 | isNetworkOrIdempotentRequestError(error) || 103 | error.response?.status == 429 104 | : () => false, 105 | retryDelay: axiosRetry.exponentialDelay, 106 | onRetry: (number, error) => 107 | console.log( 108 | `Request failed with status code ${error.response?.status}: ${error.response?.statusText}. Retry number ${number}.` 109 | ), 110 | } 111 | 112 | axiosRetry(this.axiosInstance as AxiosInstance, retryOpts) 113 | } 114 | 115 | /** 116 | * The main request method. Decides whether a GET or POST request is to be made depending on 117 | * the passed arguments. 118 | * 119 | * @param requestArgs - the parameters passed as an object 120 | */ 121 | async request( 122 | requestArgs: requestArgs 123 | ): Promise { 124 | const { endpoint, getParams, postParams, dryRun } = requestArgs 125 | const urlObj = new URL(`${this.baseURL}${endpoint}`) 126 | if (postParams !== undefined) { 127 | if (getParams !== undefined) { 128 | for (const [k, v] of Object.entries(getParams)) { 129 | urlObj.searchParams.append(k, v) 130 | } 131 | } 132 | if (dryRun === true) { 133 | const requestInfo = ` 134 | URL: ${urlObj.toString()} 135 | Method: POST 136 | Parameters: ${JSON.stringify(postParams)} 137 | ` 138 | return new Promise((resolve) => { 139 | resolve(requestInfo) 140 | }) 141 | } 142 | return this.axiosInstance 143 | .post(urlObj.toString(), postParams) 144 | .then((res) => res.data) 145 | } else { 146 | if (dryRun === true) { 147 | const requestInfo = ` 148 | URL: ${urlObj.toString()} 149 | Method: GET 150 | Parameters: ${JSON.stringify(getParams)} 151 | ` 152 | return new Promise((resolve) => resolve(requestInfo)) 153 | } 154 | 155 | return this.axiosInstance 156 | .get(urlObj.toString(), { 157 | params: getParams, 158 | }) 159 | .then((res: AxiosResponse) => res.data) 160 | } 161 | } 162 | } 163 | 164 | export default Client 165 | -------------------------------------------------------------------------------- /packages/core/Direction.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString } from "geojson" 2 | 3 | export class Directions { 4 | constructor( 5 | public readonly directions: Direction[], 6 | public readonly raw: T 7 | ) {} 8 | } 9 | 10 | interface DirectionProps { 11 | duration: number | null 12 | distance: number | null 13 | } 14 | 15 | export type DirectionFeat = Feature 16 | 17 | export class Direction { 18 | constructor( 19 | public readonly feature: DirectionFeat, 20 | public readonly raw?: T 21 | ) {} 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/Isochrone.ts: -------------------------------------------------------------------------------- 1 | import { Feature, Geometry } from "geojson" 2 | 3 | export class Isochrones { 4 | constructor( 5 | public readonly isochrones: Isochrone[], 6 | public readonly raw: T 7 | ) {} 8 | } 9 | 10 | export class Isochrone { 11 | constructor( 12 | public readonly center: [number, number], 13 | public readonly interval: number, 14 | public readonly intervalType: string, 15 | public readonly feature: Feature 16 | ) {} 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GIS • OPS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/Matrix.ts: -------------------------------------------------------------------------------- 1 | class Matrix { 2 | constructor( 3 | public readonly durations: (number | null | undefined)[][], 4 | public readonly distances: (number | null | undefined)[][], 5 | public readonly raw: T 6 | ) {} 7 | } 8 | 9 | export default Matrix 10 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # RoutingJS: Core 2 | 3 | Core utils and classes for the RoutingJS project. 4 | 5 | --- 6 | 7 | This package is part of the [RoutingJS project](https://github.com/gis-ops/routingjs), which aims to provide consistent access to various vehicle routing APIs. 8 | -------------------------------------------------------------------------------- /packages/core/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * `ErrorProps` is the type of the `properties` field of the `RoutingJSAPIError` class. 3 | * It is used to provide additional information about the error. 4 | */ 5 | export interface ErrorProps { 6 | /** `statusCode` stands for the HTTP response status code */ 7 | statusCode?: number 8 | /** `status` gives detailed info about that response */ 9 | status?: string 10 | /** `errorMessage` explains the error specific to that routing engine*/ 11 | errorMessage?: string 12 | } 13 | 14 | /** 15 | * `RoutingJSAPIError` is the error class used to handle errors thrown by different routing 16 | * engines. 17 | */ 18 | export class RoutingJSAPIError extends Error { 19 | readonly properties: T 20 | 21 | constructor(message: string, properties: T) { 22 | super(message) 23 | this.properties = properties 24 | } 25 | 26 | toJSON() { 27 | return { 28 | message: this.message, 29 | properties: this.properties, 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/index.ts: -------------------------------------------------------------------------------- 1 | export { BaseRouter, ClientConstructorArgs, Waypoint } from "./BaseRouter" 2 | export { default as Client } from "./Client" 3 | export { Direction, Directions, DirectionFeat } from "./Direction" 4 | export { default as Matrix } from "./Matrix" 5 | export { Isochrone, Isochrones } from "./Isochrone" 6 | export { RoutingJSAPIError, ErrorProps } from "./error" 7 | export { JSONValue, JSONArray, JSONObject } from "./json" 8 | export { default as options } from "./options" 9 | -------------------------------------------------------------------------------- /packages/core/json.ts: -------------------------------------------------------------------------------- 1 | export type JSONValue = string | number | boolean | JSONArray | JSONObject 2 | export type JSONArray = JSONValue[] 3 | export interface JSONObject { 4 | [k: number | string]: JSONValue 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/options.ts: -------------------------------------------------------------------------------- 1 | import { AxiosProxyConfig } from "axios" 2 | 3 | interface optionsInterface { 4 | defaultTimeout: number 5 | defaultRetryTimeout: number 6 | defaultUserAgent: string 7 | defaultProxy?: false | AxiosProxyConfig 8 | defaultMaxRetries: number 9 | } 10 | 11 | const options: optionsInterface = { 12 | defaultTimeout: 30000, 13 | defaultRetryTimeout: 30000, 14 | defaultProxy: false, 15 | defaultUserAgent: "routing-js-", 16 | defaultMaxRetries: 5, 17 | } 18 | 19 | export default options 20 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@routingjs/core", 3 | "version": "0.3.0", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "RoutingJS Core", 8 | "main": "dist/js/index.js", 9 | "module": "dist/es/index.js", 10 | "files": [ 11 | "dist" 12 | ], 13 | "typings": "dist/js/index.d.ts", 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "npm-run-all build:*", 17 | "build:js": "tsc --outDir dist/js --module commonjs", 18 | "build:es": "tsc" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/gis-ops/routingjs.git" 23 | }, 24 | "keywords": [ 25 | "routing", 26 | "vehicle routing", 27 | "navigation", 28 | "api", 29 | "geo", 30 | "geospatial" 31 | ], 32 | "author": "Christian Beiwinkel ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/gis-ops/routingjs/issues" 36 | }, 37 | "homepage": "https://github.com/gis-ops/routingjs#readme", 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.1.2", 40 | "@commitlint/config-conventional": "^17.1.0", 41 | "@rollup/plugin-typescript": "^9.0.2", 42 | "@types/geojson": "^7946.0.10", 43 | "@types/jest": "^29.1.2", 44 | "@types/node": "^18.11.0", 45 | "@typescript-eslint/eslint-plugin": "^5.40.0", 46 | "@typescript-eslint/parser": "^5.40.0", 47 | "eslint": "^8.25.0", 48 | "husky": "^8.0.1", 49 | "jest": "^29.2.0", 50 | "npm-run-all": "*", 51 | "prettier": "^2.7.1", 52 | "rollup": "^3.2.5", 53 | "ts-jest": "^29.0.3", 54 | "typescript": "^4.8.4" 55 | }, 56 | "dependencies": { 57 | "axios": "^1.1.3", 58 | "axios-retry": "^3.3.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/es" 5 | }, 6 | "files": ["index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/graphhopper/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GIS • OPS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/graphhopper/README.md: -------------------------------------------------------------------------------- 1 | # RoutingJS: GraphHopper 2 | 3 | Promise-based web client for the [GraphHopper routing engine](https://www.graphhopper.com). 4 | 5 | ## Example 6 | 7 | ```js 8 | import { GraphHopper } from "@routingjs/graphhopper" 9 | 10 | const gh = new GraphHopper({ apiKey: "my-api-key" }) // URL defaults to https://api.openrouteservice.org 11 | gh.directions( 12 | [ 13 | [8.512516, 47.380742], 14 | [8.557835, 47.359467], 15 | ], 16 | "car" 17 | ).then((d) => { 18 | // do stuff with the directions response 19 | d.directions.forEach((direction) => { 20 | console.log(direction.feature) 21 | }) 22 | }) 23 | ``` 24 | 25 | ## Installation 26 | 27 | ``` 28 | npm install @routingjs/graphhopper 29 | ``` 30 | 31 | --- 32 | 33 | This package is part of the [RoutingJS project](https://github.com/gis-ops/routingjs), which aims to provide consistent access to various vehicle routing APIs. 34 | -------------------------------------------------------------------------------- /packages/graphhopper/graphhopper.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphHopper } from "./index" 2 | import { GraphHopperAPIError } from "./index" 3 | import dotenv from "dotenv" 4 | dotenv.config() 5 | 6 | const assertError = (e: GraphHopperAPIError) => { 7 | expect(e.properties).toBeDefined() 8 | expect(e.properties).toHaveProperty("statusCode") 9 | expect(e.properties).toHaveProperty("status") 10 | expect(e.properties).toHaveProperty("errorMessage") 11 | expect(e.properties).toHaveProperty("hints") 12 | expect(e.properties.hints.length).toBeGreaterThan(0) 13 | } 14 | 15 | const g = new GraphHopper({ baseUrl: "http://localhost:8989" }) 16 | 17 | describe("GraphHopper returns responses", () => { 18 | it("gets a directions response", async () => { 19 | await g 20 | .directions( 21 | [ 22 | [42.5063, 1.51886], 23 | [42.51007, 1.53789], 24 | ], 25 | "car" 26 | ) 27 | .then((g) => { 28 | expect(g.raw).toBeDefined() 29 | expect(g.directions).toHaveLength(1) 30 | expect(g.directions[0]).toHaveProperty("feature") 31 | expect(g.directions[0].feature).toHaveProperty("geometry") 32 | expect(g.directions[0].feature.geometry).toHaveProperty( 33 | "coordinates" 34 | ) 35 | 36 | expect( 37 | g.directions[0].feature.properties.duration 38 | ).not.toBeNull() 39 | 40 | expect( 41 | g.directions[0].feature.properties.distance 42 | ).not.toBeNull() 43 | }) 44 | }) 45 | 46 | it("gets an isochrones response", async () => { 47 | await g.reachability([42.51007, 1.53789], "car", [600]).then((i) => { 48 | expect(i.raw).toBeDefined() 49 | expect(i.isochrones).toHaveLength(1) 50 | }) 51 | }) 52 | 53 | //optional 54 | if (process.env.GRAPHHOPPER_API_KEY) { 55 | it("gets a matrix response", async () => { 56 | const g = new GraphHopper({ 57 | apiKey: process.env.GRAPHHOPPER_API_KEY, 58 | }) 59 | await g 60 | .matrix( 61 | [ 62 | [42.5063, 1.51886], 63 | [42.51007, 1.53789], 64 | ], 65 | "car" 66 | ) 67 | .then((m) => { 68 | expect(m).toHaveProperty("durations") 69 | expect(m).toHaveProperty("distances") 70 | expect(m.raw).toBeDefined() 71 | }) 72 | }) 73 | } 74 | }) 75 | 76 | describe("Throws RoutingJSAPIError", () => { 77 | it("fails to get a directions response", async () => { 78 | await g 79 | .directions( 80 | [ 81 | [0.00001, 1], 82 | [42.51007, 1.53789], 83 | ], 84 | "car" 85 | ) 86 | .catch(assertError) 87 | }) 88 | 89 | it("fails to get an isochrones response", async () => { 90 | await g.reachability([0.00001, 1], "car", [600]).catch(assertError) 91 | }) 92 | 93 | //optional 94 | if (process.env.GRAPHHOPPER_API_KEY) { 95 | it("fails to get a matrix response", async () => { 96 | const g = new GraphHopper({ 97 | apiKey: process.env.GRAPHHOPPER_API_KEY, 98 | }) 99 | await g 100 | .matrix( 101 | [ 102 | [0.00001, 1], 103 | [42.51007, 1.53789], 104 | ], 105 | "car" 106 | ) 107 | .catch(assertError) 108 | }) 109 | } 110 | }) 111 | -------------------------------------------------------------------------------- /packages/graphhopper/index.ts: -------------------------------------------------------------------------------- 1 | import { decode } from "@googlemaps/polyline-codec" 2 | import { 3 | BaseRouter, 4 | ClientConstructorArgs, 5 | Direction, 6 | Directions, 7 | Isochrone, 8 | RoutingJSAPIError, 9 | ErrorProps, 10 | Isochrones, 11 | Matrix, 12 | Client, 13 | Waypoint, 14 | } from "@routingjs/core" 15 | import { LineString } from "geojson" 16 | import { 17 | GraphHopperIsochroneGetParams, 18 | GraphHopperIsochroneParams, 19 | GraphHopperIsochroneProps, 20 | GraphHopperIsochroneResponse, 21 | GraphHopperMatrixParams, 22 | GraphHopperMatrixResponse, 23 | GraphHopperProfile, 24 | GraphHopperRouteParams, 25 | GraphHopperRoutePath, 26 | GraphHopperRouteResponse, 27 | } from "./parameters" 28 | import { AxiosError } from "axios" 29 | 30 | type GraphHopperHint = { 31 | message: string 32 | details?: string 33 | point_index?: number 34 | } 35 | 36 | type GraphHopperErrorResponseProps = { 37 | message: string 38 | hints: GraphHopperHint[] 39 | } 40 | 41 | /** 42 | * `GraphHopperErrorProps` returns additional information about the error thrown by the 43 | * GraphHopper routing engine. It sends a JSON response including an error message along with 44 | * a hints array. 45 | */ 46 | export interface GraphHopperErrorProps extends ErrorProps { 47 | hints: GraphHopperHint[] 48 | } 49 | 50 | export type GraphHopperAPIError = RoutingJSAPIError 51 | 52 | const handleGraphHopperError = ( 53 | error: AxiosError 54 | ) => { 55 | const props: GraphHopperErrorProps = { 56 | statusCode: error.response?.status, 57 | status: error.response?.statusText, 58 | errorMessage: error.response?.data.message, 59 | hints: error.response?.data.hints || [], 60 | } 61 | throw new RoutingJSAPIError(error.message, props) 62 | } 63 | 64 | /** 65 | * `points` and `profile` properties are passed using the `directions()` method's top level args for 66 | * consistency. 67 | * 68 | */ 69 | export type GraphHopperDirectionsOpts = Omit< 70 | GraphHopperRouteParams, 71 | "points" | "profile" 72 | > 73 | 74 | /** Omitted properties are passed using the `.matrix()` top level function arguments for consistency. */ 75 | type GraphHopperMatrixBaseOpts = Omit< 76 | GraphHopperMatrixParams, 77 | "from_points" | "to_points" | "profile" 78 | > 79 | 80 | export interface GraphHopperMatrixOpts extends GraphHopperMatrixBaseOpts { 81 | /** */ 82 | sources?: number[] 83 | destinations?: number[] 84 | } 85 | 86 | export interface GraphHopperIsochroneOpts 87 | extends Omit< 88 | GraphHopperIsochroneParams, 89 | "point" | "profile" | "time_limit" | "distance_limit" 90 | > { 91 | interval_type?: "time" | "distance" 92 | } 93 | 94 | export type GraphHopperDirections = Directions< 95 | GraphHopperRouteResponse, 96 | GraphHopperRoutePath 97 | > 98 | 99 | export type GraphHopperIsochrones = Isochrones< 100 | GraphHopperIsochroneResponse, 101 | GraphHopperIsochroneProps 102 | > 103 | 104 | export type GraphHopperMatrix = Matrix 105 | 106 | export type GraphHopperClient = Client< 107 | | GraphHopperRouteResponse 108 | | GraphHopperIsochroneResponse 109 | | GraphHopperMatrixResponse, 110 | | { 111 | [k in keyof GraphHopperIsochroneGetParams]: GraphHopperIsochroneGetParams[k] 112 | } 113 | | { key?: string }, // for auth 114 | GraphHopperRouteParams | GraphHopperMatrixParams 115 | > 116 | 117 | /** 118 | * Performs requests to the GraphHopper API. Default public URL is 119 | * https://graphhopper.com/api/1. 120 | * 121 | * For the full documentation, see {@link https://docs.graphhopper.com}. 122 | */ 123 | export class GraphHopper implements BaseRouter { 124 | client: GraphHopperClient 125 | apiKey?: string 126 | 127 | constructor(clientArgs?: ClientConstructorArgs) { 128 | const { 129 | apiKey, 130 | baseUrl, 131 | userAgent, 132 | headers, 133 | timeout, 134 | retryOverQueryLimit, 135 | maxRetries, 136 | axiosOpts, 137 | } = clientArgs || {} 138 | this.apiKey = apiKey 139 | const defaultURL = "https://graphhopper.com/api/1" 140 | 141 | if (baseUrl === undefined && !apiKey) { 142 | throw new Error("Please provide an API key for GraphHopper") 143 | } 144 | 145 | this.client = new Client( 146 | baseUrl || defaultURL, 147 | userAgent, 148 | timeout, 149 | retryOverQueryLimit, 150 | headers, 151 | maxRetries, 152 | axiosOpts 153 | ) 154 | } 155 | 156 | /** 157 | * Get directions between two or more points. For the complete documentation, please see {@link https://docs.graphhopper.com/#operation/postRoute}. 158 | * 159 | * @param locations - coordinate tuples in lat/lon format 160 | * @param profile - one of {@link GraphHopperProfile} 161 | * @param directionsOpts - optional parameters that are passed to the route endpoint. See {@link GraphHopperDirectionsOpts} 162 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 163 | */ 164 | directions( 165 | locations: ([number, number] | Waypoint)[], 166 | profile: GraphHopperProfile, 167 | directionsOpts?: GraphHopperDirectionsOpts, 168 | dryRun?: false 169 | ): Promise 170 | directions( 171 | locations: ([number, number] | Waypoint)[], 172 | profile: GraphHopperProfile, 173 | directionsOpts: GraphHopperDirectionsOpts, 174 | dryRun: true 175 | ): Promise 176 | public async directions( 177 | locations: ([number, number] | Waypoint)[], 178 | profile: GraphHopperProfile, 179 | directionsOpts?: GraphHopperDirectionsOpts, 180 | dryRun?: boolean | undefined 181 | ): Promise { 182 | const params: GraphHopperRouteParams = { 183 | profile, 184 | points: locations.map((c) => 185 | Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat] 186 | ), 187 | ...directionsOpts, 188 | } 189 | 190 | if ( 191 | directionsOpts?.custom_model || 192 | directionsOpts?.headings || 193 | directionsOpts?.heading_penalty || 194 | directionsOpts?.pass_through || 195 | directionsOpts?.algorithm || 196 | directionsOpts?.["round_trip.distance"] || 197 | directionsOpts?.["round_trip.seed"] || 198 | directionsOpts?.["alternative_route.max_paths"] || 199 | directionsOpts?.["alternative_route.max_share_factor"] || 200 | directionsOpts?.["alternative_route.max_weight_factor"] 201 | ) { 202 | params["ch.disable"] = true // automatically detects whether contraction hierarchies need to be disabled 203 | } 204 | return this.client 205 | .request({ 206 | endpoint: `/route${this.apiKey ? "?key=" + this.apiKey : ""}`, 207 | postParams: params, 208 | dryRun, 209 | }) 210 | .then((res: GraphHopperRouteResponse) => { 211 | if (typeof res === "object") { 212 | return GraphHopper.parseDirectionsResponse(res) 213 | } else { 214 | return res 215 | } 216 | }) 217 | .catch(handleGraphHopperError) 218 | } 219 | /** 220 | * Parse a response object returned from the `/route` endpoint and returns an {@link Isochrone } object. 221 | * 222 | * @param response - the response from the server 223 | * @returns a new {@link Directions} object 224 | */ 225 | public static parseDirectionsResponse( 226 | response: GraphHopperRouteResponse 227 | ): GraphHopperDirections { 228 | return new Directions( 229 | response.paths.map((path) => { 230 | let geometry = path.points 231 | 232 | if (path.points_encoded) { 233 | geometry = { 234 | type: "LineString", 235 | coordinates: decode(path.points as string, 5).map( 236 | ([lat, lon]) => [lon, lat] 237 | ) as [number, number][], 238 | } 239 | } 240 | 241 | return new Direction( 242 | { 243 | type: "Feature", 244 | geometry: geometry as LineString, 245 | properties: { 246 | duration: path.time, 247 | distance: path.distance, 248 | }, 249 | }, 250 | path 251 | ) 252 | }), 253 | response 254 | ) 255 | } 256 | 257 | /** 258 | * Gets isochrones or equidistants for a range of time/distance values around a given set of coordinates. 259 | * 260 | * @param location - One coordinate pair denoting the location. Format: [lat, lon] 261 | * @param profile - Specifies the mode of transport. 262 | * @param intervals - Maximum range to calculate distances/durations for. You can also specify the `buckets` variable to break the single value into more isochrones. For compatibility reasons, this parameter is expressed as list. In meters or seconds depending on `interval_type`. 263 | * @param isochronesOpts - additional options specific to the isochrone endpoint. 264 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 265 | * 266 | * @see {@link https://docs.graphhopper.com/#tag/Isochrone-API} for the full documentation. 267 | */ 268 | reachability( 269 | location: [number, number] | Waypoint, 270 | profile: GraphHopperProfile, 271 | intervals: [number], 272 | isochronesOpts?: GraphHopperIsochroneOpts, 273 | dryRun?: false 274 | ): Promise 275 | reachability( 276 | location: [number, number] | Waypoint, 277 | profile: GraphHopperProfile, 278 | intervals: [number], 279 | isochronesOpts: GraphHopperIsochroneOpts, 280 | dryRun: true 281 | ): Promise 282 | public async reachability( 283 | location: [number, number] | Waypoint, 284 | profile: GraphHopperProfile, 285 | intervals: [number], 286 | isochronesOpts?: GraphHopperIsochroneOpts, 287 | dryRun?: boolean | undefined 288 | ): Promise { 289 | const params: GraphHopperIsochroneGetParams = { 290 | point: Array.isArray(location) 291 | ? `${location[0]}, ${location[1]}` 292 | : `${location.lat},${location.lon}`, 293 | profile, 294 | } 295 | 296 | const interval = intervals[0].toString() 297 | if (isochronesOpts?.interval_type === "distance") { 298 | params.distance_limit = interval 299 | } else { 300 | params.time_limit = interval 301 | } 302 | 303 | if (isochronesOpts?.buckets !== undefined) { 304 | params.buckets = isochronesOpts.buckets.toString() 305 | } 306 | 307 | if (isochronesOpts?.reverse_flow !== undefined) { 308 | params.reverse_flow = isochronesOpts.reverse_flow ? "true" : "false" 309 | } 310 | 311 | return this.client 312 | .request({ 313 | endpoint: `/isochrone`, 314 | getParams: { ...params, key: this.apiKey }, 315 | dryRun, 316 | }) 317 | .then((res) => { 318 | if (typeof res === "object") { 319 | return GraphHopper.parseIsochroneResponse( 320 | res as GraphHopperIsochroneResponse, 321 | location, 322 | isochronesOpts?.interval_type || "time" 323 | ) 324 | } else { 325 | return res 326 | } 327 | }) 328 | .catch(handleGraphHopperError) 329 | } 330 | 331 | /** 332 | * Parses a response returned from the `/isochrone` endpoint and returns an {@link Isochrones} object. 333 | * 334 | * @param response - a graphhopper isochrone response 335 | * @param center - the originally requested location 336 | * @param intervalType - whether isodistances or isochrones were requested 337 | * @returns a new Isochrones instance 338 | */ 339 | public static parseIsochroneResponse( 340 | response: GraphHopperIsochroneResponse, 341 | center: [number, number] | Waypoint, 342 | intervalType: "time" | "distance" 343 | ): GraphHopperIsochrones { 344 | const isochrones: Isochrone[] = 345 | response.polygons.map((poly) => { 346 | return new Isochrone( 347 | Array.isArray(center) ? center : [center.lat, center.lon], 348 | poly.properties.bucket, 349 | intervalType, 350 | poly 351 | ) 352 | }) 353 | return new Isochrones(isochrones, response) 354 | } 355 | 356 | /** 357 | * Makes a request to the `/matrix` endpoint. 358 | * 359 | * @remarks 360 | * 361 | * Currently not available on the open source version. 362 | * 363 | * @param locations - Specify multiple points for which the weight-, route-, time- or 364 | * distance-matrix should be calculated. In this case the starts are identical 365 | * to the destinations. 366 | * If there are N points, then NxN entries will be calculated. Format: [lat, lon] 367 | * @param profile - Specifies the mode of transport. 368 | * @param matrixOpts - additional options specific to the matrix endpoint 369 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 370 | * 371 | * @see {@link https://docs.graphhopper.com/#tag/Matrix-API} for the full documentation. 372 | * 373 | */ 374 | matrix( 375 | locations: ([number, number] | Waypoint)[], 376 | profile: GraphHopperProfile, 377 | matrixOpts?: GraphHopperMatrixOpts, 378 | dryRun?: false 379 | ): Promise 380 | matrix( 381 | locations: ([number, number] | Waypoint)[], 382 | profile: GraphHopperProfile, 383 | matrixOpts: GraphHopperMatrixOpts, 384 | dryRun: true 385 | ): Promise 386 | public async matrix( 387 | locations: ([number, number] | Waypoint)[], 388 | profile: GraphHopperProfile, 389 | matrixOpts?: GraphHopperMatrixOpts, 390 | dryRun?: boolean | undefined 391 | ): Promise { 392 | const params: GraphHopperMatrixParams = { 393 | profile, 394 | from_points: locations 395 | .filter((coords, i) => { 396 | if (matrixOpts?.sources !== undefined) { 397 | return matrixOpts.sources.includes(i) 398 | } else return true 399 | }) 400 | .map((c) => (Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat])), // reverse order for POST requests 401 | 402 | to_points: locations 403 | .filter((coords, i) => { 404 | if (matrixOpts?.destinations !== undefined) { 405 | return matrixOpts.destinations.includes(i) 406 | } else return true 407 | }) 408 | .map((c) => (Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat])), // reverse order for POST requests 409 | 410 | ...matrixOpts, 411 | } 412 | 413 | return this.client 414 | .request({ 415 | endpoint: `/matrix${this.apiKey ? "?key=" + this.apiKey : ""}`, 416 | postParams: params, 417 | dryRun, 418 | }) 419 | .then((res) => { 420 | if (typeof res === "object") { 421 | return GraphHopper.parseMatrixResponse( 422 | res as GraphHopperMatrixResponse 423 | ) as GraphHopperMatrix 424 | } else { 425 | return res 426 | } 427 | }) 428 | .catch(handleGraphHopperError) 429 | } 430 | 431 | /** 432 | * Parse a response returned from the `/matrix` endpoint. 433 | * 434 | * @param response - a GraphHopper Matrix response 435 | * @returns a new Matrix instance 436 | */ 437 | public static parseMatrixResponse( 438 | response: GraphHopperMatrixResponse 439 | ): GraphHopperMatrix { 440 | return new Matrix( 441 | response.times || [[]], 442 | response.distances || [[]], 443 | response 444 | ) 445 | } 446 | } 447 | 448 | export * from "./parameters" 449 | -------------------------------------------------------------------------------- /packages/graphhopper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@routingjs/graphhopper", 3 | "version": "0.3.0", 4 | "description": "RoutingJS GraphHopper Module", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "main": "dist/js/index.js", 9 | "module": "dist/es/index.js", 10 | "files": [ 11 | "dist" 12 | ], 13 | "typings": "dist/js/index.d.ts", 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "npm-run-all build:*", 17 | "build:js": "tsc --outDir dist/js --module commonjs", 18 | "build:es": "tsc" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/gis-ops/routingjs.git" 23 | }, 24 | "keywords": [ 25 | "routing", 26 | "vehicle routing", 27 | "navigation", 28 | "api", 29 | "geo", 30 | "geospatial" 31 | ], 32 | "author": "Christian Beiwinkel ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/gis-ops/routingjs/issues" 36 | }, 37 | "homepage": "https://github.com/gis-ops/routingjs#readme", 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.1.2", 40 | "@commitlint/config-conventional": "^17.1.0", 41 | "@rollup/plugin-typescript": "^9.0.2", 42 | "@types/geojson": "^7946.0.10", 43 | "@types/jest": "^29.1.2", 44 | "@types/node": "^18.11.0", 45 | "@typescript-eslint/eslint-plugin": "^5.40.0", 46 | "@typescript-eslint/parser": "^5.40.0", 47 | "eslint": "^8.25.0", 48 | "husky": "^8.0.1", 49 | "jest": "^29.2.0", 50 | "npm-run-all": "*", 51 | "prettier": "^2.7.1", 52 | "rollup": "^3.2.5", 53 | "ts-jest": "^29.0.3", 54 | "typescript": "^4.8.4" 55 | }, 56 | "dependencies": { 57 | "@googlemaps/polyline-codec": "^1.0.28", 58 | "@routingjs/core": "^0.3.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/graphhopper/parameters.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString, Polygon } from "geojson" 2 | import { JSONObject, JSONValue } from "@routingjs/core" 3 | 4 | /** 5 | * Request parameters common across `/route`, `/isochrone` and `/matrix` endpoints. 6 | */ 7 | interface GraphHopperBaseParams { 8 | /** The vehicle for which the route should be calculated. */ 9 | profile: GraphHopperProfile 10 | } 11 | 12 | /** 13 | * All parameters accepted by the `/route` endpoint 14 | */ 15 | export interface GraphHopperRouteParams extends GraphHopperBaseParams { 16 | /** The points for the route in an array of [longitude,latitude]. */ 17 | points: [number, number][] 18 | /** 19 | * Specifies a hint for each point in the points array to prefer a 20 | * certain street for the closest location lookup. E.g. if there is an 21 | * address or house with two or more neighboring streets you can control for 22 | * which street the closest location is looked up. Make sure you do not 23 | * include the house number of city name and only the street name to improve the 24 | * quality of the matching. 25 | */ 26 | point_hints?: string[] 27 | /** Parameter to avoid snapping to a certain road class or road environment */ 28 | snap_preventions?: GraphHopperSnapPrevention[] 29 | /** 30 | * Specifies on which side a point should be relative to the driver when she 31 | * leaves/arrives at a start/target/via point. You need to specify this parameter 32 | * for either none or all points. Only supported for motor vehicle profiles and OpenStreetMap. 33 | */ 34 | curbsides?: GraphHopperCurbside[] 35 | /** The locale of the resulting turn instructions. Defaults to "en". */ 36 | locale?: string 37 | /** 38 | * If true, a third coordinate, the altitude, is included with all positions in the response. 39 | * This changes the format of the points and snapped_waypoints fields of the response, in both 40 | * their encodings. Unless you switch off the points_encoded parameter, you need special code on 41 | * the client side that can handle three-dimensional coordinates. 42 | * 43 | * @defaultValue 44 | * Defaults to false. 45 | */ 46 | elevation?: boolean 47 | /** Enables additional attributes to be added to the returned path(s). */ 48 | details?: GraphHopperDetail[] 49 | /** 50 | * Normally, the calculated route will visit the points in the order you specified them. 51 | * If you have more than two points, you can set this parameter to "true" and the points may 52 | * be re-ordered to minimize the total travel time. Keep in mind that the limits on the number 53 | * of locations of the Route Optimization API applies, and the request costs more credits. 54 | * 55 | * @defaultValue 56 | * Defaults to false. 57 | */ 58 | optimize?: boolean 59 | /** 60 | * If true, detailed instructions are returned. 61 | * @defaultValue 62 | * Defaults to false. 63 | */ 64 | instructions?: boolean 65 | /** 66 | * Whether the points for the route should be calculated at all. If true, only returns 67 | * time and distance. 68 | * 69 | * @defaultValue 70 | * Defaults to false. 71 | */ 72 | calc_points?: boolean 73 | /** 74 | * If true, output will be formatted. 75 | * @defaultValue 76 | * Defaults to false. 77 | */ 78 | debug?: boolean 79 | /** 80 | * If `False` the coordinates in point and snapped_waypoints are returned as array using the order 81 | * [lon,lat,elevation] for every point. If true the coordinates will be encoded as string leading to less bandwith usage. 82 | * @defaultValue 83 | * Defaults to true. 84 | */ 85 | points_encoded?: boolean 86 | /** 87 | * If certain optional parameters are set, contraction hierarchies need to be disabled using this flag. 88 | * Is detected automatically in the `.directions()` method, so no need to pass explicitly. 89 | * 90 | * @defaultValue 91 | * Defaults to false. 92 | */ 93 | "ch.disable"?: boolean 94 | /** 95 | * The custom_model modifies the routing behaviour of the specified profile. 96 | * 97 | * @see {@link https://docs.graphhopper.com/#section/Custom-Model} for full documentation. 98 | */ 99 | custom_model?: GraphHopperCustomModel 100 | /** 101 | * Favour a heading direction for a certain point. Specify either one heading for the start 102 | * point or as many as there are points. In this case headings are associated by their order 103 | * to the specific points. Headings are given as north based clockwise angle between 0 and 360 104 | * degree. This parameter also influences the tour generated with algorithm=round_trip and forces 105 | * the initial direction. 106 | * 107 | * @remarks 108 | * 109 | * Requires `ch.disabled = true`. 110 | * 111 | */ 112 | headings?: number[] 113 | /** 114 | * Time penalty in seconds for not obeying a specified heading. 115 | * 116 | * @remarks 117 | * 118 | * Requires `ch.disabled = true`. 119 | * 120 | * @defaultValue 121 | * Defaults to 120. 122 | * 123 | */ 124 | heading_penalty?: number 125 | /** 126 | * If true, u-turns are avoided at via-points with regard to the heading_penalty. 127 | * 128 | * @remarks 129 | * 130 | * Requires `ch.disabled = true`. 131 | * 132 | * @defaultValue 133 | * Defaults to false. 134 | * 135 | */ 136 | pass_through?: boolean 137 | /** 138 | * Rather than looking for the shortest or fastest path, this parameter lets you solve two 139 | * different problems related to routing: With alternative_route, we give you not one but several 140 | * routes that are close to optimal, but not too similar to each other. With round_trip, the 141 | * route will get you back to where you started. This is meant for fun (think of a bike trip), 142 | * so we will add some randomness. 143 | * 144 | * @remarks 145 | * 146 | * Requires `ch.disabled = true`. 147 | * 148 | * 149 | */ 150 | algorithm?: GraphHopperAlgorithm 151 | /** 152 | * This parameter configures approximative length of the 153 | * resulting round trip. 154 | * 155 | * @remarks 156 | * 157 | * Requires `ch.disabled = true`. 158 | * Requires `algorithm=round_trip`. 159 | * 160 | * @defaultValue 161 | * Defaults to 10000. 162 | */ 163 | "round_trip.distance"?: number 164 | /** 165 | * This sets the random seed. Change this to get a different tour for each value. 166 | * 167 | * @remarks 168 | * 169 | * Requires `ch.disabled = true`. 170 | * Requires `algorithm=round_trip`. 171 | */ 172 | "round_trip.seed"?: number 173 | /** 174 | * This parameter sets the number of maximum 175 | * paths which should be calculated. Increasing can lead to worse alternatives. 176 | * 177 | * @remarks 178 | * 179 | * Requires `ch.disabled = true`. 180 | * Requires `algorithm=alternative_route`. 181 | * 182 | * @defaultValue 183 | * Defaults to 2. 184 | * 185 | */ 186 | "alternative_route.max_paths"?: number 187 | /** 188 | * This parameter sets the factor by which the alternatives routes can be longer 189 | * than the optimal route. Increasing can lead to worse alternatives. 190 | * 191 | * @remarks 192 | * 193 | * Requires `ch.disabled = true`. 194 | * Requires `algorithm=alternative_route`. 195 | * 196 | * @defaultValue 197 | * Defaults to 1.4. 198 | * 199 | */ 200 | "alternative_route.max_weight_factor"?: number 201 | /** 202 | * This parameter specifies how similar an alternative route can be to the optimal route. 203 | * Increasing can lead to worse alternatives. 204 | * 205 | * @remarks 206 | * 207 | * Requires `ch.disabled = true`. 208 | * Requires `algorithm=alternative_route`. 209 | * 210 | * @defaultValue 211 | * Defaults to 0.6. 212 | * 213 | */ 214 | "alternative_route.max_share_factor"?: number 215 | } 216 | 217 | /** 218 | * All parameters accepted by the `/isocrhone` endpoint 219 | */ 220 | export interface GraphHopperIsochroneParams extends GraphHopperBaseParams { 221 | /** A single Lon/Lat tuple. */ 222 | point: [number, number] 223 | /** 224 | * Specify which time the vehicle should travel. In seconds. 225 | * 226 | * @defaultValue 227 | * Defaults to 600. 228 | */ 229 | time_limit?: number 230 | /** Specify which distance the vehicle should travel. In meters. */ 231 | distance_limit?: number 232 | /** 233 | * Number by which to divide the given `time_limit` / `distance_limit` to create buckets nested 234 | * isochrones of time intervals `limit-n * limit / buckets`. 235 | * 236 | * @defaultValue 237 | * Defaults to 1. 238 | */ 239 | buckets?: number 240 | /** 241 | * If false the flow goes from point to the polygon, if true the flow goes from the 242 | * polygon "inside" to the point. 243 | * 244 | * @defaultValue 245 | * Defaults to false. 246 | */ 247 | reverse_flow?: boolean 248 | } 249 | 250 | /** 251 | * Since the `/isochrone` endpoint only accepts GET requests, 252 | * all parameters need to be stringified. 253 | */ 254 | export type GraphHopperIsochroneGetParams = { 255 | [K in keyof GraphHopperIsochroneParams]: string 256 | } 257 | 258 | /** 259 | * All parameters accepted by the `/matrix` endpoint. 260 | */ 261 | export interface GraphHopperMatrixParams extends GraphHopperBaseParams { 262 | /** 263 | * The starting points for the routes in an array of [longitude,latitude]. 264 | * For instance, if you want to calculate three routes from point A such as A-\>1, A-\>2, A-\>3 265 | * then you have one from_point parameter and three to_point parameters. 266 | */ 267 | from_points: [number, number][] 268 | /** 269 | * The destination points for the routes in an array of [longitude,latitude]. 270 | */ 271 | to_points: [number, number][] 272 | /** 273 | * Specifies a hint for each `from_point` parameter to prefer a certain street for the closest 274 | * location lookup. E.g. if there is an address or house with two or more neighboring streets 275 | * you can control for which street the closest location is looked up. 276 | * 277 | * @remarks 278 | * Array length needs to match `from_points` array length. 279 | */ 280 | from_point_hints?: string[] 281 | /** 282 | * Specifies a hint for each `from_point` parameter to prefer a certain street for the closest 283 | * location lookup. E.g. if there is an address or house with two or more neighboring streets 284 | * you can control for which street the closest location is looked up. 285 | * 286 | * @remarks 287 | * Array length needs to match `from_points` array length. 288 | */ 289 | to_point_hints?: string[] 290 | /** 291 | * Optional parameter to avoid snapping to a certain road class or road environment. 292 | * Multiple values are specified like `snap_prevention=ferry&snap_prevention=motorway`. 293 | * Please note that this feature does not e.g. avoid motorways for the route - it 294 | * only avoids it for the "location snap". 295 | * 296 | * @remarks 297 | * Array length needs to match `from_points` array length. 298 | */ 299 | snap_preventions?: string[] 300 | /** 301 | * Specifies on which side a point should be relative to the driver when she leaves/arrives 302 | * at a start/target/via point. You need to specify this parameter for either none or all points. 303 | * Only supported for motor vehicles and OpenStreetMap. 304 | * 305 | * @remarks 306 | * Array length needs to match `from_points` array length. 307 | */ 308 | from_curbsides?: GraphHopperCurbside[] 309 | /** 310 | * Specifies on which side a point should be relative to the driver when she leaves/arrives 311 | * at a start/target/via point. You need to specify this parameter for either none or all points. 312 | * Only supported for motor vehicles and OpenStreetMap. 313 | * 314 | * @remarks 315 | * Array length needs to match `from_points` array length. 316 | */ 317 | to_curbsides?: GraphHopperCurbside[] 318 | /** 319 | * Specifies which arrays should be included in the response. Specify one or more of 320 | * the following options 'weights', 'times', 'distances'. To specify more than one array 321 | * use e.g. out_array=times&out_array=distances. The units of the entries of distances 322 | * are meters, of times are seconds and of weights is arbitrary and it can differ for different 323 | * vehicles or versions of this API. 324 | */ 325 | out_arrays?: GraphHopperMatrixOut[] 326 | /** 327 | * Specifies whether or not the matrix calculation should return with an error as soon as 328 | * possible in case some points cannot be found or some points are not connected. If set 329 | * to false the time/weight/distance matrix will be calculated for all valid points and contain 330 | * the null value for all entries that could not be calculated. The hint field of the response 331 | * will also contain additional information about what went wrong. 332 | * 333 | * @defaultValue 334 | * Defaults to true. 335 | */ 336 | fail_fast?: boolean 337 | } 338 | 339 | /** The response object returned by the `/route` endpoint. */ 340 | export interface GraphHopperRouteResponse { 341 | hints?: JSONObject 342 | paths: GraphHopperRoutePath[] 343 | /** Additional information for your request */ 344 | info: GraphHopperResponseInfo 345 | } 346 | /** The response object returned by the `/isochrone` endpoint. */ 347 | export interface GraphHopperIsochroneResponse { 348 | polygons: Feature[] 349 | copyrights: string[] 350 | } 351 | 352 | /** The response object returned by the `/matrix` endpoint. */ 353 | export interface GraphHopperMatrixResponse { 354 | /** 355 | * The distance matrix for the specified points in the same order as the time matrix. 356 | * The distances are in meters. If fail_fast=false the matrix will contain null for connections 357 | * that could not be found. 358 | */ 359 | distances?: number[][] 360 | /** 361 | * The time matrix for the specified points in the order `[[from1->to1, from1->to2, ...], [from2->to1, from2->to2, ...], ...]`. 362 | * The times are in seconds. If `fail_fast=false` the matrix will contain null for connections that could not be found. 363 | * 364 | */ 365 | times?: number[][] 366 | /** 367 | * The weight matrix for the specified points in the same order as the time matrix. 368 | * The weights for different vehicle profiles can have a different unit but the weights array 369 | * is perfectly suited as input for Vehicle Routing Problems as it is currently faster to calculate. 370 | * If fail_fast=false the matrix will contain null for connections that could not be found. 371 | */ 372 | weights?: number[][] 373 | /** Additional information for your request */ 374 | info: GraphHopperResponseInfo 375 | /** Optional. Additional response data. */ 376 | hints?: GraphHopperMatrixHint[] 377 | } 378 | 379 | /** 380 | * The profile is used to determine the network, speed and other physical attributes to use 381 | * for routing the vehicle or pedestrian. 382 | * 383 | * @see {@link https://docs.graphhopper.com/#section/Map-Data-and-Routing-Profiles} for full documentation. 384 | */ 385 | export type GraphHopperProfile = 386 | | "car" 387 | | "car_delivery" 388 | | "car_avoid_ferry" 389 | | "car_avoid_motorway" 390 | | "car_avoid_toll" 391 | | "small_truck" 392 | | "small_truck_delivery" 393 | | "truck" 394 | | "scooter" 395 | | "scooter_delivery" 396 | | "foot" 397 | | "hike" 398 | | "bike" 399 | | "mtb" 400 | | "racingbike" 401 | 402 | /** 403 | * Specifies on which side a point should be relative to the driver when she leaves/arrives 404 | * at a start/target/via point. 405 | */ 406 | type GraphHopperCurbside = "any" | "right" | "left" 407 | 408 | type GraphHopperDetail = 409 | | "street_name" 410 | | "street_ref" 411 | | "street_destination" 412 | | "roundabout" 413 | | "country" 414 | | "time" 415 | | "distance" 416 | | "max_speed" 417 | | "max_weight" 418 | | "max_width" 419 | | "toll" 420 | | "road_class" 421 | | "road_class_link" 422 | | "road_access" 423 | | "road_environment" 424 | | "hazmat" 425 | | "hazmat_tunnel" 426 | | "hazmat_water" 427 | | "lanes" 428 | | "surface" 429 | | "smoothness" 430 | | "hike_rating" 431 | | "mtb_rating" 432 | | "foot_network" 433 | | "bike_network" 434 | | "get_off_bike" 435 | 436 | /** 437 | * A custom model allows you to modify the default routing behavior of a vehicle profile by 438 | * specifying a set of rules in JSON language. 439 | * 440 | * @see {@link https://docs.graphhopper.com/#section/Custom-Model} for the full documentation. 441 | */ 442 | type GraphHopperCustomModel = JSONObject 443 | 444 | type GraphHopperSnapPrevention = 445 | | "motorway" 446 | | "trunk" 447 | | "ferry" 448 | | "tunnel" 449 | | "bridge" 450 | | "ford" 451 | 452 | type GraphHopperAlgorithm = "round_trip" | "alternative_route" 453 | 454 | /** A route path object that includes the points along the route as well as additional route attributes. */ 455 | export interface GraphHopperRoutePath { 456 | /** 457 | * The total distance, in meters. 458 | * 459 | * @see {@link https://www.graphhopper.com/blog/2019/11/28/routing-api-using-path-details/} for more details. 460 | * 461 | */ 462 | distance: number 463 | /** 464 | * The total travel time, in milliseconds. 465 | * 466 | * @see {@link https://www.graphhopper.com/blog/2019/11/28/routing-api-using-path-details/} for more details. 467 | */ 468 | time: number 469 | /** The total ascent, in meters. */ 470 | ascend: number 471 | /** The total descent, in meters. */ 472 | descend: number 473 | /** The geometry of the route. The format depends on the value of points_encoded. */ 474 | points?: LineString | string // not returned if `calc_points=false` 475 | /** The snapped input points. The format depends on the value of points_encoded. */ 476 | snapped_waypoints: LineString | string 477 | /** 478 | * Whether the points and snapped_waypoints fields are polyline-encoded strings rather than 479 | * JSON arrays of coordinates. See the field description for more information on the two formats. 480 | */ 481 | points_encoded: boolean 482 | /** The bounding box of the route geometry. */ 483 | bbox: [number, number, number, number] 484 | /** 485 | * The instructions for this route. This feature is under active development, and our 486 | * instructions can sometimes be misleading, so be mindful when using them for navigation. 487 | */ 488 | instructions: GraphHopperInstruction[] 489 | /** 490 | * Details, as requested with the details parameter. 491 | * Consider the value `{"street_name": [[0,2,"Frankfurter Straße"],[2,6,"Zollweg"]]}`. 492 | * In this example, the route uses two streets: The first, Frankfurter Straße, is used 493 | * between points[0] and points[2], and the second, Zollweg, between points[2] and points[6]. 494 | */ 495 | details: { [Property in keyof GraphHopperDetail]: JSONValue } 496 | /** 497 | * An array of indices (zero-based), specifiying the order in which the input points are visited. 498 | * Only present if the optimize parameter was used. 499 | */ 500 | points_order: number[] 501 | } 502 | 503 | interface GraphHopperInstruction { 504 | /** 505 | * A description what the user has to do in order to follow the route. The language 506 | * depends on the locale parameter. 507 | */ 508 | text: string 509 | /** The name of the street to turn onto in order to follow the route. */ 510 | street_name: string 511 | /** The distance for this instruction, in meters. */ 512 | distance: number 513 | /** The duration for this instruction, in milliseconds. */ 514 | time: number 515 | /** 516 | * Two indices into points, referring to the beginning and the end of the segment of 517 | * the route this instruction refers to. 518 | */ 519 | interval: [number, number] 520 | sign: number 521 | /** 522 | * A number which specifies the sign to show. 523 | * @see {@link https://docs.graphhopper.com/#operation/postRoute/200/application/json/paths/instructions/sign} for full documentation. 524 | */ 525 | exit_number: number 526 | /** 527 | * Only available for roundabout instructions (sign is 6). The count of exits at which the 528 | * route leaves the roundabout. 529 | */ 530 | turn_angle: number 531 | } 532 | 533 | interface GraphHopperResponseInfo { 534 | /** Attribution according to our documentation is necessary if no white-label option included. */ 535 | copyright: string 536 | /** How long it took to generate the response. */ 537 | took: number 538 | } 539 | 540 | /** 541 | * Properties of the returned Features. 542 | */ 543 | export type GraphHopperIsochroneProps = { 544 | bucket: number 545 | } 546 | 547 | type GraphHopperMatrixOut = "weights" | "times" | "distances" 548 | 549 | interface GraphHopperMatrixHint { 550 | message: string 551 | details: string 552 | invalid_from_points?: number[] 553 | invalid_to_points?: number[] 554 | point_pairs?: [number, number][] 555 | } 556 | -------------------------------------------------------------------------------- /packages/graphhopper/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/es" 5 | }, 6 | "files": ["index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/ors/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GIS • OPS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/ors/README.md: -------------------------------------------------------------------------------- 1 | # RoutingJS: ORS 2 | 3 | Promise-based web client for [OpenRouteService](https://openrouteservice.org). 4 | 5 | ## Example 6 | 7 | ```js 8 | import { ORS } from "@routingjs/ors" 9 | 10 | const ors = new ORS({ apiKey: "my-api-key" }) // URL defaults to https://api.openrouteservice.org 11 | ors.directions( 12 | [ 13 | [8.512516, 47.380742], 14 | [8.557835, 47.359467], 15 | ], 16 | "driving-car" 17 | ).then((d) => { 18 | // do stuff with the directions response 19 | d.directions.forEach((direction) => { 20 | console.log(direction.feature) 21 | }) 22 | }) 23 | ``` 24 | 25 | ## Installation 26 | 27 | ``` 28 | npm install @routingjs/ors 29 | ``` 30 | 31 | --- 32 | 33 | This package is part of the [RoutingJS project](https://github.com/gis-ops/routingjs), which aims to provide consistent access to various vehicle routing APIs. 34 | -------------------------------------------------------------------------------- /packages/ors/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseRouter, 3 | ClientConstructorArgs, 4 | Client, 5 | Direction, 6 | Directions, 7 | RoutingJSAPIError, 8 | ErrorProps, 9 | DirectionFeat, 10 | Matrix, 11 | Isochrone, 12 | Isochrones, 13 | Waypoint, 14 | } from "@routingjs/core" 15 | 16 | import { 17 | ORSFormat, 18 | ORSIsochroneParams, 19 | ORSIsochroneResponse, 20 | ORSMatrixParams, 21 | ORSMatrixResponse, 22 | ORSProfile, 23 | ORSRoute, 24 | ORSRouteParams, 25 | ORSRouteResponse, 26 | ORSUnit, 27 | } from "./parameters" 28 | import { decode } from "@googlemaps/polyline-codec" 29 | import { AxiosError } from "axios" 30 | 31 | type ORSErrorResponseProps = { 32 | error: { 33 | code: number 34 | message: string 35 | } 36 | info?: { 37 | engine: { version: string; build_date: string } 38 | timestamp: number 39 | } 40 | } 41 | 42 | /** 43 | * `ORSErrorProps` returns additional information about the error thrown by the 44 | * ORS routing engine. It sends a JSON response with two props: error and info where the error 45 | * prop contains the error code and message. The info prop contains the engine version and 46 | * build date. 47 | */ 48 | export interface ORSErrorProps extends ErrorProps { 49 | errorCode?: number 50 | } 51 | 52 | export type ORSAPIError = RoutingJSAPIError 53 | 54 | const handleORSError = (error: AxiosError) => { 55 | const props: ORSErrorProps = { 56 | statusCode: error.response?.status, 57 | status: error.response?.statusText, 58 | errorCode: error.response?.data.error.code, 59 | errorMessage: error.response?.data.error.message, 60 | } 61 | throw new RoutingJSAPIError(error.message, props) 62 | } 63 | 64 | // we pass the coordinates as the `locations` top level parameter 65 | export type ORSDirectionsOpts = Omit 66 | 67 | export type ORSMatrixOpts = Pick< 68 | ORSMatrixParams, 69 | "metrics" | "resolve_locations" 70 | > 71 | 72 | export type ORSIsochroneOpts = Omit< 73 | ORSIsochroneParams, 74 | "locations" | "range" | "interval" 75 | > 76 | 77 | export type ORSDirections = Directions 78 | 79 | export type ORSIsochrones = Isochrones 80 | 81 | export type ORSMatrix = Matrix 82 | 83 | export type ORSClient = Client< 84 | ORSRouteResponse | ORSIsochroneResponse | ORSMatrixResponse, 85 | undefined, // we don't make any GET requests to ORS 86 | ORSRouteParams | ORSMatrixParams | ORSIsochroneParams 87 | > 88 | 89 | export class ORS implements BaseRouter { 90 | client: ORSClient 91 | apiKey?: string 92 | constructor(clientArgs?: ClientConstructorArgs) { 93 | const { 94 | apiKey, 95 | baseUrl, 96 | userAgent, 97 | timeout, 98 | retryOverQueryLimit, 99 | maxRetries, 100 | axiosOpts, 101 | } = clientArgs || {} 102 | 103 | let { headers } = clientArgs || {} 104 | 105 | if (apiKey) { 106 | headers = { ...headers, Authorization: apiKey } 107 | } 108 | 109 | const defaultURL = "https://api.openrouteservice.org" 110 | 111 | this.client = new Client( 112 | baseUrl || defaultURL, 113 | userAgent, 114 | timeout, 115 | retryOverQueryLimit, 116 | headers, 117 | maxRetries, 118 | axiosOpts 119 | ) 120 | } 121 | directions( 122 | locations: ([number, number] | Waypoint)[], 123 | profile: ORSProfile, 124 | directionsOpts?: ORSDirectionsOpts, 125 | dryRun?: false, 126 | format?: ORSFormat 127 | ): Promise 128 | directions( 129 | locations: ([number, number] | Waypoint)[], 130 | profile: ORSProfile, 131 | directionsOpts: ORSDirectionsOpts, 132 | dryRun: true, 133 | format?: ORSFormat 134 | ): Promise 135 | public async directions( 136 | locations: ([number, number] | Waypoint)[], 137 | profile: ORSProfile, 138 | directionsOpts: ORSDirectionsOpts = {}, 139 | dryRun?: boolean, 140 | format: ORSFormat = "json" 141 | ): Promise { 142 | if (typeof directionsOpts.options === "object") { 143 | if ( 144 | Object.prototype.hasOwnProperty.call( 145 | directionsOpts.options, 146 | "restrictions" 147 | ) && 148 | !Object.prototype.hasOwnProperty.call( 149 | directionsOpts.options, 150 | "vehicle_type" 151 | ) 152 | ) { 153 | throw new Error( 154 | "ORS: options.vehicle_type must be specified for driving-hgv if restrictions are set." 155 | ) 156 | } 157 | } 158 | 159 | const params: ORSRouteParams = { 160 | coordinates: locations.map((c) => 161 | Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat] 162 | ), 163 | ...directionsOpts, 164 | } 165 | 166 | return this.client 167 | .request({ 168 | endpoint: `/v2/directions/${profile}/${format}`, 169 | postParams: params, 170 | dryRun, 171 | }) 172 | .then((res: ORSRouteResponse) => { 173 | if (typeof res === "object") { 174 | return ORS.parseDirectionsResponse( 175 | res, 176 | format, 177 | directionsOpts.units || "m" 178 | ) 179 | } else { 180 | return res 181 | } 182 | }) 183 | .catch(handleORSError) 184 | } 185 | 186 | public static parseDirectionsResponse( 187 | response: ORSRouteResponse, 188 | format: ORSFormat, 189 | units?: ORSUnit 190 | ): ORSDirections { 191 | let factor = 1 192 | 193 | if (units === "km") { 194 | factor = 1000 195 | } 196 | 197 | if (units === "mi") { 198 | factor = 0.621371 * 1000 199 | } 200 | 201 | if (format === "geojson") { 202 | const routes: Direction[] = [] 203 | response.features?.forEach((feature) => { 204 | feature.properties = { 205 | ...feature.properties, 206 | ...{ 207 | duration: feature.properties.summary.duration, 208 | distance: feature.properties.summary.distance * factor, 209 | }, 210 | } 211 | routes.push(new Direction(feature)) 212 | }) 213 | 214 | return new Directions(routes, response) 215 | } else { 216 | // format is json 217 | const routes: Direction[] = [] 218 | response.routes?.forEach((route) => { 219 | let geom = null 220 | if (route.geometry) { 221 | geom = decode(route.geometry, 5).map(([lat, lon]) => [ 222 | lon, 223 | lat, 224 | ]) 225 | } 226 | 227 | const feat: DirectionFeat = { 228 | type: "Feature", 229 | properties: { 230 | distance: route.summary.distance * factor, 231 | duration: route.summary.duration, 232 | }, 233 | 234 | geometry: 235 | geom === null 236 | ? null 237 | : { 238 | type: "LineString", 239 | coordinates: geom, 240 | }, 241 | } 242 | 243 | routes.push(new Direction(feat, route)) 244 | }) 245 | 246 | return new Directions(routes, response) 247 | } 248 | } 249 | 250 | reachability( 251 | location: [number, number] | Waypoint, 252 | profile: string, 253 | intervals: number[], 254 | isochronesOpts?: ORSIsochroneOpts, 255 | dryRun?: false 256 | ): Promise 257 | reachability( 258 | location: [number, number] | Waypoint, 259 | profile: string, 260 | intervals: number[], 261 | isochronesOpts: ORSIsochroneOpts, 262 | dryRun: true 263 | ): Promise 264 | async reachability( 265 | location: [number, number] | Waypoint, 266 | profile: string, 267 | intervals: number[], 268 | isochronesOpts: ORSIsochroneOpts = {}, 269 | dryRun?: boolean 270 | ): Promise { 271 | const { range_type, ...rest } = isochronesOpts 272 | const params: ORSIsochroneParams = { 273 | locations: Array.isArray(location) 274 | ? [[location[1], location[0]]] 275 | : [[location.lon, location.lat]], // format must be lon/lat 276 | range: intervals, 277 | ...rest, 278 | } 279 | 280 | if (range_type) { 281 | params.range_type = range_type 282 | } 283 | 284 | return this.client 285 | .request({ 286 | endpoint: `/v2/isochrones/${profile}`, 287 | postParams: params, 288 | dryRun, 289 | }) 290 | .then((res: ORSIsochroneResponse) => { 291 | if (typeof res === "object") { 292 | return ORS.parseIsochroneResponse(res, range_type) 293 | } else { 294 | return res 295 | } 296 | }) 297 | .catch(handleORSError) 298 | } 299 | 300 | public static parseIsochroneResponse( 301 | response: ORSIsochroneResponse, 302 | intervalType?: "time" | "distance" 303 | ): ORSIsochrones { 304 | const isochrones: Isochrone[] = [] 305 | 306 | response.features.forEach((feature) => { 307 | isochrones.push( 308 | new Isochrone( 309 | feature.properties.center, 310 | feature.properties.interval, 311 | intervalType ? intervalType : "time", 312 | feature 313 | ) 314 | ) 315 | }) 316 | 317 | return new Isochrones(isochrones, response) 318 | } 319 | 320 | matrix( 321 | locations: ([number, number] | Waypoint)[], 322 | profile: ORSProfile, 323 | matrixOpts?: ORSMatrixOpts, 324 | dryRun?: false 325 | ): Promise 326 | matrix( 327 | locations: ([number, number] | Waypoint)[], 328 | profile: ORSProfile, 329 | matrixOpts: ORSMatrixOpts, 330 | dryRun: true 331 | ): Promise 332 | async matrix( 333 | locations: ([number, number] | Waypoint)[], 334 | profile: ORSProfile, 335 | matrixOpts: ORSMatrixOpts = {}, 336 | dryRun?: boolean 337 | ): Promise { 338 | const params: ORSMatrixParams = { 339 | locations: locations.map((c) => 340 | Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat] 341 | ), 342 | ...matrixOpts, 343 | } 344 | 345 | return this.client 346 | .request({ 347 | endpoint: `/v2/matrix/${profile}/json`, 348 | postParams: params, 349 | dryRun, 350 | }) 351 | .then((res) => { 352 | if (typeof res === "object") { 353 | return ORS.parseMatrixResponse( 354 | res as ORSMatrixResponse 355 | ) as ORSMatrix 356 | } else { 357 | return res 358 | } 359 | }) 360 | .catch(handleORSError) 361 | } 362 | 363 | public static parseMatrixResponse(response: ORSMatrixResponse): ORSMatrix { 364 | return new Matrix(response.durations, response.distances, response) 365 | } 366 | } 367 | 368 | // make all exported types and interfaces available to the public API 369 | export * from "./parameters" 370 | -------------------------------------------------------------------------------- /packages/ors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@routingjs/ors", 3 | "version": "0.3.0", 4 | "description": "RoutingJS OpenRouteService Module", 5 | "main": "dist/js/index.js", 6 | "module": "dist/es/index.js", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "typings": "dist/js/index.d.ts", 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "npm-run-all build:*", 17 | "build:js": "tsc --outDir dist/js --module commonjs", 18 | "build:es": "tsc" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/gis-ops/routingjs.git" 23 | }, 24 | "keywords": [ 25 | "routing", 26 | "vehicle routing", 27 | "navigation", 28 | "api", 29 | "geo", 30 | "geospatial" 31 | ], 32 | "author": "Christian Beiwinkel ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/gis-ops/routingjs/issues" 36 | }, 37 | "homepage": "https://github.com/gis-ops/routingjs#readme", 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.1.2", 40 | "@commitlint/config-conventional": "^17.1.0", 41 | "@rollup/plugin-typescript": "^9.0.2", 42 | "@types/geojson": "^7946.0.10", 43 | "@types/jest": "^29.1.2", 44 | "@types/node": "^18.11.0", 45 | "@typescript-eslint/eslint-plugin": "^5.40.0", 46 | "@typescript-eslint/parser": "^5.40.0", 47 | "eslint": "^8.25.0", 48 | "husky": "^8.0.1", 49 | "jest": "^29.2.0", 50 | "npm-run-all": "*", 51 | "prettier": "^2.7.1", 52 | "rollup": "^3.2.5", 53 | "ts-jest": "^29.0.3", 54 | "typescript": "^4.8.4" 55 | }, 56 | "dependencies": { 57 | "@googlemaps/polyline-codec": "^1.0.28", 58 | "@routingjs/core": "^0.3.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/ors/parameters.ts: -------------------------------------------------------------------------------- 1 | import { Feature, Geometry, LineString } from "geojson" 2 | 3 | /** Specifies the mode of transport. */ 4 | export type ORSProfile = 5 | | "driving-car" 6 | | "driving-hgv" 7 | | "foot-walking" 8 | | "foot-hiking" 9 | | "cycling-regular" 10 | | "cycling-road" 11 | | "cycling-mountain" 12 | | "cycling-electric" 13 | 14 | interface ORSBaseParams { 15 | /** Arbitrary identification string of the request reflected in the meta information. */ 16 | id?: string 17 | /** 18 | * Specifies the distance units only if range_type is set to distance. 19 | * 20 | * @defaultValue 21 | * Default: m. 22 | */ 23 | units?: ORSUnit 24 | } 25 | 26 | export interface ORSRouteParams extends ORSBaseParams { 27 | /** The locations to use for the route as an array of longitude/latitude pairs */ 28 | coordinates: [number, number][] 29 | /** 30 | * Specifies whether alternative routes are computed, and parameters for the algorithm determining 31 | * suitable alternatives. 32 | */ 33 | alternative_routes?: ORSAlternateRouteParam 34 | /** List of route attributes */ 35 | attrributes?: ORSAttribute[] 36 | /** Forces the route to keep going straight at waypoints restricting uturns there even if it would be faster. */ 37 | continue_straight?: boolean 38 | /** 39 | * For advanced options formatted as json object. 40 | * 41 | * @see {@link https://giscience.github.io/openrouteservice/documentation/routing-options/Examples.html} for examples. 42 | */ 43 | options?: object 44 | /** 45 | * Specifies whether to return elevation values for points. Please note that elevation also gets 46 | * encoded for json response encoded polyline. 47 | */ 48 | elevation?: boolean 49 | /** The extra info items to include in the response */ 50 | extra_info?: ORSExtraInfo[] 51 | /** 52 | * Specifies whether to simplify the geometry. Simplify geometry cannot be applied to routes 53 | * with more than one segment and when extra_info is required. 54 | */ 55 | geometry_simplify?: boolean 56 | /** Specifies whether to return instructions. */ 57 | instructions?: boolean 58 | /** Select html for more verbose instructions. */ 59 | instructions_format?: ORSInstructionFormat 60 | /** Language for the route instructions. */ 61 | language?: string 62 | /** 63 | * Specifies whether the maneuver object is included into the step object or not. 64 | * 65 | * @defaultValue 66 | * false 67 | */ 68 | maneuvers?: boolean 69 | /** Specifies the route preference. */ 70 | preference?: ORSPreference 71 | /** 72 | * A list of maximum distances (measured in metres) that limit the search of nearby road segments 73 | * to every given waypoint. The values must be greater than 0, the value of -1 specifies using the 74 | * maximum possible search radius. The number of radiuses correspond to the number of waypoints. 75 | * If only a single value is given, it will be applied to all waypoints. 76 | */ 77 | radiuses?: number[] 78 | /** 79 | * Provides bearings of the entrance and all passed roundabout exits. Adds the exit_bearings 80 | * array to the step object in the response. 81 | */ 82 | roundabout_exits?: boolean 83 | /** 84 | * Specifies the segments that should be skipped in the route calculation. A segment is the 85 | * connection between two given coordinates and the counting starts with 1 for the connection 86 | * between the first and second coordinate. 87 | */ 88 | skip_segments?: number[] 89 | /** 90 | * Suppress warning messages in the response 91 | * 92 | * @defaultValue 93 | * false 94 | */ 95 | suppress_warnings?: boolean 96 | /** 97 | * Specifies whether to return geometry. 98 | * 99 | * @defaultValue 100 | * true 101 | */ 102 | geometry?: boolean 103 | /** 104 | * The maximum speed specified by user. 105 | */ 106 | maximum_speed?: number 107 | } 108 | 109 | export interface ORSIsochroneParams extends ORSBaseParams { 110 | /** 111 | * The locations to use for the route as an array of longitude/latitude pairs 112 | */ 113 | locations: [number, number][] 114 | /** 115 | * Maximum range value of the analysis in seconds for time and metres for distance. Alternatively 116 | * a comma separated list of specific range values. Ranges will be the same for all locations. 117 | */ 118 | range: number[] 119 | /** List of isochrones attributes */ 120 | attributes?: ORSIsoAttribute[] 121 | /** 122 | * Specifies whether to return intersecting polygons. 123 | * 124 | * @defaultValue 125 | * false 126 | */ 127 | intersections?: boolean 128 | /** 129 | * Interval of isochrones or equidistants. This is only used if a single range value is given. 130 | * Value in seconds for time and meters for distance. 131 | * 132 | * @defaultValue 133 | * false 134 | */ 135 | interval?: number 136 | /** 137 | * start treats the location(s) as starting point, destination as goal. 138 | * 139 | * @defaultValue 140 | * start 141 | */ 142 | location_type?: "start" | "destination" 143 | /** Specifies the isochrones reachability type. */ 144 | range_type?: "time" | "distance" 145 | /** 146 | * Applies a level of generalisation to the isochrone polygons generated as a `smoothing_factor` 147 | * between 0 and 100.0. 148 | */ 149 | smoothing?: number 150 | /** Additional options for the isochrones request */ 151 | options?: object 152 | /** 153 | * Specifies the area unit. 154 | * @defaultValue 155 | * m 156 | */ 157 | area_units?: ORSUnit 158 | /** Departure date and time provided in local time zone */ 159 | time?: string 160 | } 161 | 162 | export interface ORSMatrixParams extends ORSBaseParams { 163 | /** List of comma separated lists of longitude,latitude coordinates. */ 164 | locations: [number, number][] 165 | /** 166 | * A list of indices that refers to the list of locations (starting with 0). 167 | * `{index_1},{index_2}[,{index_N} ...]` or all (default). `[0,3]` for the first and 168 | * fourth locations 169 | * 170 | * @defaultValue 171 | * all 172 | */ 173 | destinations?: number[] 174 | /** 175 | * Arbitrary identification string of the request reflected in the meta information. 176 | */ 177 | metrics?: ("distance" | "duration")[] 178 | /** 179 | * Specifies whether given locations are resolved or not. If the parameter value set to true, 180 | * every element in destinations and sources will contain a name element that identifies the name 181 | * of the closest street. 182 | * 183 | * @defaultValue 184 | * Default is false. 185 | */ 186 | resolve_locations?: boolean 187 | /** 188 | * A list of indices that refers to the list of locations (starting with 0). 189 | * `{index_1},{index_2}[,{index_N} ...]` or all (default). `[0,3]` for the first and 190 | * fourth locations 191 | * 192 | * @defaultValue 193 | * all 194 | */ 195 | sources?: number[] 196 | } 197 | 198 | export interface ORSAlternateRouteParam { 199 | share_factor: number 200 | /** Number of alternative routes (up to 3) */ 201 | target_count: number 202 | weight_factor: number 203 | } 204 | 205 | export type ORSPreference = "fastest" | "shortest" | "recommended" 206 | export type ORSUnit = "m" | "km" | "mi" 207 | export type ORSAttribute = "avgspeed" | "detourfactor" | "percentage" 208 | export type ORSExtraInfo = 209 | | "steepness" 210 | | "suitability" 211 | | "surface" 212 | | "waycategory" 213 | | "waytype" 214 | | "tollways" 215 | | "traildifficulty" 216 | | "roadaccessrestrictions" 217 | export type ORSInstructionFormat = "html" | "text" 218 | export type ORSFormat = "geojson" | "json" 219 | export type ORSIsoAttribute = "area" | "reachfactor" | "total_pop" 220 | 221 | interface ORSBaseResponse { 222 | /** Information about the service and request */ 223 | metadata: ORSMetaData 224 | } 225 | 226 | export interface ORSRouteResponse extends ORSBaseResponse { 227 | bbox: ORSBbox 228 | /** A list of routes returned from the request */ 229 | routes?: ORSRoute[] 230 | /** If the `/geojson` endpoint was requested */ 231 | type?: "FeatureCollection" 232 | /** If the `/geojson` endpoint was requested */ 233 | features?: Feature[] 234 | } 235 | 236 | export interface ORSIsochroneResponse extends ORSBaseResponse { 237 | /** Bounding box that covers all returned isochrones */ 238 | bbox: ORSBbox 239 | /** The features containing the isochrone/isodistance geometries */ 240 | features: Feature[] 241 | /** FeatureCollection */ 242 | type: string 243 | } 244 | 245 | export interface ORSMatrixResponse extends ORSBaseResponse { 246 | /** The individual sourcesof the matrix calculations. */ 247 | sources: ORSMatrixResult[] 248 | /** The individual destinations of the matrix calculations. */ 249 | destinations: ORSMatrixResult[] 250 | /** The distances of the matrix calculations. */ 251 | distances: number[][] 252 | /** The durations of the matrix calculations. */ 253 | durations: number[][] 254 | } 255 | 256 | interface ORSMatrixResult { 257 | /** 258 | * `{longitude},{latitude}` coordinates of the closest accessible point on the routing graph 259 | */ 260 | location: [number, number] 261 | /** 262 | * Name of the street the closest accessible point is situated on. Only for `resolve_locations=true` 263 | * and only if name is available. 264 | */ 265 | name?: string 266 | /** Distance between the source/destination Location and the used point on the routing graph. */ 267 | snapped_distance?: number 268 | } 269 | 270 | interface ORSMetaData { 271 | /** Copyright and attribution information */ 272 | attribution: string 273 | /** Information about the routing service */ 274 | engine: ORSEngine 275 | /** ID of the request (as passed in by the query) */ 276 | id: string 277 | /** The MD5 hash of the OSM planet file that was used for generating graphs */ 278 | osm_file_md5_hash: string 279 | /** The information that was used for generating the route */ 280 | query: ORSRouteParams 281 | /** The service that was requested */ 282 | service: string 283 | /** System message */ 284 | system_message: string 285 | /** Time that the request was made (UNIX Epoch time) */ 286 | timestamp: number 287 | } 288 | 289 | interface ORSEngine { 290 | /** The date that the service was last updated */ 291 | build_date: string 292 | /** The date that the graph data was last updated */ 293 | graph_date: string 294 | /** The backend version of the openrouteservice that was queried */ 295 | version: string 296 | } 297 | 298 | export interface ORSRoute { 299 | /** Arrival date and time */ 300 | arrival: string 301 | /** A bounding box which contains the entire route */ 302 | bbox: ORSBbox 303 | /** Departure date and time */ 304 | departure: string 305 | /** 306 | * List of extra info objects representing the extra info items that were requested for the route. 307 | */ 308 | extras: object[] 309 | /** List containing the segments and its corresponding steps which make up the route. */ 310 | segments: ORSSegment[] 311 | /** Summary information about the route */ 312 | summary: ORSSummary 313 | /** List of warnings that have been generated for the route */ 314 | warnings?: ORSWarning[] 315 | /** List containing the indices of way points corresponding to the geometry. */ 316 | way_points?: number[] 317 | /** The route geometry as a decoded polyline */ 318 | geometry?: string 319 | } 320 | 321 | interface ORSSegment { 322 | /** Contains ascent of this segment in metres. */ 323 | ascent: number 324 | /** Contains the average speed of this segment in km/h. */ 325 | avgspeed: number 326 | /** Contains descent of this segment in metres. */ 327 | descent: number 328 | /** 329 | * Contains the deviation compared to a straight line that would have the factor 1. Double 330 | * the Distance would be a 2. 331 | */ 332 | detourfactor: number 333 | /** Contains the distance of the segment in specified units. */ 334 | distance: number 335 | /** Contains the duration of the segment in seconds. */ 336 | duration: number 337 | /** Contains the proportion of the route in percent. */ 338 | percentage: number 339 | /** List containing the specific steps the segment consists of. */ 340 | steps: ORSStep[] 341 | } 342 | 343 | interface ORSStep { 344 | /** The distance for the step in metres. */ 345 | distance: number 346 | /** The duration for the step in seconds. */ 347 | duration: number 348 | /** Contains the bearing of the entrance and all passed exits in a roundabout */ 349 | extra_bearings: number[] 350 | /** Only for roundabouts. Contains the number of the exit to take. */ 351 | exit_number: number 352 | /** The routing instruction text for the step. */ 353 | instruction: string 354 | /** The maneuver to be performed */ 355 | maneuver?: ORSManeuver 356 | /** The name of the next street. */ 357 | name: string 358 | /** The instruction action for symbolisation purposes. */ 359 | type: number 360 | /** 361 | * List containing the indices of the steps start- and endpoint corresponding to the geometry. 362 | */ 363 | way_points: number[] 364 | } 365 | 366 | interface ORSManeuver { 367 | /** The azimuth angle (in degrees) of the direction right after the maneuver. */ 368 | bearing_after: number 369 | /** The azimuth angle (in degrees) of the direction right before the maneuver. */ 370 | bearing_before: number 371 | /** The coordinate of the point where a maneuver takes place. */ 372 | location: [number, number] 373 | } 374 | 375 | interface ORSSummary { 376 | /** Total ascent in meters. */ 377 | ascent: number 378 | /** Total descent in meters. */ 379 | descent: number 380 | /** Total route distance in specified units. */ 381 | distance: number 382 | /** Total duration in seconds. */ 383 | duration: number 384 | } 385 | 386 | interface ORSWarning { 387 | /** Identification code for the warning */ 388 | code: number 389 | /** The message associated with the warning */ 390 | message: string 391 | } 392 | 393 | type ORSBbox = [number, number, number, number] 394 | -------------------------------------------------------------------------------- /packages/ors/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/es" 5 | }, 6 | "files": ["index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/osrm/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GIS • OPS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/osrm/README.md: -------------------------------------------------------------------------------- 1 | # RoutingJS: OSRM 2 | 3 | Promise-based web client for the [Open Source Routing Machine](https://project-osrm.org). 4 | 5 | ## Example 6 | 7 | ```js 8 | import { OSRM } from "@routingjs/osrm" 9 | 10 | const osrm = new OSRM() // URL defaults to https://routing.openstreetmap.de/routed-bike 11 | osrm.directions([ 12 | [8.512516, 47.380742], 13 | [8.557835, 47.359467], 14 | ]).then((d) => { 15 | // do stuff with the directions response 16 | d.directions.forEach((direction) => { 17 | console.log(direction.feature) 18 | }) 19 | }) 20 | ``` 21 | 22 | ## Installation 23 | 24 | ``` 25 | npm install @routingjs/osrm 26 | ``` 27 | 28 | --- 29 | 30 | This package is part of the [RoutingJS project](https://github.com/gis-ops/routingjs), which aims to provide consistent access to various vehicle routing APIs. 31 | -------------------------------------------------------------------------------- /packages/osrm/index.ts: -------------------------------------------------------------------------------- 1 | import { decode } from "@googlemaps/polyline-codec" 2 | import { 3 | Direction, 4 | DirectionFeat, 5 | Directions, 6 | Client, 7 | RoutingJSAPIError, 8 | ErrorProps, 9 | Matrix, 10 | BaseRouter, 11 | ClientConstructorArgs, 12 | Waypoint, 13 | } from "@routingjs/core" 14 | 15 | import { 16 | OSRMGeometryObject, 17 | OSRMGeometryType, 18 | OSRMOverviewType, 19 | OSRMRoute, 20 | OSRMRouteParams, 21 | OSRMRouteResponse, 22 | OSRMTableParams, 23 | OSRMTableResponse, 24 | } from "./parameters" 25 | import { AxiosError } from "axios" 26 | 27 | type OSRMErrorResponseProps = { 28 | code: number 29 | message: string 30 | } 31 | 32 | /** 33 | * `OSRMErrorProps` returns additional information about the error thrown by the 34 | * OSRM routing engine. It sends a JSON response with the following fields: code and message, 35 | * where the specific code defines the kind of error that occurred. 36 | */ 37 | export interface OSRMErrorProps extends ErrorProps { 38 | errorCode?: number 39 | } 40 | 41 | export type OSRMAPIError = RoutingJSAPIError 42 | 43 | const handleOSRMError = (error: AxiosError) => { 44 | const props: OSRMErrorProps = { 45 | statusCode: error.response?.status, 46 | status: error.response?.statusText, 47 | errorCode: error.response?.data.code, 48 | errorMessage: error.response?.data.message, 49 | } 50 | throw new RoutingJSAPIError(error.message, props) 51 | } 52 | 53 | interface OSRMBaseOpts { 54 | /** Limits the search to given radius in meters. */ 55 | radiuses?: (number | null)[] 56 | /** 57 | * Limits the search to segments with given bearing in degrees towards true north in 58 | * clockwise direction. 59 | */ 60 | bearings?: ([number, number | null] | null)[] 61 | } 62 | 63 | export interface OSRMDirectionsOpts extends OSRMBaseOpts { 64 | /** 65 | * Search for alternative routes and return as well. 66 | * 67 | * @remarks 68 | * Please note that even if an alternative route is requested, a result cannot be guaranteed. 69 | */ 70 | alternatives?: false | number 71 | /** Return route steps for each route leg */ 72 | steps?: boolean 73 | /** 74 | * Forces the route to keep going straight at waypoints constraining uturns there even if 75 | * it would be faster. Default value depends on the profile. 76 | */ 77 | continueStraight?: boolean | "default" 78 | /** Returns additional metadata for each coordinate along the route geometry. */ 79 | annotations?: boolean 80 | /** Returned route geometry format (influences overview and per step) */ 81 | geometries?: OSRMGeometryType 82 | /** 83 | * Add overview geometry either full, simplified according to highest zoom level it 84 | * could be display on, or not at all. 85 | */ 86 | overview?: OSRMOverviewType 87 | } 88 | 89 | export interface OSRMMatrixOpts extends OSRMDirectionsOpts { 90 | /** Use location with given index as source. */ 91 | sources?: number[] 92 | /** Use location with given index as destination. */ 93 | destinations?: number[] 94 | } 95 | 96 | export type OSRMDirections = Directions 97 | 98 | export type OSRMMatrix = Matrix 99 | 100 | export type OSRMClient = Client< 101 | OSRMRouteResponse | OSRMTableResponse, 102 | Partial | Partial 103 | > 104 | 105 | export class OSRM implements BaseRouter { 106 | client: OSRMClient 107 | apiKey?: string 108 | constructor(clientArgs?: ClientConstructorArgs) { 109 | const { 110 | apiKey, 111 | baseUrl, 112 | userAgent, 113 | headers, 114 | timeout, 115 | retryOverQueryLimit, 116 | maxRetries, 117 | axiosOpts, 118 | } = clientArgs || {} 119 | 120 | this.apiKey = apiKey // TODO: add to requests 121 | 122 | const defaultURL = "https://routing.openstreetmap.de/routed-bike" 123 | 124 | this.client = 125 | new Client( 126 | baseUrl || defaultURL, 127 | userAgent, 128 | timeout, 129 | retryOverQueryLimit, 130 | headers, 131 | maxRetries, 132 | axiosOpts 133 | ) || {} 134 | } 135 | 136 | /** 137 | * Makes a request to OSRM's `/route` endpoint. 138 | * 139 | * @param locations - The coordinates tuple the route should be calculated from in order of visit. Format: [lat, lon] 140 | * @param profile - Specifies the mode of transport (superfluous for OSRM) 141 | * @param directionsOpts - Additional parameters 142 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 143 | */ 144 | public async directions( 145 | locations: ([number, number] | Waypoint)[], 146 | profile: string, 147 | directionsOpts?: OSRMDirectionsOpts, 148 | dryRun?: false 149 | ): Promise 150 | public async directions( 151 | locations: ([number, number] | Waypoint)[], 152 | profile: string, 153 | directionsOpts: OSRMDirectionsOpts, 154 | dryRun: true 155 | ): Promise 156 | public async directions( 157 | locations: ([number, number] | Waypoint)[], 158 | profile = "driving", 159 | directionsOpts: OSRMDirectionsOpts = {}, 160 | dryRun = false 161 | ): Promise { 162 | const coords = locations 163 | .map((l) => 164 | Array.isArray(l) ? `${l[1]},${l[0]}` : `${l.lon},${l.lat}` 165 | ) 166 | .join(";") 167 | 168 | const params = OSRM.getDirectionParams(directionsOpts) 169 | 170 | return this.client 171 | .request({ 172 | endpoint: `/route/v1/${profile}/${coords}`, 173 | getParams: params, 174 | dryRun, 175 | }) 176 | .then((res: OSRMRouteResponse) => { 177 | return OSRM.parseDirectionsResponse(res) 178 | }) 179 | .catch(handleOSRMError) 180 | } 181 | 182 | protected static getDirectionParams( 183 | directionsOpts: OSRMDirectionsOpts = {} 184 | ): Partial { 185 | const params: Partial = {} 186 | 187 | if (directionsOpts.radiuses) { 188 | params.radiuses = directionsOpts.radiuses.join(";") 189 | } 190 | 191 | if (directionsOpts.bearings) { 192 | params.bearings = directionsOpts.bearings 193 | .map((bearing) => { 194 | if (bearing === null) { 195 | return "" 196 | } else { 197 | return `${bearing[0]},${ 198 | bearing[1] === null ? "" : bearing[1] 199 | }` 200 | } 201 | }) 202 | .join(";") 203 | } 204 | 205 | if (directionsOpts.alternatives !== undefined) { 206 | params.alternatives = directionsOpts.alternatives.toString() 207 | } 208 | 209 | if (directionsOpts.steps !== undefined) { 210 | params.steps = directionsOpts.steps.toString() 211 | } 212 | 213 | if (directionsOpts.continueStraight !== undefined) { 214 | params.continue_straight = 215 | directionsOpts.continueStraight.toString() 216 | } 217 | 218 | if (directionsOpts.annotations !== undefined) { 219 | params.annotations = directionsOpts.annotations.toString() 220 | } 221 | 222 | if (directionsOpts.geometries) { 223 | params.geometries = directionsOpts.geometries 224 | } 225 | 226 | if (directionsOpts.overview) { 227 | params.overview = directionsOpts.overview 228 | } 229 | 230 | return params 231 | } 232 | 233 | public static parseDirectionsResponse( 234 | response: OSRMRouteResponse, 235 | geometryFormat?: OSRMGeometryType 236 | ): OSRMDirections { 237 | const directions = response.routes.map((route) => { 238 | const feature: DirectionFeat = { 239 | type: "Feature", 240 | geometry: 241 | OSRM.parseGeometry(route.geometry, geometryFormat) || null, 242 | properties: { 243 | duration: route.duration 244 | ? Math.round(route.duration) 245 | : null, 246 | distance: route.distance 247 | ? Math.round(route.distance) 248 | : null, 249 | }, 250 | } 251 | return new Direction(feature, route) 252 | }) 253 | 254 | return new Directions(directions, response) 255 | } 256 | 257 | protected static parseGeometry( 258 | routeGeometry?: string | OSRMGeometryObject, 259 | geometryFormat?: OSRMGeometryType 260 | ): OSRMGeometryObject | undefined { 261 | if (routeGeometry !== undefined) { 262 | if (geometryFormat !== "geojson") { 263 | const path = decode( 264 | routeGeometry as string, 265 | geometryFormat === undefined || 266 | geometryFormat === "polyline" 267 | ? 5 268 | : 6 269 | ) as [number, number][] 270 | 271 | return { 272 | coordinates: path, 273 | type: "LineString", 274 | } 275 | } else { 276 | return routeGeometry as OSRMGeometryObject 277 | } 278 | } else { 279 | return undefined 280 | } 281 | } 282 | 283 | public async matrix( 284 | locations: ([number, number] | Waypoint)[], 285 | profile: string, 286 | matrixOpts?: OSRMMatrixOpts, 287 | dryRun?: false 288 | ): Promise 289 | public async matrix( 290 | locations: ([number, number] | Waypoint)[], 291 | profile: string, 292 | matrixOpts: OSRMMatrixOpts, 293 | dryRun: true 294 | ): Promise 295 | public async matrix( 296 | locations: ([number, number] | Waypoint)[], 297 | profile: string, 298 | matrixOpts: OSRMMatrixOpts = {}, 299 | dryRun?: boolean 300 | ): Promise { 301 | const coords = locations 302 | .map((l) => 303 | Array.isArray(l) ? `${l[1]},${l[0]}` : `${l.lon},${l.lat}` 304 | ) 305 | .join(";") 306 | 307 | const params = OSRM.getMatrixParams(matrixOpts) 308 | 309 | return this.client 310 | .request({ 311 | endpoint: `/table/v1/${profile}/${coords}`, 312 | getParams: params, 313 | dryRun, 314 | }) 315 | .then((res: OSRMTableResponse) => { 316 | return OSRM.parseMatrixResponse(res) 317 | }) 318 | .catch(handleOSRMError) 319 | } 320 | 321 | protected static getMatrixParams( 322 | matrixOpts: OSRMMatrixOpts 323 | ): Partial { 324 | const params: Partial = {} 325 | if (matrixOpts.radiuses) { 326 | params.radiuses = matrixOpts.radiuses.join(";") 327 | } 328 | 329 | if (matrixOpts.bearings) { 330 | params.bearings = matrixOpts.bearings 331 | .map((bearing) => { 332 | if (bearing === null) { 333 | return "" 334 | } else { 335 | return `${bearing[0]},${ 336 | bearing[1] === null ? "" : bearing[1] 337 | }` 338 | } 339 | }) 340 | .join(";") 341 | } 342 | 343 | if (matrixOpts.sources) { 344 | params.sources = matrixOpts.sources.join(";") 345 | } 346 | 347 | if (matrixOpts.destinations) { 348 | params.destinations = matrixOpts.destinations.join(";") 349 | } 350 | 351 | return params 352 | } 353 | 354 | public static parseMatrixResponse(response: OSRMTableResponse): OSRMMatrix { 355 | return new Matrix(response.durations, response.distances, response) 356 | } 357 | } 358 | 359 | export * from "./parameters" 360 | -------------------------------------------------------------------------------- /packages/osrm/osrm.test.ts: -------------------------------------------------------------------------------- 1 | import { OSRM } from "./index" 2 | import { assertError } from "../../util/error" 3 | 4 | const o = new OSRM({ baseUrl: "http://localhost:5000" }) 5 | 6 | describe("OSRM returns responses", () => { 7 | it("gets a direction response", async () => { 8 | await o 9 | .directions( 10 | [ 11 | [42.5063, 1.51886], 12 | [42.51007, 1.53789], 13 | ], 14 | "driving", 15 | { radiuses: [1000, 1000] } 16 | ) 17 | .then((d) => { 18 | expect(d).toHaveProperty("directions") 19 | expect(d.directions.length).toBeGreaterThan(0) 20 | }) 21 | }) 22 | 23 | it("gets a matrix response", async () => { 24 | await o 25 | .matrix( 26 | [ 27 | [42.5063, 1.51886], 28 | [42.51007, 1.53789], 29 | ], 30 | "driving", 31 | { radiuses: [1000, 1000] }, 32 | false 33 | ) 34 | .then((m) => { 35 | expect(m).toHaveProperty("durations") 36 | expect(m.durations.length).toBeGreaterThan(0) 37 | }) 38 | }) 39 | }) 40 | 41 | describe("Throws RoutingJSAPIError", () => { 42 | it("fails to get a direction response", async () => { 43 | await o 44 | .directions( 45 | [ 46 | [0.00001, 1], 47 | [42.51007, 1.53789], 48 | ], 49 | "driving", 50 | { radiuses: [1000, 1000] } 51 | ) 52 | .catch(assertError) 53 | }) 54 | 55 | it("fails to get a matrix response", async () => { 56 | await o 57 | .matrix( 58 | [ 59 | [0.00001, 1], 60 | [42.51007, 1.53789], 61 | ], 62 | "driving", 63 | { radiuses: [1000, 1000] }, 64 | false 65 | ) 66 | .catch(assertError) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /packages/osrm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@routingjs/osrm", 3 | "version": "0.3.0", 4 | "description": "RoutingJS OSRM Module", 5 | "main": "dist/js/index.js", 6 | "module": "dist/es/index.js", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "typings": "dist/js/index.d.ts", 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "npm-run-all build:*", 17 | "build:js": "tsc --outDir dist/js --module commonjs", 18 | "build:es": "tsc" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/gis-ops/routingjs.git" 23 | }, 24 | "keywords": [ 25 | "routing", 26 | "vehicle routing", 27 | "navigation", 28 | "api", 29 | "geo", 30 | "geospatial" 31 | ], 32 | "author": "Christian Beiwinkel ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/gis-ops/routingjs/issues" 36 | }, 37 | "homepage": "https://github.com/gis-ops/routingjs#readme", 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.1.2", 40 | "@commitlint/config-conventional": "^17.1.0", 41 | "@rollup/plugin-typescript": "^9.0.2", 42 | "@types/geojson": "^7946.0.10", 43 | "@types/jest": "^29.1.2", 44 | "@types/node": "^18.11.0", 45 | "@typescript-eslint/eslint-plugin": "^5.40.0", 46 | "@typescript-eslint/parser": "^5.40.0", 47 | "eslint": "^8.25.0", 48 | "husky": "^8.0.1", 49 | "jest": "^29.2.0", 50 | "npm-run-all": "*", 51 | "prettier": "^2.7.1", 52 | "rollup": "^3.2.5", 53 | "ts-jest": "^29.0.3", 54 | "typescript": "^4.8.4" 55 | }, 56 | "dependencies": { 57 | "@googlemaps/polyline-codec": "^1.0.28", 58 | "@routingjs/core": "^0.3.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/osrm/parameters.ts: -------------------------------------------------------------------------------- 1 | import { LineString } from "geojson" 2 | 3 | interface OSRMBaseParams { 4 | /** 5 | * String of format `{longitude},{latitude};{longitude},{latitude}[;{longitude},{latitude} ...]` 6 | * or `polyline({polyline})`. 7 | */ 8 | coordinates: string 9 | /** 10 | * Mode of transportation, is determined statically by the Lua profile that is used to prepare 11 | * the data using osrm-extract. Typically car, bike or foot if using one of the supplied profiles. 12 | */ 13 | profile: string 14 | /** 15 | * Limits the search to segments with given bearing in degrees towards true north in 16 | * clockwise direction. 17 | */ 18 | bearings?: string 19 | /** Limits the search to given radius in meters. */ 20 | radiuses?: string 21 | /** Hint from previous request to derive position in street network. */ 22 | hint?: string 23 | } 24 | 25 | export interface OSRMRouteParams extends OSRMBaseParams { 26 | /** 27 | * Search for alternative routes and return as well. 28 | * 29 | * @remarks 30 | * Please note that even if an alternative route is requested, a result cannot be guaranteed. 31 | */ 32 | alternatives?: string 33 | /** Return route steps for each route leg */ 34 | steps?: string 35 | /** Returns additional metadata for each coordinate along the route geometry. */ 36 | annotations?: string 37 | /** Returned route geometry format (influences overview and per step) */ 38 | geometries?: OSRMGeometryType 39 | /** 40 | * Add overview geometry either full, simplified according to highest zoom level it 41 | * could be display on, or not at all. 42 | */ 43 | overview?: OSRMOverviewType 44 | /** 45 | * Forces the route to keep going straight at waypoints constraining uturns there even if 46 | * it would be faster. Default value depends on the profile. 47 | */ 48 | continue_straight?: string 49 | } 50 | 51 | export type OSRMGeometryType = "polyline" | "polyline6" | "geojson" 52 | export type OSRMOverviewType = "simplified" | "full" | "false" 53 | 54 | interface OSRMBaseResponse { 55 | /** 56 | * Every response object has a code field containing one of the strings below or a 57 | * service dependent code: 58 | */ 59 | code: OSRMResponseCode 60 | /** Optional human-readable error message. All other status types are service dependent. */ 61 | message?: string 62 | } 63 | 64 | export interface OSRMRouteResponse extends OSRMBaseResponse { 65 | /** Array of Waypoint objects representing all waypoints in order */ 66 | waypoints?: OSRMWayPoint[] 67 | /** 68 | * An array of Route objects, ordered by descending recommendation rank. 69 | */ 70 | routes: OSRMRoute[] 71 | } 72 | 73 | export interface OSRMRoute { 74 | /** 75 | * The whole geometry of the route value depending on overview parameter, format depending 76 | * on the geometries parameter. See RouteStep's geometry field for a parameter documentation. 77 | */ 78 | geometry?: string | OSRMGeometryObject 79 | /** The legs between the given waypoints, an array of RouteLeg objects. */ 80 | legs: OSRMLeg[] 81 | /** The distance traveled by the route, in float meters. */ 82 | distance: number 83 | /** The estimated travel time, in float number of seconds. */ 84 | duration: number 85 | } 86 | 87 | export interface OSRMGeometryObject { 88 | coordinates: [number, number][] 89 | type: "LineString" 90 | } 91 | 92 | /** Represents a route between two waypoints. */ 93 | interface OSRMLeg { 94 | /** The distance traveled by this route leg, in float meters. */ 95 | distance?: number 96 | /** The estimated travel time, in float number of seconds. */ 97 | duration?: number 98 | /** Summary of the route taken as string. Depends on the steps parameter */ 99 | summary?: string 100 | /** Depends on the `steps` parameter */ 101 | steps?: OSRMStep[] 102 | /** Additional details about each coordinate along the route geometry */ 103 | annotation?: OSRMAnnotation 104 | } 105 | 106 | interface OSRMStep { 107 | geometry?: string | LineString 108 | maneuver?: OSRMManeuver 109 | /** The distance of travel from the maneuver to the subsequent step, in float meters */ 110 | distance?: number 111 | /** The estimated travel time, in float number of seconds. */ 112 | duration?: number 113 | /** The name of the way along which travel proceeds. */ 114 | name?: number 115 | /** A reference number or code for the way. Optionally included, if ref data is available for the given way. */ 116 | ref?: number | string 117 | /** The pronunciation hint of the way name. Will be undefined if there is no pronunciation hit. */ 118 | pronunciation?: string 119 | /** The destinations of the way. Will be undefined if there are no destinations. */ 120 | destinations?: any 121 | /** A string signifying the mode of transportation. */ 122 | mode?: string 123 | /** A StepManeuver object representing the maneuver. */ 124 | intersections?: OSRMIntersections[] 125 | } 126 | 127 | /** 128 | * An intersection gives a full representation of any cross-way the path passes bay. For every step, 129 | * the very first intersection (intersections[0]) corresponds to the location of the StepManeuver. 130 | * Further intersections are listed for every cross-way until the next turn instruction. 131 | */ 132 | interface OSRMIntersections { 133 | /** A [longitude, latitude] pair describing the location of the turn. */ 134 | location?: number[] 135 | /** 136 | * A list of bearing values (e.g. [0,90,180,270]) that are available at the intersection. The 137 | * bearings describe all available roads at the intersection. 138 | */ 139 | bearings?: number[] 140 | /** 141 | * A list of entry flags, corresponding in a 1:1 relationship to the bearings. A value of true 142 | * indicates that the respective road could be entered on a valid route. false indicates that 143 | * the turn onto the respective road would violate a restriction. 144 | */ 145 | entry?: boolean[] 146 | /** 147 | * index into bearings/entry array. Used to calculate the bearing just before the turn. 148 | */ 149 | in?: number 150 | /** index into the bearings/entry array. Used to extract the bearing just after the turn. */ 151 | out?: number 152 | /** 153 | * Array of Lane objects that denote the available turn lanes at the intersection. If no lane 154 | * information is available for an intersection, the lanes property will not be present 155 | */ 156 | lanes?: OSRMLane[] 157 | } 158 | 159 | /** A Lane represents a turn lane at the corresponding turn location. */ 160 | interface OSRMLane { 161 | /** a indication (e.g. marking on the road) specifying the turn lane. */ 162 | indications?: string[] 163 | /** a boolean flag indicating whether the lane is a valid choice in the current maneuver */ 164 | valid?: boolean 165 | } 166 | 167 | interface OSRMManeuver { 168 | /** The clockwise angle from true north to the direction of travel immediately before the maneuver. */ 169 | bearing_after?: number 170 | /** The clockwise angle from true north to the direction of travel immediately after the maneuver. */ 171 | bearing_before?: number 172 | /** A [longitude, latitude] pair describing the location of the turn. */ 173 | location?: [number, number] 174 | /** An optional string indicating the direction change of the maneuver. */ 175 | modifier?: string 176 | /** 177 | * A string indicating the type of maneuver. new identifiers might be introduced without API 178 | * change Types unknown to the client should be handled like the turn type, the existance of 179 | * correct modifier values is guranteed. 180 | */ 181 | type?: string 182 | /** exit An optional integer indicating number of the exit to take. */ 183 | exit?: number 184 | } 185 | interface OSRMWayPoint { 186 | /** 187 | * Unique internal identifier of the segment (ephemeral, not constant over data updates) 188 | * This can be used on subsequent request to significantly speed up the query and to connect 189 | * multiple services. E.g. you can use the hint value obtained by the nearest query as hint 190 | * values for route inputs. 191 | */ 192 | hint?: string 193 | /** The distance of the snapped point from the original */ 194 | distance?: number 195 | /** Name of the street the coordinate snapped to */ 196 | name: string 197 | /** location Array that contains the [longitude, latitude] pair of the snapped coordinate */ 198 | location: number[] 199 | } 200 | 201 | /** 202 | * Annotation of the whole route leg with fine-grained information about each segment or node id. 203 | */ 204 | interface OSRMAnnotation { 205 | /** The distance, in metres, between each pair of coordinates */ 206 | distance?: number 207 | /** The duration between each pair of coordinates, in seconds */ 208 | duration?: number 209 | /** 210 | * The index of the datasource for the speed between each pair of coordinates. 0 is the default 211 | * profile, other values are supplied via `--segment-speed-file` to `osrm-contract` 212 | */ 213 | datasources?: number[] 214 | /** 215 | * The OSM node ID for each coordinate along the route, excluding the first/last user-supplied 216 | * coordinates 217 | */ 218 | nodes?: number[] 219 | } 220 | 221 | export interface OSRMTableParams extends OSRMBaseParams { 222 | /** Use location with given index as source. */ 223 | sources: string 224 | /** Use location with given index as destination. */ 225 | destinations: string 226 | } 227 | 228 | export interface OSRMTableResponse extends OSRMBaseResponse { 229 | /** 230 | * array of arrays that stores the matrix in row-major order. durations[i][j] gives the travel 231 | * time from the i-th waypoint to the j-th waypoint. Values are given in seconds. 232 | */ 233 | durations: number[][] 234 | distances: number[][] 235 | /** array of Waypoint objects describing all sources in order */ 236 | sources: OSRMWayPoint[] 237 | /** array of Waypoint objects describing all destinations in order */ 238 | destinations: OSRMWayPoint[] 239 | } 240 | 241 | type OSRMResponseCode = 242 | /** Request could be processed as expected. */ 243 | | "Ok" 244 | /** URL string is invalid. */ 245 | | "InvalidUrl" 246 | /** Service name is invalid. */ 247 | | "InvalidService" 248 | /** Version is not found. */ 249 | | "InvalidVersion" 250 | /** Options are invalid. */ 251 | | "InvalidOptions" 252 | /** The query string is synctactically malformed. */ 253 | | "InvalidQuery" 254 | /** The successfully parsed query parameters are invalid. */ 255 | | "InvalidValue" 256 | /** One of the supplied input coordinates could not snap to street segment. */ 257 | | "NoSegment" 258 | /** The request size violates one of the service specific request size restrictions. */ 259 | | "TooBig" 260 | -------------------------------------------------------------------------------- /packages/osrm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/es" 5 | }, 6 | "files": ["index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/valhalla/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GIS • OPS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/valhalla/README.md: -------------------------------------------------------------------------------- 1 | # RoutingJS: Valhalla 2 | 3 | Promise-based web client for the [Valhalla Open Source Routing Engine](https://github.com/valhalla/valhalla). 4 | 5 | ## Example 6 | 7 | ```js 8 | import { Valhalla } from "@routingjs/valhalla" 9 | 10 | const v = new Valhalla() // URL defaults to http://valhalla1.openstreetmap.de 11 | v.directions( 12 | [ 13 | [8.512516, 47.380742], 14 | [8.557835, 47.359467], 15 | ], 16 | "auto" 17 | ).then((d) => { 18 | // do stuff with the directions response 19 | d.directions.forEach((direction) => { 20 | console.log(direction.feature) 21 | }) 22 | }) 23 | ``` 24 | 25 | ## Installation 26 | 27 | ``` 28 | npm install @routingjs/valhalla 29 | ``` 30 | 31 | --- 32 | 33 | This package is part of the [RoutingJS project](https://github.com/gis-ops/routingjs), which aims to provide consistent access to various vehicle routing APIs. 34 | -------------------------------------------------------------------------------- /packages/valhalla/index.ts: -------------------------------------------------------------------------------- 1 | import { Feature, FeatureCollection, LineString, Point, Polygon } from "geojson" 2 | import { 3 | Client, 4 | Direction, 5 | DirectionFeat, 6 | Directions, 7 | Isochrone, 8 | RoutingJSAPIError, 9 | Isochrones, 10 | Matrix, 11 | BaseRouter, 12 | ClientConstructorArgs, 13 | ErrorProps, 14 | } from "@routingjs/core" 15 | import { 16 | MapboxAuthParams, 17 | ValhallaContours, 18 | ValhallaCostingOptsAuto, 19 | ValhallaCostingOptsBicycle, 20 | ValhallaCostingOptsMotorcycle, 21 | ValhallaCostingOptsPedestrian, 22 | ValhallaCostingOptsTruck, 23 | ValhallaCostingType, 24 | ValhallaDateTime, 25 | ValhallaDirectionsType, 26 | ValhallaIsochroneParams, 27 | ValhallaIsochroneResponse, 28 | ValhallaLocation, 29 | ValhallaMatrixParams, 30 | ValhallaMatrixResponse, 31 | ValhallaRequestUnit, 32 | ValhallaRouteParams, 33 | ValhallaRouteResponse, 34 | ValhallaShapeMatch, 35 | ValhallaTraceRouteParams, 36 | } from "./parameters" 37 | import { decode } from "@googlemaps/polyline-codec" 38 | import { AxiosError } from "axios" 39 | 40 | type ValhallaErrorResponseProps = { 41 | status_code: number 42 | status: string 43 | error_code: number 44 | error: string 45 | } 46 | 47 | /** 48 | * `ValhallaErrorProps` returns additional information about the error thrown by the 49 | * Valhalla routing engine. It sends back the status_code, status, error_code and error 50 | * message where the error_code is specific to Valhalla. 51 | */ 52 | export interface ValhallaErrorProps extends ErrorProps { 53 | errorCode?: number 54 | } 55 | 56 | export type ValhallaAPIError = RoutingJSAPIError 57 | 58 | const handleValhallaError = (error: AxiosError) => { 59 | const props: ValhallaErrorProps = { 60 | statusCode: error.response?.status, 61 | status: error.response?.statusText, 62 | errorCode: error.response?.data.error_code, 63 | errorMessage: error.response?.data.error, 64 | } 65 | throw new RoutingJSAPIError(error.message, props) 66 | } 67 | 68 | interface ValhallaBaseOpts { 69 | /** A request ID that will be returned in the response */ 70 | id?: string 71 | /** 72 | * Convenience argument to set the cost metric, one of ['shortest', 'fastest']. Note, 73 | that shortest is not guaranteed to be absolute shortest for motor vehicle profiles. It's called ``preference`` 74 | to be in line with the already existing parameter in the ORS adapter. 75 | */ 76 | preference?: "shortest" | "fastest" 77 | /** 78 | * Profiles can have several options that can be adjusted to develop the route path, 79 | as well as for estimating time along the path. Only specify the actual options dict, the profile 80 | will be filled automatically. For more information, visit: 81 | https://github.com/valhalla/valhalla/blob/master/docs/api/turn-by-turn/api-reference.md#costing-options 82 | */ 83 | costingOpts?: 84 | | ValhallaCostingOptsAuto 85 | | ValhallaCostingOptsTruck 86 | | ValhallaCostingOptsBicycle 87 | | ValhallaCostingOptsMotorcycle 88 | | ValhallaCostingOptsPedestrian 89 | /** 90 | * A set of locations to exclude or avoid within a route. 91 | */ 92 | avoidLocations?: ([number, number] | Point | Feature)[] 93 | /** 94 | * Roads intersecting these polygons 95 | will be avoided during path finding. If you only need to avoid a few specific roads, it's much more 96 | efficient to use avoid_locations. Valhalla will close open rings (i.e. copy the first coordingate to the 97 | last position). 98 | */ 99 | avoidPolygons?: ([number, number][][] | Polygon | Feature)[] 100 | /** 101 | * This is the local date and time at the location. Field `type`: 0: Current departure time, 102 | 1: Specified departure time. Field `value`: the date and time is specified 103 | in ISO 8601 format (YYYY-MM-DDThh:mm), local time. 104 | 105 | @example 106 | ```js 107 | date_time = {type: 0, value: 2021-03-03T08:06:23} 108 | ``` 109 | */ 110 | dateTime?: ValhallaDateTime 111 | } 112 | 113 | interface ValhallaAdditionalTraceOpts { 114 | /** Search radius in meters associated with supplied trace points. */ 115 | searchRadius?: number 116 | /** GPS accuracy in meters associated with supplied trace points. */ 117 | gpsAccuracy?: number 118 | /** Breaking distance in meters between trace points. */ 119 | breakageDistance?: number 120 | /** Interpolation distance in meters beyond which trace points are merged together. */ 121 | interpolationDistance?: number 122 | /** 123 | * When present and true, the successful trace_route response will include a key 124 | * linear_references. Its value is an array of base64-encoded [OpenLR](https://www.openlr-association.com/fileadmin/user_upload/openlr-whitepaper_v1.5.pdf) location 125 | * references, one for each graph edge of the road network matched by the input trace. 126 | * 127 | */ 128 | linearReferences?: boolean 129 | } 130 | 131 | export interface ValhallaDirectionOpts extends ValhallaBaseOpts { 132 | /** 133 | * Whether to return turn-by-turn instructions. Named for compatibility with other 134 | * providers. Valhalla's parameter here is 'narrative'. 135 | */ 136 | instructions?: boolean 137 | /** A number denoting how many alternate routes should be provided. 138 | * There may be no alternates or less alternates than the user specifies. 139 | * Alternates are not yet supported on multipoint routes (that is, routes with 140 | * more than 2 locations). They are also not supported on time dependent routes. 141 | */ 142 | alternatives?: number 143 | /** 144 | * Distance units for output. Allowable unit types are miles (or mi) and kilometers (or km). 145 | * If no unit type is specified, the units default to kilometers. 146 | */ 147 | units?: ValhallaRequestUnit 148 | /** 149 | * The language of the narration instructions based on the IETF BCP 47 language tag string. 150 | * If no language is specified or the specified language is unsupported, United States-based 151 | * English (en-US) is used. 152 | * 153 | * See here for a list of supported languages: {@link https://valhalla.readthedocs.io/en/latest/api/turn-by-turn/api-reference/#supported-language-tags} 154 | */ 155 | language?: string 156 | /** 157 | * 'none': no instructions are returned. 'maneuvers': only maneuvers are returned. 158 | * 'instructions': maneuvers with instructions are returned. Default 'instructions' 159 | */ 160 | directionsType?: ValhallaDirectionsType 161 | } 162 | 163 | export interface ValhallaIsochroneOpts extends ValhallaBaseOpts { 164 | /** 165 | * Set 'time' for isochrones or 'distance' for equidistants. 166 | * Default 'time'. 167 | */ 168 | intervalType?: "time" | "distance" 169 | /** 170 | * The color for the output of the contour. Specify it as a Hex value, but without the #, such as 171 | * "color":"ff0000" for red. If no color is specified, the isochrone service will assign a default color to the output. 172 | */ 173 | colors?: string[] 174 | /** 175 | * Controls whether polygons or linestrings are returned in GeoJSON geometry. Default False. 176 | */ 177 | polygons?: boolean 178 | /** 179 | * Can be used to remove smaller contours. In range [0, 1]. A value of 1 will only return the largest contour 180 | * for a given time value. A value of 0.5 drops any contours that are less than half the area of the largest 181 | * contour in the set of contours for that same time value. Default 1. 182 | */ 183 | denoise?: number 184 | /** 185 | * A floating point value in meters used as the tolerance for Douglas-Peucker generalization. 186 | * Note: Generalization of contours can lead to self-intersections, as well as intersections of adjacent contours. 187 | */ 188 | generalize?: number 189 | /** 190 | * A boolean indicating whether the input locations should be returned as MultiPoint features: one feature for the exact input coordinates and one feature 191 | * for the coordinates of the network node it snapped to. Default false. 192 | */ 193 | showLocations?: boolean 194 | } 195 | 196 | export interface ValhallaMatrixOpts extends ValhallaBaseOpts { 197 | sources?: number[] 198 | destinations?: number[] 199 | units?: ValhallaRequestUnit 200 | } 201 | 202 | export interface ValhallaTraceRouteOpts 203 | extends Omit { 204 | /** 205 | * shape_match is an optional string input parameter. It allows some control 206 | * of the matching algorithm based on the type of input. 207 | * 208 | * @remarks 209 | * `edge_walk`: Indicates an edge walking algorithm can be used. This algorithm 210 | * requires nearly exact shape matching, so it should only be used 211 | * when the shape is from a prior Valhalla route. 212 | * 213 | * `map_snap`: Indicates that a map-matching algorithm should be used because 214 | * the input shape might not closely match Valhalla edges. This 215 | * algorithm is more expensive. 216 | * 217 | * `walk_or_snap`: Also the default option. This will try edge walking and if this 218 | * does not succeed, it will fall back and use map matching. 219 | * 220 | * @defaultValue 221 | * `walk_or_snap` 222 | */ 223 | shapeMatch?: ValhallaShapeMatch 224 | /** 225 | * Begin timestamp for the trace. 226 | * 227 | * @remarks 228 | * This is used along with the durations so that timestamps can be specified for a 229 | * trace that is specified using an encoded polyline. 230 | */ 231 | beginTime?: string 232 | /** 233 | * List of durations (seconds) between each successive pair of input trace points. 234 | * 235 | * @remarks 236 | * This allows trace points to be supplied as an encoded polyline and timestamps to be 237 | * created by using this list of "delta" times along with the begin_time of the trace. 238 | */ 239 | durations?: number[] 240 | /** 241 | * A boolean value indicating whether the input timestamps or durations should be used 242 | * when computing elapsed time at each edge along the matched path. 243 | * 244 | * @remarks 245 | * If true, timestamps are used. If false (default), internal costing is applied to 246 | * compute elapsed times. 247 | * 248 | * @defaultValue 249 | * `false` 250 | */ 251 | useTimestamps?: boolean 252 | /** Additional Options */ 253 | traceOptions?: ValhallaAdditionalTraceOpts 254 | directionsOptions?: ValhallaDirectionOpts 255 | } 256 | 257 | export type ValhallaDirections = Directions< 258 | ValhallaRouteResponse, 259 | ValhallaRouteResponse 260 | > 261 | 262 | export type ValhallaIsochrones = Isochrones 263 | 264 | export type ValhallaMatrix = Matrix 265 | 266 | export type ValhallaTraceRoute = Directions< 267 | ValhallaRouteResponse, 268 | ValhallaRouteResponse 269 | > 270 | 271 | export type ValhallaClient = Client< 272 | ValhallaRouteResponse | ValhallaMatrixResponse | FeatureCollection, 273 | MapboxAuthParams, 274 | | ValhallaIsochroneParams 275 | | ValhallaRouteParams 276 | | ValhallaMatrixParams 277 | | ValhallaTraceRouteParams 278 | > 279 | 280 | export class Valhalla implements BaseRouter { 281 | client: ValhallaClient 282 | apiKey?: string 283 | constructor(clientArgs?: ClientConstructorArgs) { 284 | const { 285 | apiKey, 286 | baseUrl, 287 | userAgent, 288 | headers, 289 | timeout, 290 | retryOverQueryLimit, 291 | maxRetries, 292 | axiosOpts, 293 | } = clientArgs || {} 294 | 295 | this.apiKey = apiKey // TODO: add to requests 296 | 297 | const defaultURL = "https://valhalla1.openstreetmap.de" 298 | 299 | this.client = new Client( 300 | baseUrl || defaultURL, 301 | userAgent, 302 | timeout, 303 | retryOverQueryLimit, 304 | headers, 305 | maxRetries, 306 | axiosOpts 307 | ) 308 | } 309 | 310 | /** 311 | * Makes a request to Valhalla's `/route` endpoint. 312 | * 313 | * @param locations - The coordinates tuple the route should be calculated from in order of visit. Format: [lat, lon] 314 | * @param profile - Specifies the mode of transport 315 | * @param directionsOpts - Additional parameters, such as costing options. 316 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 317 | * @see {@link ValhallaCostingType} for available profiles 318 | */ 319 | public async directions( 320 | locations: ([number, number] | ValhallaLocation)[], 321 | profile: ValhallaCostingType, 322 | directionsOpts?: ValhallaDirectionOpts, 323 | dryRun?: false 324 | ): Promise 325 | public async directions( 326 | locations: ([number, number] | ValhallaLocation)[], 327 | profile: ValhallaCostingType, 328 | directionsOpts: ValhallaDirectionOpts, 329 | dryRun: true 330 | ): Promise 331 | public async directions( 332 | locations: ([number, number] | ValhallaLocation)[], 333 | profile: ValhallaCostingType, 334 | directionsOpts: ValhallaDirectionOpts = {}, 335 | dryRun = false 336 | ): Promise { 337 | dryRun = dryRun || false 338 | const getParams: MapboxAuthParams | undefined = this.apiKey 339 | ? { access_token: this.apiKey } 340 | : undefined 341 | const params = Valhalla.getDirectionParams( 342 | locations, 343 | profile, 344 | directionsOpts 345 | ) 346 | 347 | return this.client 348 | .request({ 349 | endpoint: "/route", 350 | postParams: params, 351 | getParams, 352 | dryRun, 353 | }) 354 | .then((res) => { 355 | if (typeof res === "object") { 356 | return this.parseDirectionsResponse( 357 | res as ValhallaRouteResponse, 358 | "main" 359 | ) 360 | } else { 361 | return res // return the request info string 362 | } 363 | }) 364 | .catch(handleValhallaError) 365 | } 366 | 367 | public static getDirectionParams( 368 | locations: ([number, number] | ValhallaLocation)[], 369 | profile: ValhallaCostingType, 370 | directionsOpts: ValhallaDirectionOpts = {} 371 | ): ValhallaRouteParams { 372 | const params: ValhallaRouteParams = { 373 | locations: Valhalla._buildLocations(locations), 374 | costing: profile, 375 | narrative: directionsOpts.instructions || false, 376 | } 377 | 378 | if ( 379 | (directionsOpts.costingOpts !== undefined && 380 | Object.keys(directionsOpts.costingOpts).length) || 381 | directionsOpts.preference !== undefined 382 | ) { 383 | params.costing_options = { 384 | [profile]: { 385 | ...directionsOpts.costingOpts, 386 | shortest: directionsOpts.preference ? true : undefined, 387 | }, 388 | } 389 | } 390 | 391 | if ( 392 | directionsOpts.language || 393 | directionsOpts.units || 394 | directionsOpts.directionsType 395 | ) { 396 | params.directions_options = { 397 | language: directionsOpts.language, 398 | units: directionsOpts.units, 399 | directions_type: directionsOpts.directionsType, 400 | } 401 | } 402 | 403 | if (directionsOpts.avoidLocations) { 404 | const avoidLocations: [number, number][] = [] 405 | directionsOpts.avoidLocations.forEach((avoid_location) => { 406 | // TODO: convert to loop 407 | if (Array.isArray(avoid_location)) { 408 | avoidLocations.push(avoid_location) 409 | } else if (avoid_location.type === "Feature") { 410 | // GeoJSON Position object can have elevation coordinate 411 | avoidLocations.push([ 412 | avoid_location.geometry.coordinates[0], 413 | avoid_location.geometry.coordinates[1], 414 | ]) 415 | } else { 416 | // geometry obj only 417 | avoidLocations.push([ 418 | avoid_location.coordinates[0], 419 | avoid_location.coordinates[1], 420 | ]) 421 | } 422 | }) 423 | params.exclude_locations = avoidLocations 424 | } 425 | 426 | if (directionsOpts.avoidPolygons) { 427 | const avoidPolygons: [number, number][][][] = [] 428 | 429 | directionsOpts.avoidPolygons.forEach((avoid_polygon) => { 430 | // TODO: convert to loop 431 | if (Array.isArray(avoid_polygon)) { 432 | avoidPolygons.push(avoid_polygon) 433 | } else if (avoid_polygon.type === "Feature") { 434 | const outerRing: [number, number][][] = 435 | avoid_polygon.geometry.coordinates.map((ring) => { 436 | return ring.map((pos) => { 437 | return [pos[0], pos[1]] // strip possible elevation 438 | }) 439 | }) 440 | avoidPolygons.push(outerRing) 441 | } 442 | params.exclude_polygons = avoidPolygons 443 | }) 444 | } 445 | 446 | if (directionsOpts.dateTime) { 447 | params.date_time = directionsOpts.dateTime 448 | } 449 | 450 | if (directionsOpts.id) { 451 | params.id = directionsOpts.id 452 | } 453 | 454 | if (directionsOpts.alternatives) { 455 | params.alternates = directionsOpts.alternatives 456 | } 457 | 458 | return params 459 | } 460 | 461 | parseDirectionsResponse( 462 | response: ValhallaRouteResponse, 463 | type: "main" 464 | ): ValhallaDirections 465 | parseDirectionsResponse( 466 | response: ValhallaRouteResponse, 467 | type: "alternative" 468 | ): Direction 469 | public parseDirectionsResponse( 470 | response: ValhallaRouteResponse, 471 | type: "main" | "alternative" = "main" 472 | ): ValhallaDirections | Direction { 473 | const geometry: [number, number][] = [] 474 | let [duration, distance] = [0, 0] 475 | let factor = 1 476 | if (response.trip?.units) { 477 | factor = ["mi", "miles"].includes(response.trip?.units) 478 | ? 0.621371 479 | : 1 480 | } 481 | 482 | response.trip?.legs?.forEach((leg) => { 483 | // TODO: convert to loop 484 | if (leg.shape) { 485 | geometry.push( 486 | ...(decode(leg.shape, 6).map(([lat, lon]) => [ 487 | lon, 488 | lat, 489 | ]) as [number, number][]) 490 | ) 491 | } 492 | 493 | if (leg.summary.length) { 494 | distance += leg.summary.length * factor 495 | } 496 | 497 | if (leg.summary.time) { 498 | duration += leg.summary.time 499 | } 500 | }) 501 | 502 | const feat: DirectionFeat = { 503 | type: "Feature", 504 | geometry: { 505 | type: "LineString", 506 | coordinates: geometry, 507 | }, 508 | properties: { distance, duration }, 509 | } 510 | 511 | if (type === "main") { 512 | const directions: Direction[] = 513 | response.alternates 514 | ? response.alternates.map((res) => { 515 | return this.parseDirectionsResponse( 516 | res, 517 | "alternative" 518 | ) 519 | }) 520 | : [] 521 | 522 | return new Directions( 523 | [new Direction(feat, response), ...directions], 524 | response 525 | ) 526 | } else { 527 | return new Direction(feat, response) 528 | } 529 | } 530 | /** 531 | * Makes a request to Valhalla's `/isochrone` endpoint. 532 | * 533 | * @param location - The coordinates tuple that represents the starting location. Format: [lat, lon] 534 | * @param profile - Specifies the mode of transport 535 | * @param isochronesOpts - Additional parameters, such as costing options. 536 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 537 | * @see {@link ValhallaCostingType} for available profiles 538 | */ 539 | public async reachability( 540 | location: [number, number] | ValhallaLocation, 541 | profile: ValhallaCostingType, 542 | intervals: number[], 543 | isochronesOpts?: ValhallaIsochroneOpts, 544 | dryRun?: false 545 | ): Promise 546 | public async reachability( 547 | location: [number, number] | ValhallaLocation, 548 | profile: ValhallaCostingType, 549 | intervals: number[], 550 | isochronesOpts: ValhallaIsochroneOpts, 551 | dryRun: true 552 | ): Promise 553 | public async reachability( 554 | location: [number, number] | ValhallaLocation, 555 | profile: ValhallaCostingType, 556 | intervals: number[], 557 | isochronesOpts: ValhallaIsochroneOpts = {}, 558 | dryRun?: boolean 559 | ): Promise { 560 | const getParams: MapboxAuthParams | undefined = this.apiKey 561 | ? { access_token: this.apiKey } 562 | : undefined 563 | const params = Valhalla.getIsochroneParams( 564 | location, 565 | profile, 566 | intervals, 567 | isochronesOpts 568 | ) 569 | 570 | return this.client 571 | .request({ 572 | endpoint: "/isochrone", 573 | postParams: params, 574 | getParams, 575 | dryRun, 576 | }) 577 | .then((res) => { 578 | if (typeof res === "object") { 579 | return Valhalla.parseIsochroneResponse( 580 | res as ValhallaIsochroneResponse, 581 | location, 582 | intervals, 583 | isochronesOpts?.intervalType 584 | ? isochronesOpts.intervalType 585 | : "time" 586 | ) as ValhallaIsochrones 587 | } else { 588 | return res // return the request info string 589 | } 590 | }) 591 | .catch(handleValhallaError) 592 | } 593 | 594 | public static getIsochroneParams( 595 | location: [number, number] | ValhallaLocation, 596 | profile: ValhallaCostingType, 597 | intervals: number[], 598 | isochroneOpts: ValhallaIsochroneOpts = {} 599 | ): ValhallaIsochroneParams { 600 | const contours: ValhallaContours[] = [] 601 | const [key, divisor]: ["time" | "distance", 60 | 1000] = 602 | isochroneOpts.intervalType !== undefined && 603 | isochroneOpts.intervalType === "distance" 604 | ? ["distance", 1000] 605 | : ["time", 60] 606 | 607 | intervals.forEach((interval, index) => { 608 | // TODO: convert to loop 609 | const contourObj: ValhallaContours = { 610 | [key]: interval / divisor, 611 | } 612 | 613 | if (isochroneOpts.colors !== undefined) { 614 | if (isochroneOpts.colors.length !== intervals.length) { 615 | throw new Error( 616 | "Colors array must be of same length as intervals array" 617 | ) 618 | } 619 | contourObj.color = isochroneOpts.colors[index] 620 | } 621 | contours.push(contourObj) 622 | }) 623 | 624 | const params: ValhallaIsochroneParams = { 625 | locations: Valhalla._buildLocations(location), 626 | costing: profile, 627 | contours, 628 | polygons: isochroneOpts.polygons, 629 | } 630 | 631 | if ( 632 | (isochroneOpts.costingOpts !== undefined && 633 | Object.keys(isochroneOpts.costingOpts).length) || 634 | isochroneOpts.preference !== undefined 635 | ) 636 | params.costing_options = { 637 | [profile]: { 638 | ...isochroneOpts.costingOpts, 639 | shortest: isochroneOpts.preference ? true : undefined, 640 | }, 641 | } 642 | 643 | if (isochroneOpts.avoidLocations) { 644 | const avoidLocations: [number, number][] = [] 645 | isochroneOpts.avoidLocations.forEach((avoid_location) => { 646 | // TODO: convert to loop 647 | if (Array.isArray(avoid_location)) { 648 | avoidLocations.push(avoid_location) 649 | } else if (avoid_location.type === "Feature") { 650 | // GeoJSON Position object can have elevation coordinate 651 | avoidLocations.push([ 652 | avoid_location.geometry.coordinates[0], 653 | avoid_location.geometry.coordinates[1], 654 | ]) 655 | } else { 656 | // geometry obj only 657 | avoidLocations.push([ 658 | avoid_location.coordinates[0], 659 | avoid_location.coordinates[1], 660 | ]) 661 | } 662 | }) 663 | params.exclude_locations = avoidLocations 664 | } 665 | 666 | if (isochroneOpts.avoidPolygons) { 667 | const avoidPolygons: [number, number][][][] = [] 668 | 669 | isochroneOpts.avoidPolygons.forEach((avoid_polygon) => { 670 | // TODO: convert to loop 671 | if (Array.isArray(avoid_polygon)) { 672 | avoidPolygons.push(avoid_polygon) 673 | } else if (avoid_polygon.type === "Feature") { 674 | const outerRing: [number, number][][] = 675 | avoid_polygon.geometry.coordinates.map((ring) => { 676 | return ring.map((pos) => { 677 | return [pos[0], pos[1]] // strip possible elevation 678 | }) 679 | }) 680 | avoidPolygons.push(outerRing) 681 | } 682 | params.exclude_polygons = avoidPolygons 683 | }) 684 | } 685 | 686 | if (isochroneOpts.dateTime) { 687 | params.date_time = isochroneOpts.dateTime 688 | } 689 | 690 | if (isochroneOpts.id) { 691 | params.id = isochroneOpts.id 692 | } 693 | 694 | if (isochroneOpts.showLocations) { 695 | params.show_locations = isochroneOpts.showLocations 696 | } 697 | 698 | return params 699 | } 700 | 701 | public static parseIsochroneResponse( 702 | response: ValhallaIsochroneResponse, 703 | location: [number, number] | ValhallaLocation, 704 | intervals: number[], 705 | intervalType: "time" | "distance" 706 | ): ValhallaIsochrones { 707 | const isochrones: Isochrone[] = [] 708 | response.features.forEach((feature, index) => { 709 | if (feature.geometry.type !== "Point") { 710 | isochrones.push( 711 | new Isochrone( 712 | Array.isArray(location) 713 | ? location 714 | : [location.lat, location.lon], 715 | intervals[index], 716 | intervalType, 717 | feature as Feature 718 | ) 719 | ) 720 | } 721 | }) 722 | 723 | return new Isochrones(isochrones, response) 724 | } 725 | 726 | /** 727 | * Makes a request to Valhalla's `/matrix` endpoint. 728 | * 729 | * @param locations - Format: [lat, lon] 730 | * @param profile - Specifies the mode of transport 731 | * @param matrixOpts - Additional parameters, such as costing options. 732 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 733 | * @see {@link ValhallaCostingType} for available profiles 734 | */ 735 | public async matrix( 736 | locations: ([number, number] | ValhallaLocation)[], 737 | profile: ValhallaCostingType, 738 | matrixOpts?: ValhallaMatrixOpts, 739 | dryRun?: false 740 | ): Promise 741 | public async matrix( 742 | locations: ([number, number] | ValhallaLocation)[], 743 | profile: ValhallaCostingType, 744 | matrixOpts: ValhallaMatrixOpts, 745 | dryRun: true 746 | ): Promise 747 | public async matrix( 748 | locations: ([number, number] | ValhallaLocation)[], 749 | profile: ValhallaCostingType, 750 | matrixOpts: ValhallaMatrixOpts = {}, 751 | dryRun?: boolean 752 | ): Promise { 753 | const getParams: MapboxAuthParams | undefined = this.apiKey 754 | ? { access_token: this.apiKey } 755 | : undefined 756 | const params = Valhalla.getMatrixParams(locations, profile, matrixOpts) 757 | 758 | return this.client 759 | .request({ 760 | endpoint: "/sources_to_targets", 761 | postParams: params, 762 | getParams, 763 | dryRun, 764 | }) 765 | .then((res) => { 766 | if (typeof res === "object") { 767 | return Valhalla.parseMatrixResponse( 768 | res as ValhallaMatrixResponse, 769 | matrixOpts?.units ? matrixOpts.units : "km" 770 | ) as ValhallaMatrix 771 | } else { 772 | return res // return the request info string 773 | } 774 | }) 775 | .catch(handleValhallaError) 776 | } 777 | 778 | public static getMatrixParams( 779 | locations: ([number, number] | ValhallaLocation)[], 780 | profile: ValhallaCostingType, 781 | matrixOpts: ValhallaMatrixOpts = {} 782 | ): ValhallaMatrixParams { 783 | const matrixLocations = Valhalla._buildLocations(locations) 784 | 785 | let sourceCoords = matrixLocations 786 | 787 | if (matrixOpts.sources) { 788 | sourceCoords = sourceCoords.filter((source, index) => { 789 | matrixOpts.sources?.includes(index) 790 | }) 791 | } 792 | 793 | let destCoords = matrixLocations 794 | 795 | if (matrixOpts.destinations) { 796 | destCoords = destCoords.filter((source, index) => { 797 | matrixOpts.destinations?.includes(index) 798 | }) 799 | } 800 | 801 | const params: ValhallaMatrixParams = { 802 | costing: profile, 803 | sources: sourceCoords, 804 | targets: destCoords, 805 | } 806 | 807 | if ( 808 | (matrixOpts.costingOpts !== undefined && 809 | Object.keys(matrixOpts.costingOpts).length) || 810 | matrixOpts.preference !== undefined 811 | ) { 812 | params.costing_options = { 813 | [profile]: { 814 | ...matrixOpts.costingOpts, 815 | shortest: matrixOpts.preference ? true : undefined, 816 | }, 817 | } 818 | } 819 | 820 | if (matrixOpts.avoidLocations) { 821 | const avoidLocations: [number, number][] = [] 822 | matrixOpts.avoidLocations.forEach((avoid_location) => { 823 | // TODO: convert to loop 824 | if (Array.isArray(avoid_location)) { 825 | avoidLocations.push(avoid_location) 826 | } else if (avoid_location.type === "Feature") { 827 | // GeoJSON Position object can have elevation coordinate 828 | avoidLocations.push([ 829 | avoid_location.geometry.coordinates[0], 830 | avoid_location.geometry.coordinates[1], 831 | ]) 832 | } else { 833 | // geometry obj only 834 | avoidLocations.push([ 835 | avoid_location.coordinates[0], 836 | avoid_location.coordinates[1], 837 | ]) 838 | } 839 | }) 840 | params.exclude_locations = avoidLocations 841 | } 842 | 843 | if (matrixOpts.avoidPolygons) { 844 | const avoidPolygons: [number, number][][][] = [] 845 | 846 | matrixOpts.avoidPolygons.forEach((avoid_polygon) => { 847 | // TODO: convert to loop 848 | if (Array.isArray(avoid_polygon)) { 849 | avoidPolygons.push(avoid_polygon) 850 | } else if (avoid_polygon.type === "Feature") { 851 | const outerRing: [number, number][][] = 852 | avoid_polygon.geometry.coordinates.map((ring) => { 853 | return ring.map((pos) => { 854 | return [pos[0], pos[1]] // strip possible elevation 855 | }) 856 | }) 857 | avoidPolygons.push(outerRing) 858 | } 859 | params.exclude_polygons = avoidPolygons 860 | }) 861 | } 862 | 863 | if (matrixOpts.id) { 864 | params.id = matrixOpts.id 865 | } 866 | 867 | return params 868 | } 869 | 870 | public static parseMatrixResponse( 871 | response: ValhallaMatrixResponse, 872 | units: ValhallaRequestUnit 873 | ): ValhallaMatrix { 874 | const factor = units === "miles" || units === "mi" ? 0.621371 : 1 875 | const durations = response.sources_to_targets.map((origin) => 876 | origin.map((dest) => dest.time) 877 | ) 878 | const distances = response.sources_to_targets.map((origin) => 879 | origin.map((dest) => { 880 | if (dest.distance !== undefined && dest.distance !== null) { 881 | return Math.round(dest.distance * 1000 * factor) 882 | } else { 883 | return null 884 | } 885 | }) 886 | ) 887 | return new Matrix(durations, distances, response) 888 | } 889 | 890 | /** 891 | * Makes a request to Valhalla's `/trace_route` endpoint. 892 | * 893 | * @param locations - Format: [lat, lon] 894 | * @param profile - Specifies the mode of transport 895 | * @param mapMatchOpts - Additional parameters, such as costing options. 896 | * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging 897 | * @see {@link ValhallaCostingType} for available profiles 898 | */ 899 | public async mapMatch( 900 | locations: ([number, number] | ValhallaLocation)[], 901 | profile: ValhallaCostingType, 902 | mapMatchOpts?: ValhallaTraceRouteOpts, 903 | dryRun?: false 904 | ): Promise 905 | public async mapMatch( 906 | locations: ([number, number] | ValhallaLocation)[], 907 | profile: ValhallaCostingType, 908 | mapMatchOpts: ValhallaTraceRouteOpts, 909 | dryRun: true 910 | ): Promise 911 | public async mapMatch( 912 | locations: ([number, number] | ValhallaLocation)[], 913 | profile: ValhallaCostingType, 914 | traceRouteOpts: ValhallaTraceRouteOpts = {}, 915 | dryRun?: boolean 916 | ): Promise { 917 | const getParams: MapboxAuthParams | undefined = this.apiKey 918 | ? { access_token: this.apiKey } 919 | : undefined 920 | const params = Valhalla.getTraceRouteParams( 921 | locations, 922 | profile, 923 | traceRouteOpts 924 | ) 925 | 926 | return this.client 927 | .request({ 928 | endpoint: "/trace_route", 929 | postParams: params, 930 | getParams, 931 | dryRun, 932 | }) 933 | .then((res) => { 934 | if (typeof res === "object") { 935 | return this.parseDirectionsResponse( 936 | res as ValhallaRouteResponse, 937 | "main" 938 | ) as ValhallaDirections 939 | } else { 940 | return res // return the request info string 941 | } 942 | }) 943 | .catch(handleValhallaError) 944 | } 945 | 946 | public static getTraceRouteParams( 947 | locations: ([number, number] | ValhallaLocation)[], 948 | profile: ValhallaCostingType, 949 | traceRouteOpts: ValhallaTraceRouteOpts = {} 950 | ): ValhallaTraceRouteParams { 951 | const params: ValhallaTraceRouteParams = { 952 | shape: this._buildLocations(locations), 953 | costing: profile, 954 | } 955 | 956 | if ( 957 | traceRouteOpts.costingOpts !== undefined && 958 | Object.keys(traceRouteOpts.costingOpts).length 959 | ) { 960 | params.costing_options = { 961 | [profile]: { 962 | ...traceRouteOpts.costingOpts, 963 | }, 964 | } 965 | } 966 | 967 | params.shape_match = traceRouteOpts.shapeMatch 968 | params.begin_time = traceRouteOpts.beginTime 969 | params.durations = traceRouteOpts.durations 970 | params.use_timestamps = traceRouteOpts.useTimestamps 971 | 972 | if (traceRouteOpts.traceOptions) { 973 | params.trace_options = { 974 | gps_accuracy: traceRouteOpts.traceOptions.gpsAccuracy, 975 | breakage_distance: traceRouteOpts.traceOptions.breakageDistance, 976 | search_radius: traceRouteOpts.traceOptions.searchRadius, 977 | interpolation_distance: 978 | traceRouteOpts.traceOptions.interpolationDistance, 979 | } 980 | } 981 | 982 | params.directions_options = { 983 | language: traceRouteOpts.directionsOptions?.language, 984 | units: traceRouteOpts.directionsOptions?.units, 985 | directions_type: traceRouteOpts.directionsOptions?.directionsType, 986 | } 987 | 988 | params.id = traceRouteOpts.id 989 | 990 | return params 991 | } 992 | 993 | public static _buildLocations( 994 | coordinates: 995 | | ([number, number] | ValhallaLocation)[] 996 | | [number, number] 997 | | ValhallaLocation 998 | ): ValhallaLocation[] 999 | public static _buildLocations( 1000 | coordinates: 1001 | | ([number, number] | ValhallaLocation)[] 1002 | | [number, number] 1003 | | ValhallaLocation 1004 | ): [ValhallaLocation] 1005 | public static _buildLocations( 1006 | coordinates: 1007 | | ([number, number] | ValhallaLocation)[] 1008 | | [number, number] 1009 | | ValhallaLocation 1010 | ): ValhallaLocation[] { 1011 | if (Array.isArray(coordinates)) { 1012 | if (Array.isArray(coordinates[0])) { 1013 | // [[lat, long], [lat, long], ...] 1014 | const locations: ValhallaLocation[] = [] 1015 | ;(coordinates as number[][]).forEach((coordPair) => { 1016 | const locObj = { lon: coordPair[1], lat: coordPair[0] } 1017 | locations.push(locObj) 1018 | }) 1019 | return locations 1020 | } else { 1021 | if (typeof coordinates[0] == "number") { 1022 | // [lat, lng] 1023 | const location: [ValhallaLocation] = [ 1024 | { 1025 | lat: (coordinates as [number, number])[0], 1026 | lon: (coordinates as [number, number])[1], 1027 | }, 1028 | ] 1029 | return location 1030 | } else { 1031 | // location objects 1032 | return coordinates as ValhallaLocation[] 1033 | } 1034 | } 1035 | } else { 1036 | // single location obj 1037 | return [coordinates] 1038 | } 1039 | } 1040 | } 1041 | 1042 | export * from "./parameters" 1043 | -------------------------------------------------------------------------------- /packages/valhalla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@routingjs/valhalla", 3 | "version": "0.3.0", 4 | "description": "RoutingJS Valhalla Module", 5 | "main": "dist/js/index.js", 6 | "module": "dist/es/index.js", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "typings": "dist/js/index.d.ts", 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "npm-run-all build:*", 17 | "build:js": "tsc --outDir dist/js --module commonjs", 18 | "build:es": "tsc" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/gis-ops/routingjs.git" 23 | }, 24 | "keywords": [ 25 | "routing", 26 | "vehicle routing", 27 | "navigation", 28 | "api", 29 | "geo", 30 | "geospatial" 31 | ], 32 | "author": "Christian Beiwinkel ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/gis-ops/routingjs/issues" 36 | }, 37 | "homepage": "https://github.com/gis-ops/routingjs#readme", 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.1.2", 40 | "@commitlint/config-conventional": "^17.1.0", 41 | "@rollup/plugin-typescript": "^9.0.2", 42 | "@types/geojson": "^7946.0.10", 43 | "@types/jest": "^29.1.2", 44 | "@types/node": "^18.11.0", 45 | "@typescript-eslint/eslint-plugin": "^5.40.0", 46 | "@typescript-eslint/parser": "^5.40.0", 47 | "eslint": "^8.25.0", 48 | "husky": "^8.0.1", 49 | "jest": "^29.2.0", 50 | "npm-run-all": "*", 51 | "prettier": "^2.7.1", 52 | "rollup": "^3.2.5", 53 | "ts-jest": "^29.0.3", 54 | "typescript": "^4.8.4" 55 | }, 56 | "dependencies": { 57 | "@googlemaps/polyline-codec": "^1.0.28", 58 | "@routingjs/core": "^0.3.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/valhalla/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/es" 5 | }, 6 | "files": ["index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/valhalla/valhalla.test.ts: -------------------------------------------------------------------------------- 1 | import { Valhalla } from "./index" 2 | import { assertError } from "../../util/error" 3 | 4 | const v = new Valhalla({ baseUrl: "http://localhost:8002" }) 5 | 6 | describe("Valhalla returns responses", () => { 7 | it("gets a directions response", async () => { 8 | await v 9 | .directions( 10 | [ 11 | [42.5063, 1.51886], 12 | [42.51007, 1.53789], 13 | ], 14 | "pedestrian" 15 | ) 16 | .then((d) => { 17 | expect(d.raw).toBeDefined() 18 | expect(d.directions).toHaveLength(1) 19 | expect(d.directions[0]).toHaveProperty("feature") 20 | expect(d.directions[0].feature).toHaveProperty("geometry") 21 | expect(d.directions[0].feature.geometry).toHaveProperty( 22 | "coordinates" 23 | ) 24 | 25 | expect( 26 | d.directions[0].feature.properties.duration 27 | ).not.toBeNull() 28 | 29 | expect( 30 | d.directions[0].feature.properties.distance 31 | ).not.toBeNull() 32 | }) 33 | }) 34 | 35 | it("gets an isochrone response", async () => { 36 | await v 37 | .reachability([42.50823, 1.52601], "pedestrian", [30, 90]) 38 | .then((i) => { 39 | expect(i).toHaveProperty("isochrones") 40 | expect(i.isochrones).toHaveLength(2) 41 | }) 42 | }) 43 | 44 | it("gets an isochrone response with polygons", async () => { 45 | await v 46 | .reachability([42.50823, 1.52601], "pedestrian", [30, 90], { 47 | polygons: true, 48 | id: "test-id", 49 | }) 50 | .then((i) => { 51 | expect(i).toHaveProperty("isochrones") 52 | expect(i.isochrones).toHaveLength(2) 53 | expect(["Polygon", "MultiPolygon"]).toContain( 54 | i.isochrones[0].feature.geometry.type 55 | ) 56 | expect(i.raw.id === "test-id") 57 | }) 58 | }) 59 | 60 | it("gets an matrix response", async () => { 61 | await v 62 | .matrix( 63 | [ 64 | [42.5063, 1.51886], 65 | [42.51007, 1.53789], 66 | ], 67 | "auto" 68 | ) 69 | .then((m) => { 70 | expect(m).toHaveProperty("durations") 71 | expect(m).toHaveProperty("distances") 72 | expect(m.durations).toHaveLength(2) 73 | }) 74 | }) 75 | 76 | it("gets a trace_route response", async () => { 77 | await v 78 | .mapMatch( 79 | [ 80 | [42.5063, 1.51886], 81 | [42.51007, 1.53789], 82 | ], 83 | "pedestrian" 84 | ) 85 | .then((d) => { 86 | expect(d.raw).toBeDefined() 87 | expect(d.directions).toHaveLength(1) 88 | expect(d.directions[0]).toHaveProperty("feature") 89 | expect(d.directions[0].feature).toHaveProperty("geometry") 90 | expect(d.directions[0].feature.geometry).toHaveProperty( 91 | "coordinates" 92 | ) 93 | expect( 94 | d.directions[0].feature.properties.duration 95 | ).not.toBeNull() 96 | expect( 97 | d.directions[0].feature.properties.distance 98 | ).not.toBeNull() 99 | }) 100 | }) 101 | }) 102 | 103 | describe("Throws RoutingJSAPIError", () => { 104 | it("fails to get a directions response", async () => { 105 | await v 106 | .directions( 107 | [ 108 | [0.00001, 1], 109 | [42.51007, 1.53789], 110 | ], 111 | "pedestrian" 112 | ) 113 | .catch(assertError) 114 | }) 115 | 116 | it("fails to get an isochrone response", async () => { 117 | await v 118 | .reachability([0.00001, 1], "pedestrian", [30, 90]) 119 | .catch(assertError) 120 | }) 121 | 122 | it("fails to get an isochrone response with polygons", async () => { 123 | await v 124 | .reachability([0.00001, 1], "pedestrian", [30, 90], { 125 | polygons: true, 126 | id: "test-id", 127 | }) 128 | .catch(assertError) 129 | }) 130 | 131 | it("fails to get a matrix response", async () => { 132 | await v 133 | .matrix( 134 | [ 135 | [0.00001, 1], 136 | [42.51007, 1.53789], 137 | ], 138 | "auto" 139 | ) 140 | .catch(assertError) 141 | }) 142 | }) 143 | -------------------------------------------------------------------------------- /test_data/andorra-latest.osm.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilsnolde/routingjs/b19e7dc655b2d540f661c1a935e74c45ab67be36/test_data/andorra-latest.osm.pbf -------------------------------------------------------------------------------- /tsconfig.doc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "packages/core", 5 | "packages/valhalla", 6 | "packages/ors", 7 | "packages/osrm", 8 | "packages/graphhopper" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "exclude": ["node_modules"], 7 | "include": ["packages", ".eslintrc.cjs"] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "allowSyntheticDefaultImports": true, 5 | "baseUrl": "./packages", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "composite": false, 9 | "esModuleInterop": true, 10 | "module": "ESNext", 11 | "moduleResolution": "node", 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "outDir": "dist", 15 | "sourceMap": true, 16 | "strictNullChecks": true, 17 | "target": "ES2017" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /util/error.ts: -------------------------------------------------------------------------------- 1 | import { ValhallaAPIError } from "valhalla" 2 | import { OSRMAPIError } from "osrm" 3 | import { ORSAPIError } from "ors" 4 | 5 | export const assertError = (e: ValhallaAPIError | OSRMAPIError | ORSAPIError) => { 6 | expect(e.properties).toBeDefined() 7 | expect(e.properties).toHaveProperty("statusCode") 8 | expect(e.properties).toHaveProperty("status") 9 | expect(e.properties).toHaveProperty("errorCode") 10 | expect(e.properties).toHaveProperty("errorMessage") 11 | } 12 | --------------------------------------------------------------------------------