├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .tx └── config ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── INSTALL.md ├── LICENSE ├── README.md ├── babel.config.js ├── config.template.js ├── css ├── leaflet-routing.css └── style.css ├── docker-compose.yml ├── gulpfile.js ├── index.html ├── js ├── Browser.js ├── LayersConfig.js ├── Map.js ├── Util.js ├── WhatsNew.js ├── control │ ├── Control.Layers.js │ ├── Export.js │ ├── Itinerary.js │ ├── Layers.js │ ├── LayersTab.js │ ├── Message.js │ ├── OpacitySlider.js │ ├── OpacitySliderControl.js │ ├── Profile.js │ ├── RoutingOptions.js │ ├── ShareRoute.js │ ├── TrackAnalysis.js │ ├── TrackMessages.js │ └── TrackStats.js ├── format │ ├── Csv.js │ ├── Fit.js │ ├── Gpx.js │ ├── Kml.js │ ├── VoiceHints.js │ └── Xml.js ├── index.js ├── plugin │ ├── Bing.js │ ├── CircleGoArea.js │ ├── Heightgraph.js │ ├── NogoAreas.js │ ├── POIMarkers.js │ ├── RouteLoaderConverter.js │ ├── Routing.js │ ├── RoutingPathQuality.js │ ├── Search.js │ ├── Sidebar.js │ ├── TracksLoader.js │ ├── leaflet-distance-marker.js │ ├── leaflet-fullHash.js │ └── stravaSegments.js ├── router │ ├── BRouter.js │ └── brouterCgi.js └── util │ ├── CheapRuler.js │ ├── ClickTolerantBoxZoom.js │ ├── LeafletPatches.js │ ├── MaplibreGlLazyLoader.js │ ├── StdPath.js │ ├── Track.js │ └── TrackEdges.js ├── keys.template.js ├── layers ├── collection │ ├── 1010.geojson │ ├── 1016.geojson │ ├── 1017.geojson │ ├── 1021.geojson │ ├── 1023.geojson │ ├── 1059.geojson │ ├── 1061.geojson │ ├── 1065.geojson │ ├── 1069.geojson │ └── extract.mjs ├── config │ ├── config.js │ ├── geometry.js │ ├── overrides.js │ └── tree.js ├── extra │ ├── historic-place-contours.geojson │ ├── ignf-aerial.geojson │ ├── ignf-map.geojson │ ├── ignf-scan25.geojson │ ├── mapaszlakow │ │ ├── mapaszlakow-cycle.geojson │ │ └── mapaszlakow-routes.geojson │ ├── openmapsurfer.geojson │ ├── osm-notes.geojson │ ├── swisstopo-aerial.geojson │ ├── swisstopo-landeskarte.geojson │ └── topplus-open.geojson ├── josm │ ├── Freemap.sk-Car.geojson │ ├── Freemap.sk-Cyclo.geojson │ ├── Freemap.sk-Hiking.geojson │ ├── Freemap.sk-Outdoor.geojson │ ├── HDM_HOT.geojson │ ├── Israel_Hiking.geojson │ ├── Israel_MTB.geojson │ ├── LICENSE │ ├── OpenStreetMap-turistautak.geojson │ ├── OpenTopoMap.geojson │ ├── Waymarked_Trails-Cycling.geojson │ ├── Waymarked_Trails-Hiking.geojson │ ├── Waymarked_Trails-MTB.geojson │ ├── cyclosm.geojson │ ├── extract.mjs │ ├── hu-hillshade.geojson │ ├── mtbmap-no.geojson │ ├── opencylemap.geojson │ ├── openpt_map.geojson │ ├── openrailwaymap.geojson │ ├── osm-cambodia_laos_thailand_vietnam-bilingual.geojson │ ├── osm-mapnik-german_style.geojson │ ├── osmbe-fr.geojson │ ├── osmbe-nl.geojson │ ├── osmbe.geojson │ ├── osmfr-basque.geojson │ ├── osmfr-breton.geojson │ ├── osmfr-occitan.geojson │ ├── osmfr.geojson │ ├── standard.geojson │ └── wikimedia-map.geojson ├── mvt │ ├── mapillary-coverage-style.json │ ├── mapillary-coverage.geojson │ ├── terrarium-hillshading-style.json │ └── terrarium-hillshading.geojson └── overpass │ ├── amenity │ ├── financial │ │ ├── atm.geojson │ │ └── bank.geojson │ ├── others │ │ ├── bench.geojson │ │ ├── kneipp_water_cure.geojson │ │ ├── public_bath.geojson │ │ ├── shelter.geojson │ │ ├── shower.geojson │ │ ├── telephone.geojson │ │ ├── toilets.geojson │ │ └── water_point.geojson │ ├── sustenance │ │ ├── bar.geojson │ │ ├── bbq.geojson │ │ ├── biergarten.geojson │ │ ├── cafe.geojson │ │ ├── drinking_water.geojson │ │ ├── fast_food.geojson │ │ ├── food_court.geojson │ │ ├── ice_cream.geojson │ │ ├── pub.geojson │ │ └── restaurant.geojson │ └── transportation │ │ ├── bicycle_charging_station.geojson │ │ ├── bicycle_parking.geojson │ │ ├── bicycle_rental.geojson │ │ ├── bicycle_repair_station.geojson │ │ ├── boat_rental.geojson │ │ ├── boat_sharing.geojson │ │ ├── bus_station.geojson │ │ ├── car_rental.geojson │ │ ├── car_sharing.geojson │ │ ├── car_wash.geojson │ │ ├── charging_station.geojson │ │ ├── ferry_terminal.geojson │ │ ├── fuel.geojson │ │ ├── grit_bin.geojson │ │ ├── motorcycle_parking.geojson │ │ ├── parking.geojson │ │ ├── parking_entrance.geojson │ │ ├── parking_space.geojson │ │ ├── railway_station.geojson │ │ ├── taxi.geojson │ │ └── vehicle_inspection.geojson │ ├── shop │ └── food │ │ ├── bakery.geojson │ │ ├── beverages.geojson │ │ ├── butcher.geojson │ │ ├── cheese.geojson │ │ ├── coffee.geojson │ │ ├── convenience.geojson │ │ ├── greengrocer.geojson │ │ ├── health_food.geojson │ │ ├── ice_cream_shop.geojson │ │ ├── organic.geojson │ │ └── supermarket.geojson │ └── tourism │ ├── apartment.geojson │ ├── artwork.geojson │ ├── attraction.geojson │ ├── camp_site.geojson │ ├── caravan_site.geojson │ ├── chalet.geojson │ ├── gallery.geojson │ ├── guest_house.geojson │ ├── hostel.geojson │ ├── hotel.geojson │ ├── information.geojson │ ├── motel.geojson │ ├── museum.geojson │ ├── picnic_site.geojson │ ├── shelter.geojson │ ├── viewpoint.geojson │ └── wilderness_hut.geojson ├── locales ├── ca.json ├── de.json ├── en.json ├── eo.json ├── es.json ├── es_ES.json ├── fa.json ├── fr.json ├── gl.json ├── hu.json ├── is.json ├── it.json ├── it_IT.json ├── keys.js ├── nb.json ├── nl.json ├── pl.json ├── pt-BR.json ├── pt.json ├── ru_RU.json ├── sv.json ├── tr.json └── zh-TW.json ├── package.json ├── renovate.json ├── resources ├── boundaries │ ├── README.md │ ├── countries.topo.json │ └── germany-states.topo.json └── standalone │ ├── run.sh │ └── segments4 │ └── .gitignore ├── tests ├── control │ ├── Export.test.js │ ├── Profile.test.js │ ├── RoutingOptions.test.js │ └── data │ │ ├── brouterTotal.json │ │ └── segments.json ├── format │ ├── Gpx.test.js │ └── data │ │ ├── 2-locus.gpx │ │ ├── 3-osmand.gpx │ │ ├── 4-comment.gpx │ │ ├── 5-gpsies.gpx │ │ ├── 6-orux.gpx │ │ ├── track.gpx │ │ ├── track.json │ │ ├── waypoints.gpx │ │ └── waypoints.json └── util │ ├── CheapRuler.test.js │ └── StdPath.test.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | layers/josm/extract.mjs 2 | layers/collection/extract.mjs 3 | gulpfile.js 4 | dist/brouter-web.js 5 | dist/turf.min.js 6 | dist/core-js-bundle.min.js 7 | dist/regenerator-runtime.js 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:compat/recommended"], 3 | "env": { 4 | "browser": true, 5 | "es2021": true 6 | }, 7 | "settings": { 8 | "polyfills": [ 9 | "URL", 10 | "URLSearchParams", 11 | "Promise", 12 | "navigator", 13 | "Uint8Array", 14 | "performance", 15 | "location.hostname", 16 | "document.body", 17 | "fetch" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'renovate/**' 7 | pull_request: 8 | 9 | concurrency: 10 | group: ${{ github.head_ref || github.ref_name }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: '16' 22 | - run: yarn --frozen-lockfile 23 | - run: yarn test 24 | - run: yarn lint 25 | - run: yarn pretty-quick --check 26 | - run: yarn build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | nbproject/ 3 | .idea/ 4 | /.project 5 | /config.js 6 | /keys.js 7 | /dist 8 | brouter-web*.zip 9 | yarn-error.log 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn pretty-quick --bail && yarn lint 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | LICENSE 2 | Dockerfile 3 | dist/ 4 | *.zip 5 | yarn.lock 6 | .idea 7 | .gitignore 8 | .eslintignore 9 | .prettierignore 10 | .tx/ 11 | layers/ 12 | locales/*.json 13 | resources/boundaries/ 14 | resources/standalone/*.sh 15 | profiles2/ 16 | tests/**/*.json 17 | tests/**/*.gpx 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "tabWidth": 4 5 | } 6 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | lang_map = pt_BR: pt-BR, zh_HK: zh-HK, nl_NL: nl-NL, da_DK: da-DK, sv_SE: sv-SE, kn_IN: kn-IN, en_NL: en-NL, gl_ES: gl-ES, fr_CA: fr-CA, zh_CN: zh-CN, zh_TW: zh-TW 4 | 5 | [o:openstreetmap:p:brouter-web:r:brouter-website] 6 | file_filter = locales/.json 7 | source_file = locales/en.json 8 | source_lang = en 9 | type = JSON 10 | 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "ms-azuretools.vscode-docker"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Chrome against localhost", 9 | "type": "chrome", 10 | "request": "launch", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}" 13 | }, 14 | { 15 | "name": "Debug Chromium (Snap) localhost", 16 | "type": "chrome", 17 | "request": "launch", 18 | "url": "http://localhost:3000", 19 | "webRoot": "${workspaceFolder}", 20 | "runtimeExecutable": "/snap/bin/chromium", 21 | "runtimeArgs": ["--new-window", "--remote-debugging-port=9222", "--disable-background-networking"], 22 | "sourceMaps": true, 23 | "sourceMapPathOverrides": { 24 | "*": "${webRoot}/*" 25 | } 26 | }, 27 | { 28 | "name": "Debug Jest tests", 29 | "type": "node", 30 | "request": "launch", 31 | "runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", "--runInBand"], 32 | "console": "integratedTerminal", 33 | "internalConsoleOptions": "neverOpen", 34 | "port": 9229 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[javascript]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | }, 6 | "[html]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | BRouter is heavily based on the following libraries: 4 | 5 | - [Leaflet](leafletjs.com/) maps library, used in conjuction with many plugins. 6 | - [Bootstrap](https://getbootstrap.com/) design library. 7 | - [JQuery](https://jquery.com) javascript library. 8 | - [Node.js](https://nodejs.org/) and [Yarn](https://yarnpkg.com/en/). 9 | 10 | ## Install dependencies 11 | 12 | ```sh 13 | yarn 14 | ``` 15 | 16 | ## Build 17 | 18 | ```sh 19 | #for development 20 | yarn build debug 21 | 22 | #for release 23 | yarn build 24 | ``` 25 | 26 | ## Develop 27 | 28 | ```sh 29 | yarn serve 30 | ``` 31 | 32 | ### Develop with Docker 33 | 34 | ```sh 35 | #to install dependencies 36 | docker-compose run --rm install 37 | 38 | #to serve for development 39 | docker-compose run --rm -p 3000:3000 serve 40 | 41 | #or 42 | docker-compose up serve 43 | ``` 44 | 45 | ## Translations 46 | 47 | `TL;DR` if you contribute to BRouter and add some translatable content, please make sure not to modify anything in `locales` folder, except `locales/en.json`. Full explanation below. 48 | 49 | ### How internationalization works 50 | 51 | BRouter is translated using [i18next](https://www.i18next.com/) library, via command `gulp i18next`. It extracts translatable elements into `locales/en.json` file (English version). (Note that unused translation keys or keys not referenced in `keys.js` might get removed automatically. Make sure to commit any changes first before running this, and only amend the previous commit after checking the diff carefully.) 52 | 53 | As soon as this file is modified, it must be uploaded by the maintainers to Transifex (manually) with the command `yarn push-transifex` (maintainers need to install the new [cli client](https://github.com/transifex/cli)). 54 | 55 | Anyone can then translate BRouter directly on [Transifex](https://www.transifex.com/openstreetmap/brouter-web/) platform. 56 | 57 | From time to time (eg. when preparing releases), we can update translated content with the command `yarn pull-transifex`. **It will overwrite all JSON files in `locales` directory**. 58 | 59 | ## License 60 | 61 | BRouter is licensed under [MIT](LICENSE). Please make sure before adding any library that it is compatible with that. (GPL licenses are incompatible for instance). 62 | 63 | ## Tests 64 | 65 | [Jest](https://jestjs.io/) is used for unit tests. 66 | 67 | Tests are located in the `tests` directory in the project root. The idea is to mirror the structure in the `js` directory and to have a test file for each file there with the same name, but with a `.test.js` suffix (which is how tests are found). 68 | 69 | Examples for running tests (see [CLI Options](https://jestjs.io/docs/cli)): 70 | 71 | ```sh 72 | # run all tests 73 | yarn test 74 | 75 | # watch to run affected tests for changed files while developing 76 | yarn test --watch 77 | 78 | # run specific test file only (while watching or without) 79 | yarn test --watch tests/format/Gpx.test.js 80 | 81 | # run a single test by name (regex for name passed to describe/test functions) 82 | yarn test --verbose -t="2-locus" 83 | ``` 84 | 85 | ## Layers 86 | 87 | ### Vector Tiles / DEM 88 | 89 | [MapLibre GL JS](https://maplibre.org/projects/maplibre-gl-js/) and [MapLibre GL Leaflet](https://github.com/maplibre/maplibre-gl-leaflet) are used to render vector tile layers. Their bundles are [only loaded](https://github.com/nrenner/brouter-web/blob/master/js/util/MaplibreGlLazyLoader.js) if the first mvt layer is actually added to the map. 90 | 91 | - [layers/mvt](https://github.com/nrenner/brouter-web/tree/master/layers/mvt) folder for layer descriptions (\*.geojson) and local styles (not a requirement) 92 | - `"type": "mvt"` in layer description 93 | - `"url": ` is for style.json (instead of tile URL), either as: 94 | - file name of local style file without `.json` (= key in dist/layers.js bundle), 95 | - suggested file naming convention: `-style.json` 96 | (stored next to description `.geojson`) 97 | - URL to remote style 98 | - access token for tile URLs 99 | - configure in `keys.js` / `keys.template.js`: 100 | `: 'mykey'` 101 | - add template to tile url in style source, e.g.: 102 | `...?access_token={keys_}` 103 | - when also appended after `?` to local style url, the layer is not added when key is not defined: 104 | `"url": "-style?{keys_}"` 105 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts as build 2 | RUN mkdir /tmp/brouter-web 3 | WORKDIR /tmp/brouter-web 4 | COPY . . 5 | RUN yarn install 6 | RUN yarn run build 7 | 8 | FROM nginx:alpine 9 | COPY --from=build /tmp/brouter-web/index.html /usr/share/nginx/html 10 | COPY --from=build /tmp/brouter-web/dist /usr/share/nginx/html/dist 11 | VOLUME [ "/usr/share/nginx/html" ] 12 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | As an alternative to the [online version](https://brouter.de/brouter-web/), the standalone server of BRouter can also be run on your local desktop. 4 | 5 | ## Install standalone zip (client and server) 6 | 7 | 1. download and unzip latest standalone archive (`brouter-web-standalone..zip`) from https://github.com/nrenner/brouter-web/releases e.g. for Linux (replace `~/opt/` with your preferred install directory and `0.11.0` with latest version): 8 | 9 | mkdir -p ~/opt/brouter 10 | cd ~/opt/brouter 11 | wget https://github.com/nrenner/brouter-web/releases/download/0.11.0/brouter-web-standalone.0.11.0.zip 12 | unzip brouter-web-standalone.0.11.0.zip 13 | 14 | 2. download one or more rd5 data file(s) from the [download directory](https://brouter.de/brouter/segments4/) or the [grid map](https://umap.openstreetmap.de/de/map/brouter-rd5-grid_9438) into the `segments4` directory 15 | 16 | ### Configure BRouter-Web 17 | 18 | In the `brouter-web` subdirectory: 19 | 20 | 1. copy `config.template.js` to `config.js` 21 | 2. add your API keys (optional) 22 | copy `keys.template.js` to `keys.js` and edit to add your keys 23 | 24 | ### Run 25 | 26 | 1. start `./run.sh` 27 | 28 | ## Running as Docker container (client only) 29 | 30 | brouter-web can be run as a Docker container, making it easy for continous deployment or running locally 31 | without having to install any build tools. 32 | 33 | The `Dockerfile` builds the application inside a NodeJS container and copies the built application into a 34 | separate Nginx based image. The application runs from a webserver only container serving only static files. 35 | 36 | ### Prerequisites 37 | 38 | - Docker installed 39 | - working directory is this repository 40 | - `config.template.js` copied to `config.js` and modified with a Brouter server, see `BR.conf.host` 41 | - `keys.template.js` to `keys.js` and add your API keys 42 | - Optionally create `profiles` directory with `brf` profile files and add path to `config.js`: 43 | BR.conf.profilesUrl = 'profiles/'; 44 | 45 | ### Building Docker image 46 | 47 | To build the Docker container run: 48 | 49 | docker build -t brouter-web . 50 | 51 | This creates a Docker image with the name `brouter-web`. 52 | 53 | ### Running Docker container 54 | 55 | To run the previously build Docker image run: 56 | 57 | docker run --rm --name brouter-web \ 58 | -p 127.0.0.1:8080:80 \ 59 | -v "`pwd`/config.js:/usr/share/nginx/html/config.js" \ 60 | -v "`pwd`/keys.js:/usr/share/nginx/html/keys.js" \ 61 | -v "`pwd`/profiles:/usr/share/nginx/html/profiles" \ 62 | brouter-web 63 | 64 | This command does the following: 65 | 66 | 1. Runs a container with the name `brouter-web` and removes it automatically after stopping 67 | 1. Binds port 80 of the container to the host interface 127.0.0.1 on port 8080 68 | 1. Takes the absolute paths of `config.js`, `keys.js` and `profiles` and mounts them inside the container 69 | 1. Uses the image `brouter-web` to run as a container 70 | 71 | brouter-web should be accessible at http://127.0.0.1:8080. 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Norbert Renner and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', {}]], 3 | sourceType: 'script', 4 | exclude: [/node_modules\/(?!overpass-layer|leaflet.locatecontrol|fit-file-writer\/).*/], 5 | }; 6 | -------------------------------------------------------------------------------- /css/leaflet-routing.css: -------------------------------------------------------------------------------- 1 | div.line-mouse-marker { 2 | background-color: white; 3 | border: 4px solid magenta; 4 | border-radius: 8px; 5 | } 6 | 7 | .dist-marker { 8 | font-size: 10px; 9 | /* 1px less than height to also center in Firefox (?) */ 10 | line-height: 17px; 11 | border: 1px solid #777; 12 | border-radius: 9px; 13 | text-align: center; 14 | text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white; 15 | color: black; 16 | background: rgba(255, 255, 255, 0.5); 17 | } 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | ## usage: docker-compose run --rm serve 5 | ## : docker-compose up (-d) serve 6 | serve: 7 | command: yarn serve 8 | image: node:lts 9 | ports: 10 | - '3000:3000' 11 | user: '1000' 12 | volumes: 13 | - ./:/src 14 | working_dir: /src 15 | 16 | ## usage: docker-compose run --rm install 17 | install: 18 | command: yarn install 19 | image: node:lts 20 | user: '1000' 21 | volumes: 22 | - ./:/src 23 | working_dir: /src 24 | -------------------------------------------------------------------------------- /js/Browser.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var touchScreen = (function () { 3 | var result = null; 4 | 5 | if ('maxTouchPoints' in navigator) { 6 | result = navigator.maxTouchPoints > 0; 7 | } else if ( 8 | window.matchMedia && 9 | window.matchMedia('(any-pointer:coarse),(any-pointer:fine),(any-pointer:none)').matches 10 | ) { 11 | result = window.matchMedia('(any-pointer:coarse)').matches; 12 | } else if ('msMaxTouchPoints' in navigator) { 13 | result = navigator.msMaxTouchPoints > 0; 14 | } 15 | 16 | return result; 17 | })(), 18 | touchScreenDetectable = touchScreen !== null, 19 | touch = touchScreenDetectable ? touchScreen : L.Browser.touch; 20 | 21 | BR.Browser = { 22 | touchScreen, 23 | touchScreenDetectable, 24 | touch, 25 | download: 26 | 'Blob' in window && 27 | 'FileReader' in window && 28 | 'readAsDataURL' in FileReader.prototype && 29 | 'download' in document.createElement('a'), 30 | }; 31 | })(); 32 | -------------------------------------------------------------------------------- /js/WhatsNew.js: -------------------------------------------------------------------------------- 1 | BR.WhatsNew = { 2 | newOnly: undefined, 3 | 4 | init() { 5 | var self = this; 6 | self.dismissableMessage = new BR.Message('whats_new_message', { 7 | onClosed() { 8 | document.getElementsByClassName('version')[0].classList.remove('version-new'); 9 | if (BR.Util.localStorageAvailable()) { 10 | localStorage.setItem('changelogVersion', self.getLatestVersion()); 11 | } 12 | }, 13 | }); 14 | $('#whatsnew').on('shown.bs.modal', function () { 15 | self.dismissableMessage.hide(); 16 | }); 17 | $('#whatsnew').on('hidden.bs.modal', function () { 18 | // next time popup is open, by default we will see everything 19 | self.prepare(false); 20 | }); 21 | if (!self.getCurrentVersion() && BR.Util.localStorageAvailable()) { 22 | localStorage.setItem('changelogVersion', self.getLatestVersion()); 23 | } 24 | self.prepare(self.hasNewVersions()); 25 | 26 | if (self.hasNewVersions()) { 27 | self.dismissableMessage.showInfo(i18next.t('whatsnew.new-version')); 28 | document.getElementsByClassName('version')[0].classList.add('version-new'); 29 | } 30 | }, 31 | 32 | getLatestVersion() { 33 | return BR.changelog.match('

')[1]; 34 | }, 35 | 36 | getCurrentVersion() { 37 | if (!BR.Util.localStorageAvailable()) return null; 38 | 39 | return localStorage.getItem('changelogVersion'); 40 | }, 41 | 42 | hasNewVersions() { 43 | return this.getCurrentVersion() && this.getCurrentVersion() !== this.getLatestVersion(); 44 | }, 45 | 46 | prepare(newOnly) { 47 | if (newOnly === this.newOnly) { 48 | // do not rebuild modal content unnecessarily 49 | return; 50 | } 51 | this.newOnly = newOnly; 52 | var container = document.querySelector('#whatsnew .modal-body'); 53 | var cl = BR.changelog; 54 | if (newOnly && this.getCurrentVersion()) { 55 | var head = '

'; 56 | var idx = cl.indexOf(head); 57 | if (idx >= 0) cl = cl.substring(0, idx); 58 | } 59 | container.innerHTML = cl; 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /js/control/Control.Layers.js: -------------------------------------------------------------------------------- 1 | BR.ControlLayers = L.Control.Layers.extend({ 2 | getActiveLayers() { 3 | var result = []; 4 | 5 | for (var i = 0; i < this._layers.length; i++) { 6 | var obj = this._layers[i]; 7 | if (this._map.hasLayer(obj.layer)) { 8 | if (obj.overlay) { 9 | result.push(obj); 10 | } else { 11 | result.unshift(obj); 12 | } 13 | } 14 | } 15 | 16 | return result; 17 | }, 18 | 19 | getActiveBaseLayer() { 20 | var activeLayers = this.getActiveLayers(); 21 | for (var i = 0; i < activeLayers.length; i++) { 22 | var obj = activeLayers[i]; 23 | if (!obj.overlay) { 24 | return obj; 25 | } 26 | } 27 | 28 | return null; 29 | }, 30 | 31 | removeActiveLayers() { 32 | var removed = []; 33 | 34 | for (var i = 0; i < this._layers.length; i++) { 35 | var obj = this._layers[i]; 36 | if (this._map.hasLayer(obj.layer)) { 37 | this._map.removeLayer(obj.layer); 38 | removed.push(obj); 39 | } 40 | } 41 | 42 | return removed; 43 | }, 44 | 45 | getLayer(name) { 46 | for (var i = 0; i < this._layers.length; i++) { 47 | var obj = this._layers[i]; 48 | if (obj.name === name) { 49 | return obj; 50 | } 51 | } 52 | 53 | return null; 54 | }, 55 | 56 | getBaseLayers() { 57 | return this._layers.filter(function (obj) { 58 | return !obj.overlay; 59 | }); 60 | }, 61 | 62 | activateLayer(obj) { 63 | if (!this._map.hasLayer(obj.layer)) { 64 | this._map.addLayer(obj.layer); 65 | } 66 | }, 67 | 68 | activateFirstLayer() { 69 | for (var i = 0; i < this._layers.length; i++) { 70 | var obj = this._layers[i]; 71 | if (!obj.overlay) { 72 | this.activateLayer(obj); 73 | break; 74 | } 75 | } 76 | }, 77 | 78 | activateBaseLayerIndex(index) { 79 | var baseLayers = this.getBaseLayers(); 80 | var obj = baseLayers[index]; 81 | 82 | this.activateLayer(obj); 83 | }, 84 | 85 | _addLayer(layer, name, overlay) { 86 | L.Control.Layers.prototype._addLayer.call(this, layer, name, overlay); 87 | 88 | // override z-index assignment to fix that base layers added later 89 | // are on top of overlays; set all base layers to 0 90 | if (this.options.autoZIndex && layer.setZIndex) { 91 | if (!overlay) { 92 | // undo increase in super method 93 | this._lastZIndex--; 94 | 95 | layer.setZIndex(0); 96 | } 97 | } 98 | }, 99 | }); 100 | -------------------------------------------------------------------------------- /js/control/Itinerary.js: -------------------------------------------------------------------------------- 1 | BR.Itinerary = L.Class.extend({ 2 | initialize() { 3 | this._content = document.getElementById('itinerary'); 4 | this.update(); 5 | }, 6 | 7 | update(polyline, segments) { 8 | var i, 9 | j, 10 | iter, 11 | html = ''; 12 | 13 | html += '
';
14 |         for (i = 0; segments && i < segments.length; i++) {
15 |             iter = segments[i].feature.iternity;
16 |             for (j = 0; iter && j < iter.length; j++) {
17 |                 html += iter[j] + '\n';
18 |             }
19 |         }
20 |         html += '
'; 21 | 22 | this._content.innerHTML = html; 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /js/control/Message.js: -------------------------------------------------------------------------------- 1 | BR.Message = L.Class.extend({ 2 | options: { 3 | // true to manually attach click event to close button, 4 | // Bootstrap data-api's auto-initialization doesn't work in Controls because of stopPropagation 5 | alert: false, 6 | onClosed: null, 7 | }, 8 | 9 | initialize(id, options) { 10 | L.setOptions(this, options); 11 | this.id = id; 12 | }, 13 | 14 | _show(msg, type) { 15 | var ele = L.DomUtil.get(this.id), 16 | iconClass, 17 | alertClass; 18 | switch (type) { 19 | case 'error': 20 | iconClass = 'fa-times-circle'; 21 | alertClass = 'alert-danger'; 22 | break; 23 | case 'warning': 24 | iconClass = 'fa-exclamation-triangle'; 25 | alertClass = 'alert-warning'; 26 | break; 27 | case 'loading': 28 | iconClass = 'fa-spinner fa-pulse'; 29 | alertClass = 'alert-secondary'; 30 | break; 31 | default: 32 | case 'info': 33 | iconClass = 'fa-info-circle'; 34 | alertClass = 'alert-info'; 35 | break; 36 | } 37 | 38 | L.DomEvent.disableClickPropagation(ele); 39 | 40 | ele.innerHTML = 41 | ''; 52 | 53 | if (this.options.onClosed) { 54 | $('#' + this.id + ' .alert').on('closed.bs.alert', this.options.onClosed); 55 | } 56 | 57 | if (this.options.alert) { 58 | $('#' + this.id + ' .alert').alert(); 59 | } 60 | }, 61 | 62 | hide() { 63 | $('#' + this.id + ' .alert').alert('close'); 64 | }, 65 | 66 | showError(err) { 67 | if (err && err.message) err = err.message; 68 | 69 | if (err == 'target island detected for section 0\n') { 70 | err = i18next.t('warning.no-route-found'); 71 | } else if (err == 'no track found at pass=0\n') { 72 | err = i18next.t('warning.no-route-found'); 73 | } else if (err == 'to-position not mapped in existing datafile\n') { 74 | err = i18next.t('warning.invalid-route-to'); 75 | } else if (err == 'from-position not mapped in existing datafile\n') { 76 | err = i18next.t('warning.invalid-route-from'); 77 | } else if (err && err.startsWith('null description for: ')) { 78 | err = i18next.t('warning.no-route-found'); 79 | } 80 | this._show(err, 'error'); 81 | }, 82 | 83 | showWarning(msg) { 84 | this._show(msg, 'warning'); 85 | }, 86 | 87 | showInfo(msg) { 88 | this._show(msg, 'info'); 89 | }, 90 | 91 | showLoading(msg) { 92 | this._show(msg, 'loading'); 93 | }, 94 | }); 95 | 96 | // static instance as global control 97 | BR.message = new BR.Message('message'); 98 | -------------------------------------------------------------------------------- /js/control/OpacitySlider.js: -------------------------------------------------------------------------------- 1 | BR.OpacitySlider = L.Class.extend({ 2 | options: { 3 | id: '', 4 | reversed: true, 5 | orientation: 'vertical', 6 | defaultValue: BR.conf.defaultOpacity, 7 | title: '', 8 | callback(opacity) {}, 9 | }, 10 | 11 | initialize(options) { 12 | L.setOptions(this, options); 13 | 14 | var input = (this.input = $('')), 15 | item = BR.Util.localStorageAvailable() ? localStorage['opacitySliderValue' + this.options.id] : null, 16 | value = item ? parseInt(item) : this.options.defaultValue * 100, 17 | minOpacity = (BR.conf.minOpacity || 0) * 100; 18 | 19 | if (value < minOpacity) { 20 | value = minOpacity; 21 | } 22 | 23 | input 24 | .slider({ 25 | id: this.options.id, 26 | min: 0, 27 | max: 100, 28 | step: 1, 29 | value, 30 | orientation: this.options.orientation, 31 | reversed: this.options.reversed, 32 | selection: this.options.reversed ? 'before' : 'after', // inverted, serves as track style, see css 33 | tooltip: 'hide', 34 | }) 35 | .on('slide slideStop', { self: this }, function (evt) { 36 | evt.data.self.options.callback(evt.value / 100); 37 | }) 38 | .on('slideStop', { self: this }, function (evt) { 39 | if (BR.Util.localStorageAvailable()) { 40 | localStorage['opacitySliderValue' + evt.data.self.options.id] = evt.value; 41 | } 42 | }); 43 | 44 | this.getElement().title = this.options.title; 45 | 46 | this.options.callback(value / 100); 47 | 48 | if (this.options.muteKeyCode) { 49 | L.DomEvent.addListener(document, 'keydown', this._keydownListener, this); 50 | L.DomEvent.addListener(document, 'keyup', this._keyupListener, this); 51 | } 52 | }, 53 | 54 | _keydownListener(e) { 55 | if (BR.Util.keyboardShortcutsAllowed(e) && e.keyCode === this.options.muteKeyCode) { 56 | this.options.callback(0); 57 | } 58 | }, 59 | 60 | _keyupListener(e) { 61 | if (BR.Util.keyboardShortcutsAllowed(e) && e.keyCode === this.options.muteKeyCode) { 62 | this.options.callback(this.input.val() / 100); 63 | } 64 | }, 65 | 66 | getElement() { 67 | return this.input.slider('getElement'); 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /js/control/OpacitySliderControl.js: -------------------------------------------------------------------------------- 1 | BR.OpacitySliderControl = L.Control.extend({ 2 | options: { 3 | position: 'topleft', 4 | }, 5 | 6 | onAdd(map) { 7 | var container = L.DomUtil.create('div', 'leaflet-bar control-slider'); 8 | 9 | // prevent also dragging map in Chrome 10 | L.DomEvent.disableClickPropagation(container); 11 | 12 | // migrate legacy value 13 | if (BR.Util.localStorageAvailable()) { 14 | var value = localStorage.getItem('opacitySliderValue'); 15 | if (value !== null) { 16 | localStorage.setItem('opacitySliderValue' + this.options.id, value); 17 | localStorage.removeItem('opacitySliderValue'); 18 | } 19 | } 20 | 21 | var slider = new BR.OpacitySlider(this.options); 22 | container.appendChild(slider.getElement()); 23 | 24 | var stopClickAfterSlide = function (evt) { 25 | L.DomEvent.stop(evt); 26 | removeStopClickListeners(); 27 | }; 28 | 29 | var removeStopClickListeners = function () { 30 | document.removeEventListener('click', stopClickAfterSlide, true); 31 | document.removeEventListener('mousedown', removeStopClickListeners, true); 32 | }; 33 | 34 | slider.input 35 | .on('slideStart', function (evt) { 36 | // dragging beyond slider control selects zoom control +/- text in Firefox 37 | L.DomUtil.disableTextSelection(); 38 | }) 39 | .on('slideStop', { self: this }, function (evt) { 40 | L.DomUtil.enableTextSelection(); 41 | 42 | // When dragging outside slider and over map, click event after mouseup 43 | // adds marker when active on Chromium. So disable click (not needed) 44 | // once after sliding. 45 | document.addEventListener('click', stopClickAfterSlide, true); 46 | // Firefox does not fire click event in this case, so make sure stop listener 47 | // is always removed on next mousedown. 48 | document.addEventListener('mousedown', removeStopClickListeners, true); 49 | }); 50 | 51 | return container; 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /js/control/TrackStats.js: -------------------------------------------------------------------------------- 1 | BR.TrackStats = L.Class.extend({ 2 | update(polyline, segments) { 3 | if (segments.length == 0) { 4 | $('#stats-container').hide(); 5 | $('#stats-info').show(); 6 | return; 7 | } 8 | 9 | $('#stats-container').show(); 10 | $('#stats-info').hide(); 11 | 12 | document.getElementById('beeline-warning').hidden = !BR.Routing.hasBeeline(segments); 13 | 14 | var stats = this.calcStats(polyline, segments), 15 | length1 = L.Util.formatNum(stats.trackLength / 1000, 1).toLocaleString(), 16 | length3 = L.Util.formatNum(stats.trackLength / 1000, 3).toLocaleString(undefined, { 17 | minimumFractionDigits: 3, 18 | }), 19 | formattedAscend = stats.filteredAscend.toLocaleString(), 20 | formattedPlainAscend = stats.plainAscend.toLocaleString(), 21 | formattedCost = stats.cost.toLocaleString(), 22 | meanCostFactor = stats.trackLength 23 | ? L.Util.formatNum(stats.cost / stats.trackLength, 2).toLocaleString() 24 | : '0', 25 | formattedTime = 26 | Math.trunc(stats.totalTime / 3600) + ':' + ('0' + Math.trunc((stats.totalTime % 3600) / 60)).slice(-2), 27 | formattedTimeHMS = formattedTime + ':' + ('0' + Math.trunc(stats.totalTime % 60)).slice(-2), 28 | formattedEnergy = L.Util.formatNum(stats.totalEnergy / 3600000, 2).toLocaleString(), 29 | meanEnergy = stats.trackLength 30 | ? L.Util.formatNum(stats.totalEnergy / 36 / stats.trackLength, 2).toLocaleString() 31 | : '0'; 32 | 33 | $('#distance').html(length1); 34 | // alternative 3-digit format down to meters as tooltip 35 | $('#distance').attr('title', length3 + ' km'); 36 | $('#ascend').html(formattedAscend); 37 | $('#plainascend').html(formattedPlainAscend); 38 | $('#cost').html(formattedCost); 39 | $('#meancostfactor').html(meanCostFactor); 40 | $('#totaltime').html(formattedTime); 41 | // alternative time format with seconds display as tooltip 42 | $('#totaltime').attr('title', formattedTimeHMS + ' h'); 43 | $('#totalenergy').html(formattedEnergy); 44 | $('#meanenergy').html(meanEnergy); 45 | }, 46 | 47 | calcStats(polyline, segments) { 48 | var stats = { 49 | trackLength: 0, 50 | filteredAscend: 0, 51 | plainAscend: 0, 52 | totalTime: 0, 53 | totalEnergy: 0, 54 | cost: 0, 55 | }; 56 | var i, props; 57 | 58 | for (i = 0; segments && i < segments.length; i++) { 59 | props = segments[i].feature.properties; 60 | stats.trackLength += +props['track-length']; 61 | stats.filteredAscend += +props['filtered ascend']; 62 | stats.plainAscend += +props['plain-ascend']; 63 | stats.totalTime += +props['total-time']; 64 | stats.totalEnergy += +props['total-energy']; 65 | stats.cost += +props['cost']; 66 | } 67 | 68 | return stats; 69 | }, 70 | }); 71 | -------------------------------------------------------------------------------- /js/format/Csv.js: -------------------------------------------------------------------------------- 1 | BR.Csv = { 2 | format(geoJson) { 3 | const separator = '\t'; 4 | const newline = '\n'; 5 | const messages = geoJson.features[0].properties.messages; 6 | let csv = ''; 7 | 8 | for (const entries of messages) { 9 | csv += entries.join(separator) + newline; 10 | } 11 | 12 | return csv; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /js/format/Fit.js: -------------------------------------------------------------------------------- 1 | BR.Fit = { 2 | format(geoJson, turnInstructionMode = 0) { 3 | if (!geoJson?.features) return ''; 4 | 5 | function calcDistance(p1, p2) { 6 | const [ilon1, ilat1] = btools.util.CheapRuler.toIntegerLngLat(p1); 7 | const [ilon2, ilat2] = btools.util.CheapRuler.toIntegerLngLat(p2); 8 | return btools.util.CheapRuler.distance(ilon1, ilat1, ilon2, ilat2); 9 | } 10 | 11 | const course = geoJson.features[0]; 12 | const track = course.geometry.coordinates; 13 | const startPoint = track[0]; 14 | const endPoint = track[track.length - 1]; 15 | let trackStamps = course.properties.times || course.properties.coordTimes || []; 16 | let last = 0; 17 | trackStamps = trackStamps.map((stamp) => { 18 | stamp = Math.round(stamp); // FIT only support seconds 19 | // avoid strange & identical timestamps. This is required(?) for matching 20 | // of turn instructions with the route. 21 | if (stamp <= last) { 22 | stamp = last + 1; 23 | } 24 | last = stamp; 25 | // FIT epoch starts at 1989-12-31T00:00 - avoid wrapping 26 | return (631065600 + stamp) * 1000; 27 | }); 28 | 29 | const encoder = new FITCourseFile( 30 | course.properties.name, 31 | trackStamps[0], 32 | course.properties['total-time'], 33 | startPoint, 34 | endPoint, 35 | course.properties['filtered-ascend'] 36 | ); 37 | let distanceTotal = 0; 38 | let distanceTillPoint = [0]; 39 | encoder.point(trackStamps[0], track[0], track[0][2], 0); 40 | for (let i = 1; i < track.length; i++) { 41 | distanceTotal += calcDistance(track[i - 1], track[i]); 42 | distanceTillPoint[i] = distanceTotal; 43 | encoder.point(trackStamps[i], track[i], track[i][2], distanceTotal); 44 | } 45 | // Note 1: Mixing points and hints is also legal should some devices 46 | // require it - until then keep it simple. 47 | // Note 2: The distance inside the FIT files seems somewhat redundant. 48 | // Not sure if it is really required. May depend on the device? 49 | // Note 3: Can't use hint.distance that is the value to the next turn. 50 | const voiceHints = BR.voiceHints(geoJson, 2); 51 | voiceHints._loopHints((hint, cmd, coord) => { 52 | encoder.turn( 53 | trackStamps[hint.indexInTrack], 54 | coord, 55 | cmd.fit, 56 | cmd.fit === 'generic' ? cmd.message : undefined, 57 | distanceTillPoint[hint.indexInTrack] 58 | ); 59 | }); 60 | 61 | return encoder.finalize(trackStamps[trackStamps.length - 1]); 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /js/format/Gpx.js: -------------------------------------------------------------------------------- 1 | // derived from BRouter btools.router.OsmTrack.formatAsGpx 2 | BR.Gpx = { 3 | format(geoJson, turnInstructionMode = 0, transportMode = 'bike') { 4 | if (!geoJson?.features) return ''; 5 | 6 | class GpxTransform { 7 | constructor(voiceHintsTransform) { 8 | this.voiceHintsTransform = voiceHintsTransform; 9 | this.comment = voiceHintsTransform?.comment || ''; 10 | 11 | if (this.voiceHintsTransform) { 12 | Object.keys(this.voiceHintsTransform).forEach((member) => { 13 | if (!GpxTransform.prototype.hasOwnProperty(member)) { 14 | this[member] = this.voiceHintsTransform[member]; 15 | } 16 | }); 17 | } 18 | } 19 | 20 | wpt(wpt, feature, coord, index) { 21 | // not in use right now, just to be safe in case of future overrides 22 | wpt = (voiceHintsTransform?.wpt && voiceHintsTransform.wpt(wpt, feature, coord, index)) || wpt; 23 | if (feature.properties.name) { 24 | wpt.name = feature.properties.name; 25 | } 26 | const type = feature.properties.type; 27 | if (type && type !== 'poi') { 28 | wpt.type = type; 29 | } 30 | return wpt; 31 | } 32 | 33 | trk(trk, feature, coordsList) { 34 | trk = (voiceHintsTransform?.trk && voiceHintsTransform.trk(trk, feature, coordsList)) || trk; 35 | // name as first tag, by using assign and in this order 36 | return Object.assign( 37 | { 38 | name: feature.properties.name, 39 | link: { 40 | '@href': location.href, 41 | text: BR.conf.appName || 'BRouter-Web', 42 | }, 43 | }, 44 | trk 45 | ); 46 | } 47 | } 48 | 49 | let voiceHintsTransform; 50 | if (turnInstructionMode > 1) { 51 | const voiceHints = BR.voiceHints(geoJson, turnInstructionMode, transportMode); 52 | voiceHintsTransform = voiceHints.getGpxTransform(); 53 | } 54 | const gpxTransform = new GpxTransform(voiceHintsTransform); 55 | 56 | let gpx = togpx(geoJson, { 57 | creator: (BR.conf.appName || 'BRouter-Web') + ' ' + BR.version, 58 | featureTitle() {}, 59 | featureDescription() {}, 60 | featureCoordTimes() {}, 61 | transform: gpxTransform, 62 | }); 63 | const statsComment = BR.Gpx._statsComment(geoJson); 64 | gpx = '' + statsComment + gpxTransform.comment + gpx; 65 | gpx = BR.Xml.pretty(gpx); 66 | return gpx; 67 | }, 68 | 69 | // 70 | _statsComment(geoJson) { 71 | const props = geoJson.features?.[0].properties; 72 | if (!props) return ''; 73 | 74 | let comment = ''; 87 | return comment; 88 | }, 89 | 90 | // 14833 -> 4h 7m 13s 91 | // see BRouter OsmTrack.getFormattedTime2 92 | formatTime(seconds) { 93 | const hours = Math.trunc(seconds / 3600); 94 | const minutes = Math.trunc((seconds - hours * 3600) / 60); 95 | seconds = seconds - hours * 3600 - minutes * 60; 96 | let time = ''; 97 | if (hours != 0) time += hours + 'h '; 98 | if (minutes != 0) time += minutes + 'm '; 99 | if (seconds != 0) time += seconds + 's'; 100 | return time; 101 | }, 102 | }; 103 | -------------------------------------------------------------------------------- /js/format/Kml.js: -------------------------------------------------------------------------------- 1 | BR.Kml = { 2 | format(geoJson) { 3 | // don't export properties as , probably no need for it 4 | geoJson.features[0].properties = { name: geoJson.features[0].properties.name }; 5 | return BR.Xml.pretty(tokml(geoJson)); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /js/format/Xml.js: -------------------------------------------------------------------------------- 1 | BR.Xml = { 2 | // modified version of 3 | // https://gist.github.com/sente/1083506#gistcomment-2254622 4 | // MIT License, Copyright (c) 2016 Stuart Powers, ES6 version by Jonathan Gruber 5 | pretty(xml, indentSize = 1) { 6 | const PADDING = ' '.repeat(indentSize); 7 | const newline = '\n'; 8 | 9 | // Remove all the newlines and then remove all the spaces between tags 10 | xml = xml.replace(/>\s*(\r\n|\n|\r)\s*<').replace(/>\s+<'); 11 | xml = xml.replace('', ''); 12 | 13 | // break into lines, keeping defined tags on a single line 14 | const reg = /><(\/?)([\w!?][^ />]*)/g; 15 | const singleLineTagList = ['trkpt', 'wpt']; 16 | let lines = []; 17 | let singleLineTag = null; 18 | let startIndex = 0; 19 | let match; 20 | while ((match = reg.exec(xml)) !== null) { 21 | const tag = match[2]; 22 | if (singleLineTag) { 23 | if (singleLineTag === tag) { 24 | singleLineTag = null; 25 | } 26 | } else { 27 | if (singleLineTagList.includes(tag)) { 28 | const closeIndex = xml.indexOf('>', match.index + 1); 29 | const selfClosing = xml.charAt(closeIndex - 1) === '/'; 30 | if (!selfClosing) { 31 | singleLineTag = tag; 32 | } 33 | } 34 | let endIndex = match.index + 1; 35 | lines.push(xml.substring(startIndex, endIndex)); 36 | startIndex = endIndex; 37 | } 38 | } 39 | lines.push(xml.substring(startIndex)); 40 | 41 | // indent 42 | const startTextEnd = /.+<\/\w[^>]*>$/; 43 | const endTag = /^<\/\w/; 44 | const startTag = /^<\w[^>]*[^\/]>.*$/; 45 | let pad = 0; 46 | lines = lines.map((node, index) => { 47 | let indent = 0; 48 | if (node.match(startTextEnd)) { 49 | indent = 0; 50 | } else if (node.match(endTag) && pad > 0) { 51 | pad -= 1; 52 | } else if (node.match(startTag)) { 53 | indent = 1; 54 | } else { 55 | indent = 0; 56 | } 57 | 58 | pad += indent; 59 | 60 | return PADDING.repeat(pad - indent) + node; 61 | }); 62 | 63 | // break gpx attributes into separate lines 64 | for (const [i, line] of lines.entries()) { 65 | if (line.includes('Bing Maps' + 6 | ' (TOU)', 7 | }, 8 | 9 | initialize(key, options) { 10 | L.BingLayer.prototype.initialize.call(this, key, options); 11 | 12 | this._logo = L.control({ position: 'bottomleft' }); 13 | this._logo.onAdd = function (map) { 14 | this._div = L.DomUtil.create('div', 'bing-logo'); 15 | this._div.innerHTML = 16 | ''; 17 | return this._div; 18 | }; 19 | }, 20 | 21 | onAdd(map) { 22 | L.BingLayer.prototype.onAdd.call(this, map); 23 | map.addControl(this._logo); 24 | }, 25 | 26 | onRemove(map) { 27 | L.BingLayer.prototype.onRemove.call(this, map); 28 | map.removeControl(this._logo); 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /js/plugin/Sidebar.js: -------------------------------------------------------------------------------- 1 | BR.Sidebar = L.Control.Sidebar.extend({ 2 | storageId: 'sidebar-control', 3 | 4 | options: { 5 | position: 'right', 6 | container: 'sidebar', 7 | tabContainer: 'sidebarTabs', 8 | autopan: false, 9 | defaultTabId: '', 10 | 11 | shortcut: { 12 | toggleTabs: 84, // char code for 't' 13 | }, 14 | 15 | // Tabs to be notified when shown or hidden 16 | // (tab div id -> object implementing show/hide methods) 17 | listeningTabs: {}, 18 | }, 19 | 20 | initialize(id, options) { 21 | L.Control.Sidebar.prototype.initialize.call(this, id, options); 22 | 23 | this.oldTab = null; 24 | 25 | L.DomEvent.addListener(document, 'keydown', this._keydownListener, this); 26 | }, 27 | 28 | addTo(map) { 29 | L.Control.Sidebar.prototype.addTo.call(this, map); 30 | 31 | this.on('content', this._notifyOnContent, this); 32 | this.on('closing', this._notifyOnClose, this); 33 | this.on('toggleExpand', this._notifyOnResize, this); 34 | 35 | this.on( 36 | 'closing', 37 | function () { 38 | this._map.getContainer().focus(); 39 | }, 40 | this 41 | ); 42 | 43 | this.recentTab = this.options.defaultTabId; 44 | this.on( 45 | 'content', 46 | function (tab) { 47 | this.recentTab = tab.id; 48 | }, 49 | this 50 | ); 51 | 52 | this._rememberTabState(); 53 | 54 | if (L.Browser.touch && BR.Browser.touchScreenDetectable && !BR.Browser.touchScreen) { 55 | L.DomUtil.removeClass(this._container, 'leaflet-touch'); 56 | L.DomUtil.removeClass(this._tabContainer, 'leaflet-touch'); 57 | } 58 | 59 | return this; 60 | }, 61 | 62 | showPanel(id) { 63 | var tab = this._getTab(id); 64 | tab.hidden = false; 65 | 66 | return this; 67 | }, 68 | 69 | _rememberTabState() { 70 | if (BR.Util.localStorageAvailable()) { 71 | this.on('content closing', this._storeActiveTab, this); 72 | 73 | var tabId = localStorage.getItem(this.storageId); 74 | 75 | // 'true': legacy value for toggling old sidebar 76 | if (tabId === 'true') { 77 | tabId = this.options.defaultTabId; 78 | } else if (tabId === null) { 79 | // not set: closed by default for new users 80 | tabId = ''; 81 | } 82 | if (tabId !== '' && this._getTab(tabId)) { 83 | this.open(tabId); 84 | } 85 | } 86 | }, 87 | 88 | _notifyShow(tab) { 89 | if (tab && tab.show) { 90 | tab.show(); 91 | } 92 | }, 93 | 94 | _notifyHide(tab) { 95 | if (tab && tab.hide) { 96 | tab.hide(); 97 | } 98 | }, 99 | 100 | _notifyOnContent(e) { 101 | var tab = this.options.listeningTabs[e.id]; 102 | this._notifyHide(this.oldTab); 103 | this._notifyShow(tab); 104 | this.oldTab = tab; 105 | }, 106 | 107 | _notifyOnClose(e) { 108 | this._notifyHide(this.oldTab); 109 | this.oldTab = null; 110 | }, 111 | 112 | _notifyOnResize(e) { 113 | var tab = this.oldTab; 114 | if (tab && tab.onResize) { 115 | tab.onResize(); 116 | } 117 | }, 118 | 119 | _storeActiveTab(e) { 120 | localStorage.setItem(this.storageId, e.id || ''); 121 | }, 122 | 123 | _keydownListener(e) { 124 | if (BR.Util.keyboardShortcutsAllowed(e) && e.keyCode === this.options.shortcut.toggleTabs) { 125 | if ($('#sidebarTabs > ul > li[class=active]').length) { 126 | // sidebar is currently open, close current tab 127 | if (!e.shiftKey) { 128 | this.close(); 129 | } 130 | } else { 131 | // sidebar is currently closed, open recent or default tab 132 | this.open(this.recentTab); 133 | } 134 | if (e.shiftKey) { 135 | // try to find next tab 136 | var nextTab = $('#sidebarTabs > ul > li[class=active] ~ li:not([hidden]) > a'); 137 | if (!nextTab.length) { 138 | // wrap around to first tab 139 | nextTab = $('#sidebarTabs > ul > li:not([hidden]) > a'); 140 | } 141 | // switch to next or first tab 142 | this.open(nextTab.attr('href').slice(1)); 143 | } 144 | } 145 | }, 146 | }); 147 | 148 | BR.sidebar = function (divId, options) { 149 | return new BR.Sidebar(divId, options); 150 | }; 151 | -------------------------------------------------------------------------------- /js/plugin/stravaSegments.js: -------------------------------------------------------------------------------- 1 | BR.stravaSegments = function (map, layersControl) { 2 | var stravaControl = L.control 3 | .stravaSegments({ 4 | runningTitle: i18next.t('map.strava-shortcut', { action: '$t(map.strava-running)', key: 'S' }), 5 | bikingTitle: i18next.t('map.strava-shortcut', { action: '$t(map.strava-biking)', key: 'S' }), 6 | loadingTitle: i18next.t('map.loading'), 7 | stravaToken: BR.keys.strava, 8 | }) 9 | .addTo(map); 10 | layersControl.addOverlay(stravaControl.stravaLayer, i18next.t('map.layer.strava-segments')); 11 | stravaControl.onError = function (err) { 12 | BR.message.showError( 13 | i18next.t('warning.strava-error', { 14 | error: err && err.message ? err.message : err, 15 | }) 16 | ); 17 | }; 18 | 19 | L.setOptions(this, { 20 | shortcut: { 21 | toggleLayer: 83, // char code for 's' 22 | }, 23 | }); 24 | 25 | // hide strava buttons when layer is inactive 26 | var toggleStravaControl = function () { 27 | var stravaBar = stravaControl.runningButton.button.parentElement; 28 | stravaBar.hidden = !stravaBar.hidden; 29 | }; 30 | toggleStravaControl(); 31 | stravaControl.stravaLayer.on('add remove', toggleStravaControl); 32 | 33 | L.DomEvent.addListener( 34 | document, 35 | 'keydown', 36 | function (e) { 37 | if (BR.Util.keyboardShortcutsAllowed(e) && e.keyCode === this.options.shortcut.toggleLayer) { 38 | if (map.hasLayer(stravaControl.stravaLayer)) { 39 | map.removeLayer(stravaControl.stravaLayer); 40 | } else { 41 | map.addLayer(stravaControl.stravaLayer); 42 | } 43 | } 44 | }, 45 | this 46 | ); 47 | 48 | return stravaControl; 49 | }; 50 | -------------------------------------------------------------------------------- /js/router/brouterCgi.js: -------------------------------------------------------------------------------- 1 | // BRouter online demo interface 2 | // TODO remove or adopt to new structure (only supports two waypoints!) 3 | var brouterCgi = (function () { 4 | // http://brouter.de/cgi-bin/brouter.sh?coords=13.404681_52.520185_13.340278_52.512356_trekking_0 5 | //var URL_TEMPLATE = '/cgi-bin/proxy.cgi?url=' + 'http://brouter.de/cgi-bin/brouter.sh?coords={fromLng}_{fromLat}_{toLng}_{toLat}_{profile}_{alt}'; 6 | var URL_TEMPLATE = 7 | '/proxy.php?url=' + 'cgi-bin/brouter.sh?coords={fromLng}_{fromLat}_{toLng}_{toLat}_{profile}_{alt}'; 8 | var PRECISION = 6; 9 | 10 | function getUrl(polyline) { 11 | var latLngs = polyline.getLatLngs(); 12 | var urlParams = { 13 | fromLat: L.Util.formatNum(latLngs[0].lat, PRECISION), 14 | fromLng: L.Util.formatNum(latLngs[0].lng, PRECISION), 15 | toLat: L.Util.formatNum(latLngs[1].lat, PRECISION), 16 | toLng: L.Util.formatNum(latLngs[1].lng, PRECISION), 17 | profile: 'trekking', 18 | alt: '0', 19 | }; 20 | var url = L.Util.template(URL_TEMPLATE, urlParams); 21 | //console.log(url); 22 | //return 'test/test.gpx'; 23 | return url; 24 | } 25 | 26 | return { 27 | getUrl, 28 | }; 29 | })(); 30 | -------------------------------------------------------------------------------- /js/util/ClickTolerantBoxZoom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Avoids conflict between shift-click and shift-drag. 3 | * Extends BoxZoom to support a small click tolerance like in Draggable and 4 | * a larger drag tolerance as a "neutral zone" before starting with box zoom dragging, 5 | * to avoid accidental zooms. 6 | */ 7 | BR.ClickTolerantBoxZoom = L.Map.BoxZoom.extend({ 8 | clickTolerance: L.Draggable.prototype.options.clickTolerance, 9 | // use more than clickTolerance before starting box zoom to surely avoid accidental zooms 10 | dragTolerance: 15, 11 | // flag to enable or disable click/drag tolerance, classic BoxZoom behaviour when false 12 | tolerant: true, 13 | 14 | // "neutral zone", state between clickTolerance and dragTolerance, 15 | // already signals dragging to map and thus prevents click 16 | _preMoved: false, 17 | 18 | moved() { 19 | return this._preMoved || this._moved; 20 | }, 21 | 22 | _resetState() { 23 | L.Map.BoxZoom.prototype._resetState.call(this); 24 | this._preMoved = false; 25 | }, 26 | 27 | _onMouseMove(e) { 28 | if (!this._moved) { 29 | const point = this._map.mouseEventToContainerPoint(e); 30 | 31 | // derived from L.Draggable._onMove 32 | var offsetPoint = point.clone()._subtract(this._startPoint); 33 | var offset = Math.abs(offsetPoint.x || 0) + Math.abs(offsetPoint.y || 0); 34 | 35 | if (this.tolerant && offset < this.dragTolerance) { 36 | if (!this._preMoved && offset >= this.clickTolerance) { 37 | this._preMoved = true; 38 | } 39 | 40 | return; 41 | } 42 | } 43 | 44 | L.Map.BoxZoom.prototype._onMouseMove.call(this, e); 45 | }, 46 | 47 | _onMouseUp(e) { 48 | L.Map.BoxZoom.prototype._onMouseUp.call(this, e); 49 | 50 | if (!this._moved && this._preMoved) { 51 | this._clearDeferredResetState(); 52 | this._resetStateTimeout = setTimeout(L.Util.bind(this._resetState, this), 0); 53 | } 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /js/util/LeafletPatches.js: -------------------------------------------------------------------------------- 1 | // Fixes wrong added offset when dragging, which can leave mouse off the marker 2 | // after dragging and cause a map click 3 | // see https://github.com/Leaflet/Leaflet/pull/7446 4 | // see https://github.com/Leaflet/Leaflet/issues/4457 5 | L.Draggable.prototype._onMoveOrig = L.Draggable.prototype._onMove; 6 | L.Draggable.prototype._onMove = function (e) { 7 | var start = !this._moved; 8 | 9 | this._onMoveOrig.call(this, e); 10 | 11 | if (start && this._moved) { 12 | var offset = this._newPos.subtract(this._startPos); 13 | this._startPos = this._startPos.add(offset); 14 | this._newPos = this._newPos.add(offset); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /js/util/MaplibreGlLazyLoader.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // import() uses path relative to calling script, script injection relative to index.html, 3 | // so figure out script-relative path without hard-coding it (only as fallback) for the latter 4 | // eslint-disable-next-line 5 | const baseUrl = document.currentScript?.src.split('/').slice(0, -1).join('/') || window.location.origin + '/dist'; 6 | const getAbsoluteScriptUrl = (src) => new URL(src, baseUrl + '/').href; 7 | 8 | // simple custom import() polyfill that doesn't need module support and can't load modules 9 | // (derived from various sources) 10 | function importPolyfill(src) { 11 | return new Promise((resolve, reject) => { 12 | try { 13 | // `import` is a reserved keyword even in old browsers not supporting it, 14 | // so it needs to be evaluated at runtime, otherwise causes a parse error on load 15 | new Function('return import("' + src + '")')().then(() => resolve()); 16 | } catch (e) { 17 | const url = getAbsoluteScriptUrl(src); 18 | var script = document.createElement('script'); 19 | script.onload = () => { 20 | resolve(); 21 | }; 22 | script.onerror = () => { 23 | reject(new Error(`Error importing: "${url}"`)); 24 | script.remove(); 25 | }; 26 | script.src = url; 27 | document.body.appendChild(script); 28 | } 29 | }); 30 | } 31 | 32 | /** 33 | * Only load Maplibre bundles when layer is actually added, using dynamic imports 34 | */ 35 | BR.MaplibreGlLazyLoader = L.Layer.extend({ 36 | initialize(options) { 37 | this.options = options; 38 | }, 39 | 40 | onAdd(map) { 41 | if (!('maplibreGL' in L)) { 42 | this._load(); 43 | } else { 44 | this._addGlLayer(); 45 | } 46 | return this; 47 | }, 48 | 49 | onRemove(map) { 50 | if (this.glLayer) { 51 | this._map.removeLayer(this.glLayer); 52 | } 53 | this.glLayer = null; 54 | return this; 55 | }, 56 | 57 | // needed when overlay, also requires `position: absolute` (see css) 58 | setZIndex(zIndex) { 59 | this.options.zIndex = zIndex; 60 | return this; 61 | }, 62 | 63 | setOpacity(opacity) { 64 | if (this.glLayer) { 65 | const glMap = this.glLayer.getMaplibreMap(); 66 | if (glMap.getLayer('hillshading')) { 67 | glMap.setPaintProperty('hillshading', 'hillshade-exaggeration', opacity); 68 | } else { 69 | glMap.getCanvas().style.opacity = opacity; 70 | } 71 | } 72 | }, 73 | 74 | async _load() { 75 | await importPolyfill('./maplibre-gl.js'); 76 | await importPolyfill('./leaflet-maplibre-gl.js'); 77 | 78 | this._addGlLayer(); 79 | }, 80 | 81 | _addGlLayer() { 82 | this.glLayer = L.maplibreGL(this.options); 83 | // see LayersConfig.createLayer 84 | this.glLayer.getAttribution = function () { 85 | return this.options.mapLink; 86 | }; 87 | this._map.addLayer(this.glLayer); 88 | 89 | this._updateZIndex(); 90 | }, 91 | 92 | _updateZIndex() { 93 | if (this.glLayer && this.glLayer.getContainer() && this.options.zIndex != null) { 94 | this.glLayer.getContainer().style.zIndex = this.options.zIndex; 95 | } 96 | }, 97 | }); 98 | 99 | BR.maplibreGlLazyLoader = function (options) { 100 | return new BR.MaplibreGlLazyLoader(options); 101 | }; 102 | })(); 103 | -------------------------------------------------------------------------------- /js/util/Track.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Track loading commons 3 | */ 4 | BR.Track = { 5 | /** 6 | * Returns common options for styling and appearance of tracks 7 | * 8 | * @param {BR.ControlLayers} [layersControl] Layers control instance 9 | * @param {boolean} [filterPois=false] exclude points not of type from/via/to, set true when also calling `addPoiMarkers` 10 | * 11 | * @returns {Object} to pass as `options` parameter to `L.geoJson` 12 | */ 13 | getGeoJsonOptions(layersControl, filterPois = false) { 14 | // https://github.com/mapbox/simplestyle-spec/tree/master/1.1.0 15 | const styleMapping = [ 16 | ['stroke', 'color'], 17 | ['stroke-width', 'weight'], 18 | ['stroke-opacity', 'opacity'], 19 | ['fill', 'fillColor'], 20 | ['fill-opacity', 'fillOpacity'], 21 | ]; 22 | return { 23 | style(geoJsonFeature) { 24 | var currentLayerId = layersControl?.getActiveBaseLayer().layer.id; 25 | const featureStyle = { 26 | color: currentLayerId === 'cyclosm' ? 'yellow' : 'blue', 27 | weight: 4, 28 | }; 29 | for (const [simpleStyle, leafletStyle] of styleMapping) { 30 | if (geoJsonFeature?.properties?.[simpleStyle]) { 31 | featureStyle[leafletStyle] = geoJsonFeature.properties[simpleStyle]; 32 | } 33 | } 34 | return featureStyle; 35 | }, 36 | interactive: false, 37 | filter(geoJsonFeature) { 38 | if (filterPois) { 39 | // remove POIs, added separately, see `addPoiMarkers` 40 | return !BR.Track.isPoiPoint(geoJsonFeature); 41 | } 42 | return true; 43 | }, 44 | pointToLayer(geoJsonPoint, latlng) { 45 | // route waypoint (type=from/via/to) 46 | return L.marker(latlng, { 47 | interactive: false, 48 | opacity: 0.7, 49 | // prevent being on top of route markers 50 | zIndexOffset: -1000, 51 | }); 52 | }, 53 | pane: 'tracks', 54 | }; 55 | }, 56 | 57 | /** 58 | * Add Points in the passed `geoJson` as POI markers, except route waypoints (type=from|via|to) 59 | * 60 | * @param {BR.PoiMarkers} pois POI control instance 61 | * @param {Object} geoJson GeoJSON object 62 | */ 63 | addPoiMarkers(pois, geoJson) { 64 | turf.featureEach(geoJson, function (feature, idx) { 65 | if (BR.Track.isPoiPoint(feature)) { 66 | var coord = turf.getCoord(feature); 67 | var latlng = L.GeoJSON.coordsToLatLng(coord); 68 | var name = ''; 69 | if (feature.properties && feature.properties.name) name = feature.properties.name; 70 | pois.addMarker(latlng, name); 71 | } 72 | }); 73 | }, 74 | 75 | /** 76 | * Checks if the passed GeoJSON Point feature is a route waypoint. 77 | * 78 | * Route points are exported e.g. as GPX `wpt` with a `type=from|via|to` property 79 | * if the "waypoints" option is checked in the Export dialog. 80 | * 81 | * @param {Object} geoJsonPointFeature GeoJSON Point feature 82 | */ 83 | isRouteWaypoint(geoJsonPointFeature) { 84 | var props = geoJsonPointFeature.properties; 85 | if (props && props.type) { 86 | var wptType = props.type; 87 | if (wptType === 'from' || wptType === 'via' || wptType === 'to') { 88 | return true; 89 | } 90 | } 91 | return false; 92 | }, 93 | 94 | /** 95 | * Checks if the passed GeoJSON feature should be added as a POI 96 | * 97 | * @param {Object} geoJsonFeature GeoJSON feature 98 | */ 99 | isPoiPoint(geoJsonFeature) { 100 | return turf.getType(geoJsonFeature) === 'Point' && !BR.Track.isRouteWaypoint(geoJsonFeature); 101 | }, 102 | }; 103 | -------------------------------------------------------------------------------- /js/util/TrackEdges.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The track messages and track analysis panels share some functionality 3 | * which is defined in this class to prevent code duplication. 4 | * 5 | * @type {L.Class} 6 | */ 7 | BR.TrackEdges = L.Class.extend({ 8 | statics: { 9 | getFeature(featureMessage) { 10 | //["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"] 11 | return { 12 | cost: { 13 | perKm: parseInt(featureMessage[4]), 14 | elev: parseInt(featureMessage[5]), 15 | turn: parseInt(featureMessage[6]), 16 | node: parseInt(featureMessage[7]), 17 | initial: parseInt(featureMessage[8]), 18 | }, 19 | distance: parseInt(featureMessage[3]), 20 | wayTags: featureMessage[9], 21 | nodeTags: featureMessage[10], 22 | }; 23 | }, 24 | }, 25 | 26 | /** 27 | * List of indexes for the track array where 28 | * a segment with different features ends 29 | * 30 | * @type {number[]} 31 | * @see BR.TrackMessages 32 | */ 33 | edges: [], 34 | 35 | /** 36 | * @param {Array} segments 37 | */ 38 | initialize(segments) { 39 | this.edges = this.getTrackEdges(segments); 40 | }, 41 | 42 | /** 43 | * Find the indexes where a track segment ends, i.e. where the waytags change. 44 | * 45 | * Used in TrackMessages and TrackAnalysis for highlighting track segments. 46 | * 47 | * @param {Array} segments 48 | * 49 | * @return {number[]} 50 | */ 51 | getTrackEdges(segments) { 52 | var messages, 53 | segLatLngs, 54 | length, 55 | si, 56 | mi, 57 | latLng, 58 | i, 59 | segIndex, 60 | baseIndex = 0, 61 | edges = []; 62 | 63 | // track latLngs index for end node of edge 64 | for (si = 0; si < segments.length; si++) { 65 | messages = segments[si].feature.properties.messages; 66 | segLatLngs = segments[si].getLatLngs(); 67 | length = segLatLngs.length; 68 | segIndex = 0; 69 | 70 | for (mi = 1; mi < messages.length; mi++) { 71 | latLng = this.getMessageLatLng(messages[mi]); 72 | 73 | for (i = segIndex; i < length; i++) { 74 | if (latLng.equals(segLatLngs[i])) { 75 | break; 76 | } 77 | } 78 | if (i === length) { 79 | i = length - 1; 80 | } 81 | 82 | segIndex = i + 1; 83 | edges.push(baseIndex + i); 84 | } 85 | baseIndex += length; 86 | } 87 | 88 | return edges; 89 | }, 90 | 91 | getMessageLatLng(message) { 92 | var lon = message[0] / 1000000, 93 | lat = message[1] / 1000000; 94 | 95 | return L.latLng(lat, lon); 96 | }, 97 | }); 98 | -------------------------------------------------------------------------------- /keys.template.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // COPYING: Please get your own API keys from the sites listed below 3 | 4 | BR.keys = { 5 | // Bing maps, https://www.microsoft.com/maps/default.aspx 6 | bing: '', 7 | 8 | // DigitalGlobe, https://developer.digitalglobe.com/maps-api/#plans 9 | digitalGlobe: '', 10 | 11 | // Thunderforest, https://thunderforest.com/pricing/ 12 | thunderforest: '', 13 | 14 | // Strava API token in case you want to display Strava segments 15 | strava: '', 16 | 17 | // OpenMapSurfer (OpenRouteService API), https://openrouteservice.org/plans/ 18 | openrouteservice: '', 19 | 20 | // Mapillary, https://www.mapillary.com/dashboard/developers 21 | mapillary: ``, 22 | 23 | // IGN France (for Scan25 base layer), https://geoservices.ign.fr/actualites/2023-11-20-acces-donnesnonlibres-gpf 24 | ignf: '', 25 | }; 26 | })(); 27 | -------------------------------------------------------------------------------- /layers/collection/1010.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "OpenStreetMap.se", 5 | "maxZoom": 18, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © OpenStreetMap Sweden", 7 | "id": "1010", 8 | "subdomains": [ 9 | "a", 10 | "b", 11 | "c", 12 | "d", 13 | "e", 14 | "f" 15 | ], 16 | "url": "http://{s}.tile.openstreetmap.se/hydda/full/{z}/{x}/{y}.png", 17 | "dataSource": "LayersCollection" 18 | }, 19 | "type": "Feature" 20 | } -------------------------------------------------------------------------------- /layers/collection/1016.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "4UMaps", 5 | "maxZoom": 15, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © 4UMaps", 7 | "id": "1016", 8 | "url": "http://4umaps.eu/{z}/{x}/{y}.png", 9 | "dataSource": "LayersCollection" 10 | }, 11 | "type": "Feature" 12 | } -------------------------------------------------------------------------------- /layers/collection/1017.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Osmapa.pl", 5 | "maxZoom": 20, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © Osmapa.pl", 7 | "id": "1017", 8 | "threed": "true", 9 | "language": "pl", 10 | "url": "http://{s}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png", 11 | "dataSource": "LayersCollection" 12 | }, 13 | "type": "Feature" 14 | } -------------------------------------------------------------------------------- /layers/collection/1021.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Спутник", 5 | "maxZoom": 19, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © Спутник", 7 | "id": "1021", 8 | "threed": "true", 9 | "language": "ru", 10 | "url": "http://{s}.tiles.maps.sputnik.ru/{z}/{x}/{y}.png", 11 | "dataSource": "LayersCollection" 12 | }, 13 | "type": "Feature" 14 | } -------------------------------------------------------------------------------- /layers/collection/1023.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Космоснимки", 5 | "maxZoom": 18, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © ScanEx", 7 | "id": "1023", 8 | "subdomains": [ 9 | "a", 10 | "b", 11 | "c", 12 | "d" 13 | ], 14 | "language": "ru", 15 | "url": "http://{s}.tile.osm.kosmosnimki.ru/kosmo/{z}/{x}/{y}.png", 16 | "dataSource": "LayersCollection" 17 | }, 18 | "type": "Feature" 19 | } -------------------------------------------------------------------------------- /layers/collection/1059.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "ÖPNV Karte", 5 | "maxZoom": 18, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © ÖPNV Karte", 7 | "id": "1059", 8 | "url": "http://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png", 9 | "dataSource": "LayersCollection" 10 | }, 11 | "type": "Feature" 12 | } -------------------------------------------------------------------------------- /layers/collection/1061.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Thunderforest Outdoors", 5 | "maxZoom": 22, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © Andy Allan", 7 | "id": "1061", 8 | "url": "http://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png", 9 | "dataSource": "LayersCollection" 10 | }, 11 | "type": "Feature" 12 | } -------------------------------------------------------------------------------- /layers/collection/1065.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Hike & Bike", 5 | "maxZoom": 19, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © Colin Marquardt", 7 | "id": "1065", 8 | "url": "http://toolserver.org/tiles/hikebike/{z}/{x}/{y}.png", 9 | "dataSource": "LayersCollection" 10 | }, 11 | "type": "Feature" 12 | } -------------------------------------------------------------------------------- /layers/collection/1069.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Refuges.info hiking", 5 | "maxZoom": 18, 6 | "attribution": "Map data: © OpenStreetMap contributors, under ODbL | Tiles: © sly", 7 | "id": "1069", 8 | "old": "true", 9 | "url": "http://maps.refuges.info/hiking/{z}/{x}/{y}.png", 10 | "dataSource": "LayersCollection" 11 | }, 12 | "type": "Feature" 13 | } -------------------------------------------------------------------------------- /layers/collection/extract.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import fetch from 'node-fetch'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | import vm from 'vm'; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const outDir = __dirname; 11 | 12 | var includeList = [ 13 | "1016", // 4UMaps 14 | "1065", // Hike & Bike Map 15 | "1061", // Thunderforest Outdoors 16 | "1021", // kosmosnimki.ru 17 | "1017", // sputnik.ru 18 | "1023", // Osmapa.pl - Mapa OpenStreetMap Polska 19 | "1010", // OpenStreetMap.se (Hydda.Full) 20 | "1069", // MRI (maps.refuges.info), 21 | "1059" // ÖPNV Karte 22 | ]; 23 | 24 | function extract(constantsJs) { 25 | const getLayerDataByID = vm.runInNewContext(constantsJs + '; getLayerDataByID'); 26 | 27 | for (let i = 0; i < includeList.length; i++) { 28 | let id = includeList[i]; 29 | 30 | let layer = getLayerDataByID(id); 31 | if (!layer) { 32 | console.warn('Layer not found: ' + id); 33 | continue; 34 | } 35 | //console.log(`${layer.id}, ${layer.name}, ${layer.address}`); 36 | 37 | layer.url = layer.address; 38 | delete layer.address; 39 | 40 | let geoJson = { 41 | geometry: null, 42 | properties: layer, 43 | type: "Feature" 44 | }; 45 | geoJson.properties.dataSource = 'LayersCollection'; 46 | 47 | const outFileName = path.join(outDir, layer.id + '.geojson'); 48 | const data = JSON.stringify(geoJson, null, 2); 49 | fs.writeFileSync(outFileName, data); 50 | } 51 | } 52 | 53 | // https://github.com/Edward17/LayersCollection/blob/gh-pages/constants.js 54 | fetch('http://edward17.github.io/LayersCollection/constants.js') 55 | .then(res => res.text()) 56 | .then(text => extract(text)) 57 | .catch(err => console.error(err)); 58 | -------------------------------------------------------------------------------- /layers/config/config.js: -------------------------------------------------------------------------------- 1 | BR.confLayers = {}; 2 | 3 | BR.confLayers.defaultBaseLayers = [ 4 | 'standard', 5 | 'OpenTopoMap', 6 | 'Stamen.Terrain', 7 | 'Esri.WorldImagery' 8 | ]; 9 | 10 | // worldwide monolingual layers to add as default when browser language matches 11 | BR.confLayers.languageDefaultLayers = [ 12 | 'osm-mapnik-german_style', 13 | 'cyclosm', 14 | '1021' // sputnik.ru 15 | ]; 16 | 17 | BR.confLayers.defaultOverlays = [ 18 | 'terrarium-hillshading', 19 | 'Waymarked_Trails-Cycling', 20 | 'Waymarked_Trails-Hiking' 21 | ]; 22 | 23 | BR.confLayers.legacyNameToIdMap = { 24 | 'OpenStreetMap': 'standard', 25 | 'OpenStreetMap.de': 'osm-mapnik-german_style', 26 | 'OpenTopoMap': 'OpenTopoMap', 27 | 'Esri World Imagery': 'Esri.WorldImagery', 28 | 'Cycling (Waymarked Trails)': 'Waymarked_Trails-Cycling', 29 | 'Hiking (Waymarked Trails)': 'Waymarked_Trails-Hiking', 30 | 'HikeBike.HillShading': 'terrarium-hillshading', 31 | 'mapillary-coverage-raster': 'mapillary-coverage' 32 | }; 33 | 34 | BR.confLayers.leafletProvidersIncludeList = [ 35 | 'Stamen.Terrain', 36 | 'MtbMap', 37 | 'OpenStreetMap.CH', 38 | 'HikeBike.HillShading', 39 | 'Esri.WorldImagery' 40 | ]; 41 | -------------------------------------------------------------------------------- /layers/extra/historic-place-contours.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 5.45, 7 | 45 8 | ], 9 | [ 10 | 17, 11 | 45 12 | ], 13 | [ 14 | 17, 15 | 56.27 16 | ], 17 | [ 18 | 5.45, 19 | 56.27 20 | ], 21 | [ 22 | 5.45, 23 | 45 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "html": "Produced using Copernicus data and information funded by the European Union - EU-DEM layers, download from OpenDEM, Data sources SRTM, ASTER GDEM, Tiles from Historic.Place" 33 | }, 34 | "country_code": "DACH", 35 | "id": "historic-place-contours", 36 | "min_zoom": 12, 37 | "max_zoom": 15, 38 | "name": "Contours DACH", 39 | "overlay": true, 40 | "type": "tms", 41 | "url": "http://tiles.historic.place/ele/de/{zoom}/{x}/{y}.png", 42 | "valid-georeference": true 43 | }, 44 | "type": "Feature" 45 | } -------------------------------------------------------------------------------- /layers/extra/ignf-aerial.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "html": "© IGN" 6 | }, 7 | "id": "ignf-aerial", 8 | "max_zoom": 21, 9 | "name": "IGN France - Photographies aériennes", 10 | "type": "tms", 11 | "url": "https://data.geopf.fr/wmts?LAYER=ORTHOIMAGERY.ORTHOPHOTOS&EXCEPTIONS=text/xml&FORMAT=image/jpeg&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&STYLE=normal&TILEMATRIXSET=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" 12 | }, 13 | "type": "Feature" 14 | } 15 | 16 | -------------------------------------------------------------------------------- /layers/extra/ignf-map.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "html": "© IGN" 6 | }, 7 | "id": "ignf-map", 8 | "max_zoom": 21, 9 | "name": "IGN France - Plan IGN", 10 | "type": "tms", 11 | "url": "https://data.geopf.fr/wmts?LAYER=GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2&EXCEPTIONS=text/xml&FORMAT=image/png&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&STYLE=normal&TILEMATRIXSET=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" 12 | }, 13 | "type": "Feature" 14 | } 15 | 16 | -------------------------------------------------------------------------------- /layers/extra/ignf-scan25.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "html": "© IGN" 6 | }, 7 | "id": "ignf-scan25", 8 | "max_zoom": 21, 9 | "name": "IGN France - Carte topographique (Scan25)", 10 | "type": "tms", 11 | "url": "https://data.geopf.fr/private/wmts?LAYER=GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN25TOUR&EXCEPTIONS=text/xml&FORMAT=image/jpeg&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&STYLE=normal&apikey={keys_ignf}&TILEMATRIXSET=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" 12 | }, 13 | "type": "Feature" 14 | } 15 | 16 | -------------------------------------------------------------------------------- /layers/extra/openmapsurfer.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "html": "GIScience Research Group @ University of Heidelberg (info)" 6 | }, 7 | "id": "openmapsurfer", 8 | "max_zoom": 19, 9 | "name": "OpenMapSurfer", 10 | "type": "tms", 11 | "url": "https://api.openrouteservice.org/mapsurfer/{zoom}/{x}/{y}.png?api_key={keys_openrouteservice}" 12 | }, 13 | "type": "Feature" 14 | } 15 | -------------------------------------------------------------------------------- /layers/extra/osm-notes.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "text": "© OpenStreetMap contributors", 6 | "url": "https://www.openstreetmap.org/" 7 | }, 8 | "id": "osm-notes", 9 | "name": "OpenStreetMap Notes", 10 | "overlay": true, 11 | "dataSource": "OpenStreetMapNotesAPI" 12 | }, 13 | "type": "Feature" 14 | } 15 | -------------------------------------------------------------------------------- /layers/extra/swisstopo-aerial.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "html": "© Swisstopo" 6 | }, 7 | "id": "swisstopo-aerial", 8 | "max_zoom": 28, 9 | "name": "Swisstopo Aerial Photographs", 10 | "type": "tms", 11 | "url": "https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.swissimage/default/current/3857/{z}/{x}/{y}.jpeg", 12 | "layers": "web", 13 | "format": "image/jpeg" 14 | }, 15 | "type": "Feature" 16 | } 17 | 18 | -------------------------------------------------------------------------------- /layers/extra/swisstopo-landeskarte.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "html": "© Swisstopo" 6 | }, 7 | "id": "swisstopo-landeskarte", 8 | "max_zoom": 28, 9 | "name": "Swisstopo Landeskarte", 10 | "type": "tms", 11 | "url": "https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-farbe/default/current/3857/{z}/{x}/{y}.jpeg", 12 | "layers": "web", 13 | "format": "image/jpeg" 14 | }, 15 | "type": "Feature" 16 | } 17 | 18 | -------------------------------------------------------------------------------- /layers/extra/topplus-open.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 4.219, 7 | 46.317 8 | ], 9 | [ 10 | 16.875, 11 | 46.317 12 | ], 13 | [ 14 | 16.875, 15 | 55.776 16 | ], 17 | [ 18 | 4.219, 19 | 55.776 20 | ], 21 | [ 22 | 4.219, 23 | 46.317 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "html": "© Bundesamt für Kartographie und Geodäsie (2023), Datenquellen" 32 | }, 33 | "id": "topplus-open", 34 | "name": "TopPlusOpen", 35 | "type": "tms", 36 | "url": "https://sgx.geodatenzentrum.de/wmts_topplus_open/tile/1.0.0/web/default/WEBMERCATOR/{zoom}/{y}/{x}.png", 37 | "max_zoom": 19, 38 | "layers": "web", 39 | "format": "image/png" 40 | }, 41 | "type": "Feature" 42 | } 43 | -------------------------------------------------------------------------------- /layers/josm/Freemap.sk-Car.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 16.69, 7 | 47.64 8 | ], 9 | [ 10 | 22.67, 11 | 47.64 12 | ], 13 | [ 14 | 22.67, 15 | 49.65 16 | ], 17 | [ 18 | 16.69, 19 | 49.65 20 | ], 21 | [ 22 | 16.69, 23 | 47.64 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "text": "Map © Freemap Slovakia, data © OpenStreetMap contributors", 33 | "url": "https://www.freemap.sk/?layers=A" 34 | }, 35 | "category": "osmbasedmap", 36 | "country_code": "SK", 37 | "icon": "https://raw.githubusercontent.com/FreemapSlovakia/freemap-v3-react/master/src/images/freemap-logo-small.png", 38 | "id": "Freemap.sk-Car", 39 | "max_zoom": 16, 40 | "min_zoom": 8, 41 | "name": "Freemap.sk Car", 42 | "type": "tms", 43 | "url": "https://tile.freemap.sk/A/{zoom}/{x}/{y}.jpeg", 44 | "dataSource": "JOSM" 45 | }, 46 | "type": "Feature" 47 | } -------------------------------------------------------------------------------- /layers/josm/Freemap.sk-Cyclo.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 16.69, 7 | 47.64 8 | ], 9 | [ 10 | 22.67, 11 | 47.64 12 | ], 13 | [ 14 | 22.67, 15 | 49.65 16 | ], 17 | [ 18 | 16.69, 19 | 49.65 20 | ], 21 | [ 22 | 16.69, 23 | 47.64 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "text": "Map © Freemap Slovakia, data © OpenStreetMap contributors and SRTM", 33 | "url": "https://www.freemap.sk/?layers=C" 34 | }, 35 | "category": "osmbasedmap", 36 | "country_code": "SK", 37 | "icon": "https://raw.githubusercontent.com/FreemapSlovakia/freemap-v3-react/master/src/images/freemap-logo-small.png", 38 | "id": "Freemap.sk-Cyclo", 39 | "max_zoom": 16, 40 | "min_zoom": 8, 41 | "name": "Freemap.sk Bicycle", 42 | "type": "tms", 43 | "url": "https://tile.freemap.sk/C/{zoom}/{x}/{y}.jpeg", 44 | "dataSource": "JOSM" 45 | }, 46 | "type": "Feature" 47 | } -------------------------------------------------------------------------------- /layers/josm/Freemap.sk-Hiking.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 16.69, 7 | 47.64 8 | ], 9 | [ 10 | 22.67, 11 | 47.64 12 | ], 13 | [ 14 | 22.67, 15 | 49.65 16 | ], 17 | [ 18 | 16.69, 19 | 49.65 20 | ], 21 | [ 22 | 16.69, 23 | 47.64 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "text": "Map © Freemap Slovakia, data © OpenStreetMap contributors and SRTM", 33 | "url": "https://www.freemap.sk/?layers=T" 34 | }, 35 | "category": "osmbasedmap", 36 | "country_code": "SK", 37 | "icon": "https://raw.githubusercontent.com/FreemapSlovakia/freemap-v3-react/master/src/images/freemap-logo-small.png", 38 | "id": "Freemap.sk-Hiking", 39 | "max_zoom": 16, 40 | "min_zoom": 8, 41 | "name": "Freemap.sk Hiking", 42 | "type": "tms", 43 | "url": "https://tile.freemap.sk/T/{zoom}/{x}/{y}.jpeg", 44 | "dataSource": "JOSM" 45 | }, 46 | "type": "Feature" 47 | } -------------------------------------------------------------------------------- /layers/josm/Freemap.sk-Outdoor.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 23.642578, 7 | 50.625073 8 | ], 9 | [ 10 | 21.566162, 11 | 51.213766 12 | ], 13 | [ 14 | 14.348145, 15 | 51.213766 16 | ], 17 | [ 18 | 11.832275, 19 | 50.412018 20 | ], 21 | [ 22 | 11.840515, 23 | 47.802087 24 | ], 25 | [ 26 | 10.30242919922, 27 | 47.80024163283 28 | ], 29 | [ 30 | 8.60229492188, 31 | 47.94210682755 32 | ], 33 | [ 34 | 6.88293457031, 35 | 47.58764167942 36 | ], 37 | [ 38 | 5.5959892273, 39 | 46.29749238675 40 | ], 41 | [ 42 | 5.0039263916, 43 | 45.2122799013 44 | ], 45 | [ 46 | 5.00517393317, 47 | 43.33975593618 48 | ], 49 | [ 50 | 7.72338871875, 51 | 38.4277739576 52 | ], 53 | [ 54 | 11.29668914795, 55 | 37.26312626931 56 | ], 57 | [ 58 | 11.56859802246, 59 | 36.46547630448 60 | ], 61 | [ 62 | 15.20506713867, 63 | 36.48314944975 64 | ], 65 | [ 66 | 20.17639160156, 67 | 39.31092541213 68 | ], 69 | [ 70 | 21.31896972657, 71 | 40.69729900864 72 | ], 73 | [ 74 | 23.642578125, 75 | 41.1621139394 76 | ], 77 | [ 78 | 26.125488, 79 | 41.162114 80 | ], 81 | [ 82 | 28.201904, 83 | 41.869561 84 | ], 85 | [ 86 | 29.94873, 87 | 45.243953 88 | ], 89 | [ 90 | 26.993408, 91 | 48.356249 92 | ], 93 | [ 94 | 23.642578, 95 | 50.625073 96 | ] 97 | ] 98 | ], 99 | "type": "Polygon" 100 | }, 101 | "properties": { 102 | "attribution": { 103 | "required": true, 104 | "text": "Map © Freemap Slovakia, data © OpenStreetMap contributors and SRTM", 105 | "url": "https://www.freemap.sk/?layers=X" 106 | }, 107 | "category": "osmbasedmap", 108 | "country_code": "SK", 109 | "icon": "https://raw.githubusercontent.com/FreemapSlovakia/freemap-v3-react/master/src/images/freemap-logo-small.png", 110 | "id": "Freemap.sk-Outdoor", 111 | "max_zoom": 19, 112 | "min_zoom": 6, 113 | "name": "Freemap.sk Outdoor", 114 | "type": "tms", 115 | "url": "https://outdoor.tiles.freemap.sk/{zoom}/{x}/{y}", 116 | "dataSource": "JOSM" 117 | }, 118 | "type": "Feature" 119 | } -------------------------------------------------------------------------------- /layers/josm/HDM_HOT.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© OpenStreetMap contributors, tiles courtesy of Humanitarian OpenStreetMap Team", 7 | "url": "https://www.hotosm.org/" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "https://wiki.openstreetmap.org/w/images/thumb/c/c9/Hot_logo.svg/300px-Hot_logo.svg.png", 11 | "id": "HDM_HOT", 12 | "max_zoom": 20, 13 | "name": "HDM (Humanitarian OpenStreetMap Team)", 14 | "type": "tms", 15 | "url": "https://{switch:a,b,c}.tile.openstreetmap.fr/hot/{zoom}/{x}/{y}.png", 16 | "valid-georeference": true, 17 | "dataSource": "JOSM" 18 | }, 19 | "type": "Feature" 20 | } -------------------------------------------------------------------------------- /layers/josm/Israel_Hiking.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 34.64563, 7 | 32.92073 8 | ], 9 | [ 10 | 34.98374, 11 | 33.13352 12 | ], 13 | [ 14 | 35.15662, 15 | 33.09994 16 | ], 17 | [ 18 | 35.31781, 19 | 33.11463 20 | ], 21 | [ 22 | 35.36541, 23 | 33.06285 24 | ], 25 | [ 26 | 35.46229, 27 | 33.09994 28 | ], 29 | [ 30 | 35.51741, 31 | 33.12652 32 | ], 33 | [ 34 | 35.5266, 35 | 33.21531 36 | ], 37 | [ 38 | 35.53893, 39 | 33.25442 40 | ], 41 | [ 42 | 35.56446, 43 | 33.2969 44 | ], 45 | [ 46 | 35.61264, 47 | 33.27918 48 | ], 49 | [ 50 | 35.67429, 51 | 33.30627 52 | ], 53 | [ 54 | 35.70785, 55 | 33.34269 56 | ], 57 | [ 58 | 35.75363, 59 | 33.35091 60 | ], 61 | [ 62 | 35.81509, 63 | 33.3392 64 | ], 65 | [ 66 | 35.91531, 67 | 32.9406 68 | ], 69 | [ 70 | 35.80834, 71 | 32.772 72 | ], 73 | [ 74 | 35.77835, 75 | 32.72446 76 | ], 77 | [ 78 | 35.59491, 79 | 32.62828 80 | ], 81 | [ 82 | 35.5729, 83 | 32.36541 84 | ], 85 | [ 86 | 35.59461, 87 | 32.21856 88 | ], 89 | [ 90 | 35.55452, 91 | 32.02901 92 | ], 93 | [ 94 | 35.57225, 95 | 31.75415 96 | ], 97 | [ 98 | 35.48771, 99 | 31.41951 100 | ], 101 | [ 102 | 35.4209, 103 | 31.25116 104 | ], 105 | [ 106 | 35.47936, 107 | 31.1783 108 | ], 109 | [ 110 | 35.42771, 111 | 30.95172 112 | ], 113 | [ 114 | 35.3321, 115 | 30.77107 116 | ], 117 | [ 118 | 35.20709, 119 | 30.53307 120 | ], 121 | [ 122 | 35.17202, 123 | 30.11204 124 | ], 125 | [ 126 | 35.07514, 127 | 29.83713 128 | ], 129 | [ 130 | 35.02336, 131 | 29.64569 132 | ], 133 | [ 134 | 34.93992, 135 | 29.39946 136 | ], 137 | [ 138 | 34.89517, 139 | 29.37711 140 | ], 141 | [ 142 | 34.84785, 143 | 29.59084 144 | ], 145 | [ 146 | 34.69667, 147 | 30.10714 148 | ], 149 | [ 150 | 34.52423, 151 | 30.40912 152 | ], 153 | [ 154 | 34.48879, 155 | 30.64515 156 | ], 157 | [ 158 | 34.07929, 159 | 31.52265 160 | ], 161 | [ 162 | 34.64563, 163 | 32.92073 164 | ] 165 | ] 166 | ], 167 | "type": "Polygon" 168 | }, 169 | "properties": { 170 | "attribution": { 171 | "required": true, 172 | "text": "Tiles © IsraelHiking, CC BY-SA-NC 3.0. Data by OpenStreetMap under ODbL.", 173 | "url": "https://israelhiking.osm.org.il/" 174 | }, 175 | "category": "osmbasedmap", 176 | "country_code": "IL", 177 | "description": "Israel Hiking map", 178 | "icon": "https://israelhiking.osm.org.il/content/favicons/favicon.ico", 179 | "id": "Israel_Hiking", 180 | "max_zoom": 16, 181 | "min_zoom": 7, 182 | "name": "Israel Hiking", 183 | "privacy_policy_url": "https://github.com/IsraelHikingMap/Site/wiki/Privacy-Policy-and-Terms-of-Service", 184 | "type": "tms", 185 | "url": "https://israelhiking.osm.org.il/Tiles/{zoom}/{x}/{y}.png", 186 | "dataSource": "JOSM" 187 | }, 188 | "type": "Feature" 189 | } -------------------------------------------------------------------------------- /layers/josm/Israel_MTB.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 34.64563, 7 | 32.92073 8 | ], 9 | [ 10 | 34.98374, 11 | 33.13352 12 | ], 13 | [ 14 | 35.15662, 15 | 33.09994 16 | ], 17 | [ 18 | 35.31781, 19 | 33.11463 20 | ], 21 | [ 22 | 35.36541, 23 | 33.06285 24 | ], 25 | [ 26 | 35.46229, 27 | 33.09994 28 | ], 29 | [ 30 | 35.51741, 31 | 33.12652 32 | ], 33 | [ 34 | 35.5266, 35 | 33.21531 36 | ], 37 | [ 38 | 35.53893, 39 | 33.25442 40 | ], 41 | [ 42 | 35.56446, 43 | 33.2969 44 | ], 45 | [ 46 | 35.61264, 47 | 33.27918 48 | ], 49 | [ 50 | 35.67429, 51 | 33.30627 52 | ], 53 | [ 54 | 35.70785, 55 | 33.34269 56 | ], 57 | [ 58 | 35.75363, 59 | 33.35091 60 | ], 61 | [ 62 | 35.81509, 63 | 33.3392 64 | ], 65 | [ 66 | 35.91531, 67 | 32.9406 68 | ], 69 | [ 70 | 35.80834, 71 | 32.772 72 | ], 73 | [ 74 | 35.77835, 75 | 32.72446 76 | ], 77 | [ 78 | 35.59491, 79 | 32.62828 80 | ], 81 | [ 82 | 35.5729, 83 | 32.36541 84 | ], 85 | [ 86 | 35.59461, 87 | 32.21856 88 | ], 89 | [ 90 | 35.55452, 91 | 32.02901 92 | ], 93 | [ 94 | 35.57225, 95 | 31.75415 96 | ], 97 | [ 98 | 35.48771, 99 | 31.41951 100 | ], 101 | [ 102 | 35.4209, 103 | 31.25116 104 | ], 105 | [ 106 | 35.47936, 107 | 31.1783 108 | ], 109 | [ 110 | 35.42771, 111 | 30.95172 112 | ], 113 | [ 114 | 35.3321, 115 | 30.77107 116 | ], 117 | [ 118 | 35.20709, 119 | 30.53307 120 | ], 121 | [ 122 | 35.17202, 123 | 30.11204 124 | ], 125 | [ 126 | 35.07514, 127 | 29.83713 128 | ], 129 | [ 130 | 35.02336, 131 | 29.64569 132 | ], 133 | [ 134 | 34.93992, 135 | 29.39946 136 | ], 137 | [ 138 | 34.89517, 139 | 29.37711 140 | ], 141 | [ 142 | 34.84785, 143 | 29.59084 144 | ], 145 | [ 146 | 34.69667, 147 | 30.10714 148 | ], 149 | [ 150 | 34.52423, 151 | 30.40912 152 | ], 153 | [ 154 | 34.48879, 155 | 30.64515 156 | ], 157 | [ 158 | 34.07929, 159 | 31.52265 160 | ], 161 | [ 162 | 34.64563, 163 | 32.92073 164 | ] 165 | ] 166 | ], 167 | "type": "Polygon" 168 | }, 169 | "properties": { 170 | "attribution": { 171 | "required": true, 172 | "text": "Tiles © IsraelHiking, CC BY-SA-NC 3.0. Data by OpenStreetMap under ODbL.", 173 | "url": "https://israelhiking.osm.org.il/" 174 | }, 175 | "category": "osmbasedmap", 176 | "country_code": "IL", 177 | "description": "Israel MTB map", 178 | "icon": "https://israelhiking.osm.org.il/content/favicons/favicon.ico", 179 | "id": "Israel_MTB", 180 | "max_zoom": 16, 181 | "min_zoom": 7, 182 | "name": "Israel MTB", 183 | "privacy_policy_url": "https://github.com/IsraelHikingMap/Site/wiki/Privacy-Policy-and-Terms-of-Service", 184 | "type": "tms", 185 | "url": "https://israelhiking.osm.org.il/MTBTiles/{zoom}/{x}/{y}.png", 186 | "dataSource": "JOSM" 187 | }, 188 | "type": "Feature" 189 | } -------------------------------------------------------------------------------- /layers/josm/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | JOSM imagery database is licensed under Creative Commons (CC-BY-SA). 3 | Content since April 2014 is dual licensed with LGPL (except contents copied from ELI, which is only CC-BY-SA). 4 | 5 | https://creativecommons.org/licenses/by-sa/3.0/ 6 | 7 | Source: https://josm.openstreetmap.de/wiki/Maps#Otherimportantinformation 8 | -------------------------------------------------------------------------------- /layers/josm/Waymarked_Trails-Cycling.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© waymarkedtrails.org, OpenStreetMap contributors, CC by-SA 3.0", 7 | "url": "https://cycling.waymarkedtrails.org/help/legal" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "https://static.waymarkedtrails.org/img/map_cycling.png", 11 | "id": "Waymarked_Trails-Cycling", 12 | "max_zoom": 18, 13 | "name": "Waymarked Trails: Cycling", 14 | "overlay": true, 15 | "type": "tms", 16 | "url": "https://tile.waymarkedtrails.org/cycling/{zoom}/{x}/{y}.png", 17 | "valid-georeference": true, 18 | "dataSource": "JOSM" 19 | }, 20 | "type": "Feature" 21 | } -------------------------------------------------------------------------------- /layers/josm/Waymarked_Trails-Hiking.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© waymarkedtrails.org, OpenStreetMap contributors, CC by-SA 3.0", 7 | "url": "https://hiking.waymarkedtrails.org/help/legal" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "https://static.waymarkedtrails.org/img/map_hiking.png", 11 | "id": "Waymarked_Trails-Hiking", 12 | "max_zoom": 18, 13 | "name": "Waymarked Trails: Hiking", 14 | "overlay": true, 15 | "type": "tms", 16 | "url": "https://tile.waymarkedtrails.org/hiking/{zoom}/{x}/{y}.png", 17 | "valid-georeference": true, 18 | "dataSource": "JOSM" 19 | }, 20 | "type": "Feature" 21 | } -------------------------------------------------------------------------------- /layers/josm/Waymarked_Trails-MTB.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© waymarkedtrails.org, OpenStreetMap contributors, CC by-SA 3.0", 7 | "url": "https://mtb.waymarkedtrails.org/help/legal" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "https://static.waymarkedtrails.org/img/map_mtb.png", 11 | "id": "Waymarked_Trails-MTB", 12 | "max_zoom": 18, 13 | "name": "Waymarked Trails: MTB", 14 | "overlay": true, 15 | "type": "tms", 16 | "url": "https://tile.waymarkedtrails.org/mtb/{zoom}/{x}/{y}.png", 17 | "valid-georeference": true, 18 | "dataSource": "JOSM" 19 | }, 20 | "type": "Feature" 21 | } -------------------------------------------------------------------------------- /layers/josm/cyclosm.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "Tiles © CyclOSM, Openstreetmap France, data © OpenStreetMap contributors, ODBL", 7 | "url": "https://www.openstreetmap.org/" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAUdEVYdFRpdGxlAENvbWJpbmVkIFNoYXBlc3SJBQAAAvdJREFUSIntk1tI03EUx7/n99/mls3StJXZPaOyueyhfOgyL/lgdKGbUBRFYumyKCIiiKT7SxRY6oPZxaRSiKAiCVsWhSzQzBGpLalIMl1G6tqm//1OLxk+NIvwIaLP049zzu98OL8L8J9/HgqWMCdnnwYhSWE+U28vutgft6Tbpktf4AiRaPuqU/a57ub7f1tgWZKTJiVWeXu+7HM5yrosyds3ssAGBr0csGGmZHkFRO8FU1rD/cIDvyWwpOQUMaQVTK0s2E2gTmKES3AEiLb31wnGShASTSPUSg3xhGaP4cRgU4j+hSSEM0Q5EdoBImbWM6NbgDqlSu3OqsIWGZBGnZaXTxndmzouvK8gaqS6N8P86WPxqYxtgwrmpGYvJZZuATb7fP4sZ1XhOgJFAjQZpClXFN6ZsGRHtEYR+WnmLnOvSq3P3hlKLhVfNL7+YChxNOnPFBxfuzyooL6q8A5ApvCAaW3Tk5JuS6ptHUvUMeiwlGoCgIVSqqWZSZ/tjR/0gTvlxWYpMW6SdXPItdLzez55RH2oHrmDHhEIanV1njrXmjONZaB4RqT7sNN+7hEICxgULVk8jzT6xwuF275fXoNRMdwCAJ1C9jcd2thBBcyks1rzNHXVBS5tqBxTUVERiE+yzQfBQcT39CLkLEPTyCqNBoDnC0yHugPeZQAgA1gcM0p9+zPBj1c0OzknjQibvDplq+tuvj/emhvDinrB29O12uUo6wKAXbsyTaFa34u3bo2zxeNJr4mL829+9+aYRsu7Eyb61tsOXL8RVAAA8Sm5Cwmq7XvmKwve33CvqH1gzam8jMQenyhr6QiJFgIyYrjsmx3jrZxq8k962Dhiy8GjpS8H1gf9yb9iT26GJSxMGG9VGh1TFoVqsue0XIuJUKffeBq2Zv+Rqy/+tG9QlmVlDXtwecXtV7fTm04e3GDujytDJWiure3rjFp8My7Km5IY68keO3Ge/f5jZ8eQCQZKxhp6Z7V7QmoePGpwD2X///ylfAN0qT1vK+TL4gAAAABJRU5ErkJggg==", 11 | "id": "cyclosm", 12 | "max_zoom": 20, 13 | "mod-tile-features": true, 14 | "name": "CyclOSM", 15 | "type": "tms", 16 | "url": "https://{switch:a,b,c}.tile-cyclosm.openstreetmap.fr/cyclosm/{zoom}/{x}/{y}.png", 17 | "valid-georeference": true, 18 | "dataSource": "JOSM" 19 | }, 20 | "type": "Feature" 21 | } -------------------------------------------------------------------------------- /layers/josm/extract.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import fetch from 'node-fetch'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename); 8 | const outDir = __dirname; 9 | 10 | const includeList = [ 11 | "cyclosm", 12 | "osmbe", 13 | "osmbe-fr", 14 | "osmbe-nl", 15 | "osmfr-basque", 16 | "osmfr-breton", 17 | "osmfr-occitan", 18 | "OpenStreetMap-turistautak", 19 | "hu-hillshade", 20 | "Israel_Hiking", 21 | "Israel_MTB", 22 | "mtbmap-no", 23 | "Freemap.sk-Outdoor", 24 | "Freemap.sk-Car", 25 | "Freemap.sk-Hiking", 26 | "Freemap.sk-Cyclo", 27 | "opencylemap", 28 | "standard", 29 | "HDM_HOT", 30 | "osmfr", 31 | "osm-mapnik-german_style", 32 | "OpenTopoMap", 33 | "osm-cambodia_laos_thailand_vietnam-bilingual", 34 | "Waymarked_Trails-Hiking", 35 | "Waymarked_Trails-Cycling", 36 | "Waymarked_Trails-MTB", 37 | "wikimedia-map", 38 | "openpt_map", 39 | "openrailwaymap" 40 | ]; 41 | 42 | function extract(layersJosm) { 43 | for (let i = 0; i < layersJosm.features.length; i++) { 44 | let layer = layersJosm.features[i]; 45 | let props = layer.properties; 46 | let id = props.id; 47 | if (includeList.includes(id)) { 48 | //console.log(`${id}, ${props.name}, ${props.url}`); 49 | 50 | props.dataSource = 'JOSM'; 51 | 52 | const outFileName = path.join(outDir, id + '.geojson'); 53 | const data = JSON.stringify(layer, null, 2); 54 | fs.writeFileSync(outFileName, data); 55 | 56 | includeList.splice(includeList.indexOf(id), 1); 57 | } 58 | } 59 | 60 | if (includeList.length > 0) { 61 | console.warn('Layers not found: ', includeList); 62 | } 63 | } 64 | 65 | fetch('https://josm.openstreetmap.de/maps?format=geojson') 66 | .then(res => res.json()) 67 | .then(json => extract(json)) 68 | .catch(err => console.error(err)); 69 | -------------------------------------------------------------------------------- /layers/josm/hu-hillshade.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 15, 7 | 45 8 | ], 9 | [ 10 | 24, 11 | 45 12 | ], 13 | [ 14 | 24, 15 | 49 16 | ], 17 | [ 18 | 15, 19 | 49 20 | ], 21 | [ 22 | 15, 23 | 45 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "text": "SRTM" 33 | }, 34 | "category": "elevation", 35 | "country_code": "HU", 36 | "id": "hu-hillshade", 37 | "max_zoom": 18, 38 | "min_zoom": 0, 39 | "name": "Hillshade Hungary", 40 | "overlay": true, 41 | "type": "tms", 42 | "url": "https://{switch:a,b,c}.map.turistautak.hu/tiles/shading/{zoom}/{x}/{y}.png", 43 | "valid-georeference": true, 44 | "dataSource": "JOSM" 45 | }, 46 | "type": "Feature" 47 | } -------------------------------------------------------------------------------- /layers/josm/mtbmap-no.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 31.904253, 7 | 70.4368136 8 | ], 9 | [ 10 | 28.4765186, 11 | 71.3289643 12 | ], 13 | [ 14 | 23.6865015, 15 | 71.2514263 16 | ], 17 | [ 18 | 16.8090601, 19 | 70.0730823 20 | ], 21 | [ 22 | 11.1620655, 23 | 67.5253903 24 | ], 25 | [ 26 | 9.975542, 27 | 64.811576 28 | ], 29 | [ 30 | 4.2187061, 31 | 62.1449966 32 | ], 33 | [ 34 | 4.3725367, 35 | 59.1871966 36 | ], 37 | [ 38 | 6.1743055, 39 | 57.8915032 40 | ], 41 | [ 42 | 7.932118, 43 | 57.7393554 44 | ], 45 | [ 46 | 10.777577, 47 | 58.8649103 48 | ], 49 | [ 50 | 11.7224012, 51 | 58.762509 52 | ], 53 | [ 54 | 12.722157, 55 | 60.1141506 56 | ], 57 | [ 58 | 13.0517469, 59 | 61.3493518 60 | ], 61 | [ 62 | 12.5243921, 63 | 63.6169922 64 | ], 65 | [ 66 | 14.2382593, 67 | 63.9856094 68 | ], 69 | [ 70 | 15.1171656, 71 | 65.9016624 72 | ], 73 | [ 74 | 18.6987085, 75 | 68.3749083 76 | ], 77 | [ 78 | 20.0610132, 79 | 68.2612583 80 | ], 81 | [ 82 | 21.0058375, 83 | 68.7841518 84 | ], 85 | [ 86 | 25.2465601, 87 | 68.3506025 88 | ], 89 | [ 90 | 26.9384546, 91 | 69.8472011 92 | ], 93 | [ 94 | 28.7621851, 95 | 69.6112133 96 | ], 97 | [ 98 | 28.5864039, 99 | 68.8556004 100 | ], 101 | [ 102 | 31.069314, 103 | 69.5191547 104 | ], 105 | [ 106 | 31.904253, 107 | 70.4368136 108 | ] 109 | ] 110 | ], 111 | "type": "Polygon" 112 | }, 113 | "properties": { 114 | "attribution": { 115 | "required": true, 116 | "text": "© MTBmap.no", 117 | "url": "https://www.mtbmap.no/" 118 | }, 119 | "category": "osmbasedmap", 120 | "country_code": "NO", 121 | "description": "Norwegian mountain biking map from OSM (max zoom 14-16, varies per region)", 122 | "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAyVBMVEUAAAAAAAAAAAAAAABHcEwAAAAAAAAAAAAAAAAAAAAAAAAGBgYAAAAAAAD///8DAwMKCgrX19d3d3fe3t4fHx/4+PikpKQGBgbb29vIyMizs7MlJSXExMQQEBA2NjYNDQ2pqam6urr+/v4UFBS8vLzg4OCMjIxvb297e3vLy8taWlqXl5fs7Oz7+/vn5+c7OzvQ0NDT09MxMTFFRUWTk5O/v7/k5OReXl6urq5iYmJLS0uGhoZOTk7y8vJpaWmcnJyDg4N/f3+bm5thW7HyAAAADXRSTlM9D3VaADJrd4BEKIEG/oGXoQAAAP1JREFUKM9tkIdywyAMhmmb0WVhjLcd2/HK3jvdff+HKjQ1kNr/HYdOH5J+hG463QbdtdCtJtXDImyje5nP5oaIkQLiKcwCxzH+A30PAAXpg30NdPJmMcLPWAXxINLwMAUuy7yewfT6YtgWJJ6ugmPoXDxH2BxiCTKAd1GJzQo4+bS/7ClN/wAeARB1WAV8loelXwczKHAZ+rgGtim4Zq1VSSkZr+Ar21Ga6xJsw1hbz9fwSdj6DCJBwR+d2O5CnhgtKvBE+R2sUv+DB/ZAVFA+dTLZRC7/kBsIcN6fFt8u65d4m7OXKK6CY1j+mjns8oO029Ea9Yie0UOD2q0frM8mZOHCEDcAAAAASUVORK5CYII=", 123 | "id": "mtbmap-no", 124 | "max_zoom": 14, 125 | "min_zoom": 3, 126 | "name": "MTBmap.no", 127 | "tile-size": "512", 128 | "tile_size": 512, 129 | "type": "tms", 130 | "url": "https://mtbmap.no/tiles/osm/mtbmap/{zoom}/{x}/{y}.jpg", 131 | "valid-georeference": true, 132 | "dataSource": "JOSM" 133 | }, 134 | "type": "Feature" 135 | } -------------------------------------------------------------------------------- /layers/josm/opencylemap.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "terms-of-use-text": "Maps © Thunderforest", 7 | "terms-of-use-url": "https://www.thunderforest.com/terms/", 8 | "text": "Data © OpenStreetMap contributors", 9 | "url": "https://www.openstreetmap.org/copyright/" 10 | }, 11 | "category": "osmbasedmap", 12 | "icon": "data:image/png;base64,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", 13 | "id": "opencylemap", 14 | "max_zoom": 22, 15 | "name": "OpenCycleMap", 16 | "privacy_policy_url": "https://www.thunderforest.com/privacy", 17 | "type": "tms", 18 | "url": "https://{switch:a,b,c}.tile.thunderforest.com/cycle/{zoom}/{x}/{y}.png?apikey={apikey}", 19 | "valid-georeference": true, 20 | "dataSource": "JOSM" 21 | }, 22 | "type": "Feature" 23 | } -------------------------------------------------------------------------------- /layers/josm/openpt_map.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© OpenStreetMap contributors, CC-BY-SA", 7 | "url": "http://openptmap.de/" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "http://openptmap.de/favicon_pt.png", 11 | "id": "openpt_map", 12 | "max_zoom": 17, 13 | "min_zoom": 4, 14 | "name": "OpenPT Map (overlay)", 15 | "overlay": true, 16 | "type": "tms", 17 | "url": "http://openptmap.de/tiles/{zoom}/{x}/{y}.png", 18 | "valid-georeference": true, 19 | "dataSource": "JOSM" 20 | }, 21 | "type": "Feature" 22 | } -------------------------------------------------------------------------------- /layers/josm/openrailwaymap.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "Rendering: OpenRailwayMap, © Map data OpenStreetMap contributors", 7 | "url": "https://www.openrailwaymap.org/" 8 | }, 9 | "category": "osmbasedmap", 10 | "description": "Overlay imagery showing railway infrastructure based on OpenStreetMap data", 11 | "icon": "https://www.openrailwaymap.org/img/openrailwaymap-64.png", 12 | "id": "openrailwaymap", 13 | "max_zoom": 20, 14 | "name": "OpenRailwayMap - Default", 15 | "overlay": true, 16 | "privacy_policy_url": "https://www.openrailwaymap.org/en/imprint", 17 | "type": "tms", 18 | "url": "https://{switch:a,b,c}.tiles.openrailwaymap.org/standard/{zoom}/{x}/{y}.png", 19 | "valid-georeference": true, 20 | "dataSource": "JOSM" 21 | }, 22 | "type": "Feature" 23 | } -------------------------------------------------------------------------------- /layers/josm/osm-cambodia_laos_thailand_vietnam-bilingual.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 92.1023798, 7 | 20.8135629 8 | ], 9 | [ 10 | 93.5690546, 11 | 24.0975527 12 | ], 13 | [ 14 | 94.1733026, 15 | 23.9269484 16 | ], 17 | [ 18 | 95.1950312, 19 | 26.707274 20 | ], 21 | [ 22 | 96.7550898, 23 | 27.5286657 24 | ], 25 | [ 26 | 97.5845575, 27 | 28.5805966 28 | ], 29 | [ 30 | 98.738122, 31 | 27.514051 32 | ], 33 | [ 34 | 98.7436151, 35 | 25.8799151 36 | ], 37 | [ 38 | 97.6779413, 39 | 24.7577376 40 | ], 41 | [ 42 | 97.9635858, 43 | 24.042382 44 | ], 45 | [ 46 | 98.8205194, 47 | 24.1627239 48 | ], 49 | [ 50 | 99.5236444, 51 | 22.9593356 52 | ], 53 | [ 54 | 100.3695917, 55 | 21.5051376 56 | ], 57 | [ 58 | 101.7923212, 59 | 22.4830518 60 | ], 61 | [ 62 | 105.3628778, 63 | 23.3331079 64 | ], 65 | [ 66 | 106.8185663, 67 | 22.8480137 68 | ], 69 | [ 70 | 108.1973505, 71 | 21.3619661 72 | ], 73 | [ 74 | 107.4389505, 75 | 18.8539792 76 | ], 77 | [ 78 | 117.1453714, 79 | 7.4656173 80 | ], 81 | [ 82 | 119.6172953, 83 | 5.2875389 84 | ], 85 | [ 86 | 118.1231546, 87 | 4.0502277 88 | ], 89 | [ 90 | 117.2552347, 91 | 4.3624942 92 | ], 93 | [ 94 | 115.8654642, 95 | 4.3460623 96 | ], 97 | [ 98 | 115.5084085, 99 | 3.0249771 100 | ], 101 | [ 102 | 114.552598, 103 | 1.5100953 104 | ], 105 | [ 106 | 113.5418558, 107 | 1.2574836 108 | ], 109 | [ 110 | 112.9650736, 111 | 1.5704982 112 | ], 113 | [ 114 | 112.2454691, 115 | 1.5100953 116 | ], 117 | [ 118 | 111.67418, 119 | 1.0158321 120 | ], 121 | [ 122 | 110.4546976, 123 | 0.9004918 124 | ], 125 | [ 126 | 109.4988871, 127 | 1.9218969 128 | ], 129 | [ 130 | 103.2256937, 131 | 1.1256762 132 | ], 133 | [ 134 | 100.4626322, 135 | 3.2388904 136 | ], 137 | [ 138 | 97.6721048, 139 | 8.0588831 140 | ], 141 | [ 142 | 93.892808, 143 | 15.9398659 144 | ], 145 | [ 146 | 92.1023798, 147 | 20.8135629 148 | ] 149 | ] 150 | ], 151 | "type": "Polygon" 152 | }, 153 | "properties": { 154 | "attribution": { 155 | "required": true, 156 | "text": "© osm-tools.org & OpenStreetMap contributors, CC-BY-SA", 157 | "url": "https://www.osm-tools.org/" 158 | }, 159 | "category": "osmbasedmap", 160 | "id": "osm-cambodia_laos_thailand_vietnam-bilingual", 161 | "max_zoom": 20, 162 | "name": "Cambodia, Laos, Thailand, Vietnam, Malaysia, Myanmar bilingual", 163 | "privacy_policy_url": "https://www.osm-tools.org", 164 | "type": "tms", 165 | "url": "https://{switch:a,b,c,d}.tile.osm-tools.org/osm/{zoom}/{x}/{y}.png", 166 | "valid-georeference": true, 167 | "dataSource": "JOSM" 168 | }, 169 | "type": "Feature" 170 | } -------------------------------------------------------------------------------- /layers/josm/osm-mapnik-german_style.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© OpenStreetMap contributors, CC-BY-SA", 7 | "url": "https://www.openstreetmap.org/" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "data:image/png;base64,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", 11 | "id": "osm-mapnik-german_style", 12 | "max_zoom": 19, 13 | "mod-tile-features": true, 14 | "name": "OpenStreetMap (German Style)", 15 | "type": "tms", 16 | "url": "https://tile.openstreetmap.de/{zoom}/{x}/{y}.png", 17 | "valid-georeference": true, 18 | "dataSource": "JOSM" 19 | }, 20 | "type": "Feature" 21 | } -------------------------------------------------------------------------------- /layers/josm/osmfr-basque.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 16.2075922, 7 | 62.7408449 8 | ], 9 | [ 10 | 16.2900797, 11 | 33.8453727 12 | ], 13 | [ 14 | -10.7364447, 15 | 33.8043768 16 | ], 17 | [ 18 | -10.8189321, 19 | 62.7182339 20 | ], 21 | [ 22 | 16.2075922, 23 | 62.7408449 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "text": "Tiles © OpenStreetMap France, data © OpenStreetMap contributors, ODbL", 33 | "url": "https://www.openstreetmap.org/" 34 | }, 35 | "category": "osmbasedmap", 36 | "icon": "data:image/png;base64,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", 37 | "id": "osmfr-basque", 38 | "max_zoom": 20, 39 | "mod-tile-features": true, 40 | "name": "OpenStreetMap (Basque Style)", 41 | "type": "tms", 42 | "url": "https://tile.openstreetmap.bzh/eu/{zoom}/{x}/{y}.png", 43 | "valid-georeference": true, 44 | "dataSource": "JOSM" 45 | }, 46 | "type": "Feature" 47 | } -------------------------------------------------------------------------------- /layers/josm/osmfr-breton.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 16.2075922, 7 | 62.7408449 8 | ], 9 | [ 10 | 16.2900797, 11 | 33.8453727 12 | ], 13 | [ 14 | -10.7364447, 15 | 33.8043768 16 | ], 17 | [ 18 | -10.8189321, 19 | 62.7182339 20 | ], 21 | [ 22 | 16.2075922, 23 | 62.7408449 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "text": "Tiles © OpenStreetMap France, data © OpenStreetMap contributors, ODbL", 33 | "url": "https://www.openstreetmap.org/" 34 | }, 35 | "category": "osmbasedmap", 36 | "icon": "data:image/png;base64,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", 37 | "id": "osmfr-breton", 38 | "max_zoom": 20, 39 | "mod-tile-features": true, 40 | "name": "OpenStreetMap (Breton Style)", 41 | "type": "tms", 42 | "url": "https://tile.openstreetmap.bzh/br/{zoom}/{x}/{y}.png", 43 | "valid-georeference": true, 44 | "dataSource": "JOSM" 45 | }, 46 | "type": "Feature" 47 | } -------------------------------------------------------------------------------- /layers/josm/osmfr-occitan.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "coordinates": [ 4 | [ 5 | [ 6 | 16.2075922, 7 | 62.7408449 8 | ], 9 | [ 10 | 16.2900797, 11 | 33.8453727 12 | ], 13 | [ 14 | -10.7364447, 15 | 33.8043768 16 | ], 17 | [ 18 | -10.8189321, 19 | 62.7182339 20 | ], 21 | [ 22 | 16.2075922, 23 | 62.7408449 24 | ] 25 | ] 26 | ], 27 | "type": "Polygon" 28 | }, 29 | "properties": { 30 | "attribution": { 31 | "required": true, 32 | "text": "Tiles © OpenStreetMap France, data © OpenStreetMap contributors, ODbL", 33 | "url": "https://www.openstreetmap.org/" 34 | }, 35 | "category": "osmbasedmap", 36 | "icon": "data:image/png;base64,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", 37 | "id": "osmfr-occitan", 38 | "max_zoom": 20, 39 | "mod-tile-features": true, 40 | "name": "OpenStreetMap (Occitan Style)", 41 | "type": "tms", 42 | "url": "https://tile.openstreetmap.bzh/oc/{zoom}/{x}/{y}.png", 43 | "valid-georeference": true, 44 | "dataSource": "JOSM" 45 | }, 46 | "type": "Feature" 47 | } -------------------------------------------------------------------------------- /layers/josm/osmfr.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "Tiles © cquest@Openstreetmap France, data © OpenStreetMap contributors, ODBL", 7 | "url": "https://www.openstreetmap.org/" 8 | }, 9 | "category": "osmbasedmap", 10 | "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAGb0lEQVR4Xq3MS2xcVx3H8e855z7m3vH1jMdjO43zshIrtpM0SZ3WFEcRVFFKoRWPBYtAHwgWCFUsQN100QeCsqAhCESrirRASdWWIkoUqgKFJm3UZ5o2oW2akrR5uXbi2ON4PPfOfZ17iERW2bCAz1/f3V8/cfDC02UEj4OxKHjdFMUfmUzOlAeXZFZuuVIKN8zi1dPh7Hcn5091G2EOB5b//NplA28/9tAT4bq111jD64ZKHR2VQWPEbULI0aHqZz7NZeLNmSeVMOJu4AfGgNEGLsR/95YuetcRzprcFJvaaVxrpm2mmqfp8FxKSYk8ck+0pt1nykFp0A/k9Tnt/lq3T2dHEOmi2DxSu+EdAAFwcPqp640xe42h26Qa2hqnXsFVHs04pNFuYgvbtOWCmD3aMj2VNaLi1+jwyxgpCKOQRCdMN6ZMd5Bmw1cPfG+5O/7LeX0ECyBppx9Zrn0c6BbNHJNr4qhFYWtsJCWpSFUm3ntxips2bxe+p1BKIJQECpRbkCQ2wu0XzbnIeemvhzYCVNR6JMDm5bdNF9qcwhiKbpvMg2KujZDmUgX1jgqT719gy+iX6QxsPM9F2YqclHbRRpUEXmCRZTGFlZGKxSMbR6+uA0gu863OzLc7cVUZN6jgKI8kicl0ykzjAiW5lGrVAWUwokApSa3UTY/XR2B3UlDgVy1sB8od7tgt2++4AcACOHL+uRW2bW8QQiCxcUoS6XssyBbnF6Y5+8kci8vjlJwqjnQwWpDlgtRYZDpHmxJzWUyk26gOYXxK0g+8G4bXX/snC0DZcmVh9EoQ5DpDSIVJYmIiGvkCurAQpkLU9kkTEEJeCjxbURSCZgTkfVjOApoM6Qtc3109Nj7uWO9M70EbPQT4YMhNjrWQEdHmTPs8SgfUisUgDLoAgcGyEjJtEaeaVlKQ5gVSGYwBhETaEmVJcemQaZ44aRpvyLIEnedGoNHNkNTTeI5DT2kRljeJziVhnKNlE1GaJFfnuRi3SXMDxmA7IUqCI20qdifkxfED+/dlanzb1cYL3BFhsTXPU4Ep0FGKUVALegCb1oLN7FlNpd5HIUNQLYxIsO0EKcByWljuAmXbp7fcIwgVb7xyaNeep3YfUnt272PLjaP1em/lZqmEk4cJQoMKfBxVIcwjlAp47okX6ap3XaoP221CIREyx7La2E6O7/h0eTXK+Bw+8uHrP7znvocXLe6flwCHXj06kab5hJAS6dhQFOgiJ2qXsKLVuHqAgdVD7HrwXibPTKJzFyMMAgECHGXTJTqxE4t3T5xMD7y29+mFRjgpLSUlYJ793T9ORWF0Lk1StCzQNqTTAle30flbzJ/bx6n3Xsa2Fb9/5Eccfu19wnlNFBniWJA3JecbDfa/8y5nJg7bW7ZeuyYKZwyF0QIAkC988Ogzfof6SppapHGZqgNTExPsfngfgaXpqyWk9QF6lwWk7YJlK1fQaTysqk8sE6LwIq4OEMql1DH3/O03PnBHUCk3JMCeA7/q6u1a0ltiNZ1eL719cPrcWR752d+Yn2lx/HSDY40qY2Pb2PapjYxv3YBX1syZ88weP3bp90Omw3PMOxNUe9FTn9gn5uemQqfk5BbAisHBlVo7g7PFx9TdgDMnZvjtzheI51qkWY5xYWxTLxXfJ5IVevIZ6gMDxCYnPjWDXZ0nSiOkEmYqO5l8cGT6FSD6+NhRJMCF1sWV88VkX6f3n/FHd+xl9uwsrTDEOIbPfWEj120eorw0JEzLhGWfop3ixxKrP0CTIQSAEVmhw5/f94uXAANg3b/r+1YrnV1T86ucPDzBrp3PcvHcHGmaUekL+OKtm9m0ailOUEe4FrXyWaKFblM4HmUvElNzF4jTxFiOhRRSJO34JHCOy6RU0i60uepfx07nO+593FycapAkCbX+Kl+/cxvXfXYIU3GjN/Yf2vPPg2/9JVxoNYNqQ+hcilznlKWDcpSwlCWiZrtx/O3Tf+AKClj88I9v/9aD99/aHBkaLlYNDTXuvPubH/3kN3e9vP3bn78HGAP6gSXA6K3fufnOXz+/888vHH3i9O4XH3j1roe+8dDo1pGvAeuAClfa/+aTQH35Yztuenz7l4aPLB9c81O/3H3L5VHFlUACZaAH6AQc/pv1o9cEiwbWbRlev+mrazeOblg3usnj/6nWV2fVyFp71fCa0sDgkOKyq5av4H/xb0Ky8po5hQEuAAAAAElFTkSuQmCC", 11 | "id": "osmfr", 12 | "max_zoom": 20, 13 | "mod-tile-features": true, 14 | "name": "OpenStreetMap (French Style)", 15 | "type": "tms", 16 | "url": "https://{switch:a,b,c}.tile.openstreetmap.fr/osmfr/{zoom}/{x}/{y}.png", 17 | "valid-georeference": true, 18 | "dataSource": "JOSM" 19 | }, 20 | "type": "Feature" 21 | } -------------------------------------------------------------------------------- /layers/josm/standard.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© OpenStreetMap contributors", 7 | "url": "https://www.openstreetmap.org/" 8 | }, 9 | "category": "osmbasedmap", 10 | "default": true, 11 | "icon": "data:image/png;base64,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", 12 | "id": "standard", 13 | "license_url": "https://wiki.osmfoundation.org/wiki/Terms_of_Use", 14 | "max_zoom": 19, 15 | "mod-tile-features": true, 16 | "name": "OpenStreetMap Carto (Standard)", 17 | "privacy_policy_url": "https://wiki.osmfoundation.org/wiki/Privacy_Policy", 18 | "type": "tms", 19 | "url": "https://tile.openstreetmap.org/{zoom}/{x}/{y}.png", 20 | "valid-georeference": true, 21 | "dataSource": "JOSM" 22 | }, 23 | "type": "Feature" 24 | } -------------------------------------------------------------------------------- /layers/josm/wikimedia-map.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": { 5 | "required": true, 6 | "text": "© OpenStreetMap contributors, CC-BY-SA", 7 | "url": "https://www.openstreetmap.org/" 8 | }, 9 | "category": "osmbasedmap", 10 | "id": "wikimedia-map", 11 | "max_zoom": 18, 12 | "name": "Wikimedia Map", 13 | "type": "tms", 14 | "url": "https://maps.wikimedia.org/osm-intl/{zoom}/{x}/{y}.png", 15 | "valid-georeference": true, 16 | "dataSource": "JOSM" 17 | }, 18 | "type": "Feature" 19 | } -------------------------------------------------------------------------------- /layers/mvt/mapillary-coverage-style.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "sources": { 4 | "mapillary-coverage": { 5 | "type": "vector", 6 | "tiles": [ 7 | "https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token={keys_mapillary}" 8 | ], 9 | "minzoom": 0, 10 | "maxzoom": 14 11 | } 12 | }, 13 | "layers": [ 14 | { 15 | "id": "mapillary-sequence", 16 | "type": "line", 17 | "source": "mapillary-coverage", 18 | "source-layer": "sequence", 19 | "minzoom": 6, 20 | "layout": { 21 | "line-cap": "round", 22 | "line-join": "round" 23 | }, 24 | "paint": { 25 | "line-opacity": 1, 26 | "line-color": "rgb(53, 175, 109)", 27 | "line-width": 2 28 | } 29 | }, 30 | { 31 | "id": "mapillary-image", 32 | "type": "circle", 33 | "source": "mapillary-coverage", 34 | "source-layer": "image", 35 | "interactive": true, 36 | "minzoom": 14, 37 | "paint": { 38 | "circle-radius": 3, 39 | "circle-opacity": 1, 40 | "circle-color": "rgb(53, 175, 109)" 41 | } 42 | }, 43 | { 44 | "filter": ["==", "is_pano", true], 45 | "id": "mapillary-pano", 46 | "type": "circle", 47 | "source": "mapillary-coverage", 48 | "source-layer": "image", 49 | "minzoom": 17, 50 | "paint": { 51 | "circle-radius": 9, 52 | "circle-opacity": 0.2, 53 | "circle-color": "rgb(53, 175, 109)" 54 | } 55 | }, 56 | { 57 | "id": "mapillary-overview", 58 | "type": "circle", 59 | "source": "mapillary-coverage", 60 | "source-layer": "overview", 61 | "maxzoom": 6, 62 | "paint": { 63 | "circle-radius": 2, 64 | "circle-opacity": 0.5, 65 | "circle-color": "rgb(53, 175, 109)" 66 | } 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /layers/mvt/mapillary-coverage.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "attribution": "© Mapillary, CC BY", 5 | "id": "mapillary-coverage", 6 | "name": "Mapillary Coverage", 7 | "overlay": true, 8 | "type": "mvt", 9 | "url": "mapillary-coverage-style?{keys_mapillary}" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/mvt/terrarium-hillshading-style.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "sources": { 4 | "dem": { 5 | "type": "raster-dem", 6 | "tiles": [ 7 | "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png" 8 | ], 9 | "encoding": "terrarium", 10 | "tileSize": 256, 11 | "maxzoom": 15 12 | } 13 | }, 14 | "layers": [ 15 | { 16 | "id": "hillshading", 17 | "source": "dem", 18 | "type": "hillshade", 19 | "paint": { 20 | "hillshade-exaggeration": 0.5, 21 | "hillshade-shadow-color": "#000000", 22 | "hillshade-highlight-color": "#FFFFFF", 23 | "hillshade-accent-color": "rgba(0, 0, 0, 1)" 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /layers/mvt/terrarium-hillshading.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "id": "terrarium-hillshading", 5 | "overlay": true, 6 | "type": "mvt", 7 | "url": "terrarium-hillshading-style", 8 | "mapUrl": "https://registry.opendata.aws/terrain-tiles/", 9 | "attribution": "Terrain Tiles at Registry of Open Data on AWS:
  • ArcticDEM terrain data DEM(s) were created from DigitalGlobe, Inc., imagery and funded under National Science Foundation awards 1043681, 1559691, and 1542736
  • Australia terrain data © Commonwealth of Australia (Geoscience Australia) 2017
  • Austria terrain data © offene Daten Österreichs – Digitales Geländemodell (DGM) Österreich
  • Canada terrain data contains information licensed under the Open Government Licence – Canada
  • Europe terrain data produced using Copernicus data and information funded by the European Union - EU-DEM layers
  • Global ETOPO1 terrain data U.S. National Oceanic and Atmospheric Administration
  • Mexico terrain data source: INEGI, Continental relief, 2016
  • New Zealand terrain data Copyright 2011 Crown copyright (c) Land Information New Zealand and the New Zealand Government (All rights reserved)
  • Norway terrain data © Kartverket
  • United Kingdom terrain data © Environment Agency copyright and/or database right 2015. All rights reserved
  • United States 3DEP (formerly NED) and global GMTED2010 and SRTM terrain data courtesy of the U.S. Geological Survey.
" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/financial/atm.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "ATM", 5 | "id": "atm", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-bank", 9 | "query": "(nwr[amenity=atm]; nwr[amenity=bank][atm][atm!=no];);" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/financial/bank.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bank", 5 | "id": "bank", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-bank", 9 | "query": "nwr[amenity=bank];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/bench.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bench", 5 | "id": "bench", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-bench", 9 | "query": "nwr[amenity=bench];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/kneipp_water_cure.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Kneipp water cure", 5 | "id": "kneipp_water_cure", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-hospital", 9 | "query": "nwr[amenity=kneipp_water_cure];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/public_bath.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Public bath", 5 | "id": "public_bath", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-water", 9 | "query": "nwr[amenity=public_bath];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/shelter.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Shelter", 5 | "id": "shelter", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-shelter", 9 | "query": "nwr[amenity=shelter];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/shower.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Shower", 5 | "id": "shower", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-shower", 9 | "query": "nwr[amenity=shower];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/telephone.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Telephone", 5 | "id": "telephone", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-phone-flip", 9 | "query": "nwr[amenity=telephone];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/toilets.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Toilets", 5 | "id": "toilets", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-toilet", 9 | "query": "nwr[amenity=toilets];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/others/water_point.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Water point", 5 | "id": "water_point", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-drinking-water", 9 | "query": "nwr[amenity=water_point];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/bar.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bar", 5 | "id": "bar", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-bar", 9 | "query": "nwr[amenity=bar];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/bbq.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "BBQ", 5 | "id": "bbq", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-bbq", 9 | "query": "nwr[amenity=bbq];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/biergarten.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Biergarten", 5 | "id": "biergarten", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-beer", 9 | "query": "nwr[amenity=biergarten];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/cafe.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Cafe", 5 | "id": "cafe", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-cafe", 9 | "query": "nwr[amenity=cafe];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/drinking_water.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Drinking water", 5 | "id": "drinking_water", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-drinking-water", 9 | "query": "nwr[amenity=drinking_water];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/fast_food.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Fast food", 5 | "id": "fast_food", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-fast-food", 9 | "query": "nwr[amenity=fast_food];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/food_court.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Food court", 5 | "id": "food_court", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-restaurant", 9 | "query": "nwr[amenity=food_court];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/ice_cream.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Ice cream", 5 | "id": "ice_cream", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-ice-cream", 9 | "query": "nwr[amenity=ice_cream];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/pub.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Pub", 5 | "id": "pub", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-beer", 9 | "query": "nwr[amenity=pub];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/sustenance/restaurant.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Restaurant", 5 | "id": "restaurant", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-restaurant", 9 | "query": "nwr[amenity=restaurant];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/bicycle_charging_station.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bicycle Charging Station", 5 | "id": "bicycle_charging_station", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-charging-station", 9 | "query": "nwr[amenity=charging_station][bicycle=yes];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/bicycle_parking.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bicycle parking", 5 | "id": "bicycle_parking", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-bicycle", 9 | "query": "nwr[amenity=bicycle_parking];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/bicycle_rental.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bicycle rental", 5 | "id": "bicycle_rental", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-bicycle_rental", 9 | "query": "nwr[amenity=bicycle_rental];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/bicycle_repair_station.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bicycle repair station", 5 | "id": "bicycle_repair_station", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-bicycle_repair", 9 | "query": "nwr[amenity=bicycle_repair_station];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/boat_rental.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Boat rental", 5 | "id": "boat_rental", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-boat_rental", 9 | "query": "nwr[amenity=boat_rental];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/boat_sharing.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Boat sharing", 5 | "id": "boat_sharing", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "query": "nwr[amenity=boat_sharing];" 9 | }, 10 | "type": "Feature" 11 | } 12 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/bus_station.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bus station", 5 | "id": "bus_station", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-bus", 9 | "query": "nwr[amenity=bus_station];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/car_rental.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Car rental", 5 | "id": "car_rental", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-car-rental", 9 | "query": "nwr[amenity=car_rental];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/car_sharing.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Car sharing", 5 | "id": "car_sharing", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-sign_and_car", 9 | "query": "nwr[amenity=car_sharing];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/car_wash.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Car wash", 5 | "id": "car_wash", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-car_wash", 9 | "query": "nwr[amenity=car_wash];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/charging_station.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Charging station", 5 | "id": "charging_station", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-charging-station", 9 | "query": "nwr[amenity=charging_station];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/ferry_terminal.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Ferry terminal", 5 | "id": "ferry_terminal", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-ferry", 9 | "query": "nwr[amenity=ferry_terminal];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/fuel.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Fuel", 5 | "id": "fuel", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-fuel", 9 | "query": "nwr[amenity=fuel];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/grit_bin.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Grit bin", 5 | "id": "grit_bin", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-box", 9 | "query": "nwr[amenity=grit_bin];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/motorcycle_parking.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Motorcycle parking", 5 | "id": "motorcycle_parking", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-motorcycle", 9 | "query": "nwr[amenity=motorcycle_parking];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/parking.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Parking", 5 | "id": "parking", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-car_parked", 9 | "query": "nwr[amenity=parking];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/parking_entrance.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Parking entrance", 5 | "id": "parking_entrance", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-entrance-alt1", 9 | "query": "nwr[amenity=parking_entrance];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/parking_space.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Parking space", 5 | "id": "parking_space", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-parking_space", 9 | "query": "nwr[amenity=parking_space];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/railway_station.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Railway station", 5 | "id": "railway_station", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-train", 9 | "query": "nwr[railway=station];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/taxi.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Taxi", 5 | "id": "taxi", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-taxi", 9 | "query": "nwr[amenity=taxi];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/amenity/transportation/vehicle_inspection.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Vehicle inspection", 5 | "id": "vehicle_inspection", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-car", 9 | "query": "nwr[amenity=vehicle_inspection];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/bakery.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Bakery", 5 | "id": "bakery", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-bakery", 9 | "query": "nwr[shop=bakery];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/beverages.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Beverages", 5 | "id": "beverages", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-bottles", 9 | "query": "nwr[shop=beverages];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/butcher.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Butcher", 5 | "id": "butcher", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-cleaver", 9 | "query": "nwr[shop=butcher];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/cheese.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Cheese", 5 | "id": "cheese", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-cheese", 9 | "query": "nwr[shop=cheese];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/coffee.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Coffee", 5 | "id": "coffee", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-coffee", 9 | "query": "nwr[shop=coffee];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/convenience.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Convenience", 5 | "id": "convenience", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-basket-shopping", 9 | "query": "nwr[shop=convenience];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/greengrocer.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Greengrocer", 5 | "id": "greengrocer", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-carrot", 9 | "query": "nwr[shop=greengrocer];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/health_food.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Health food", 5 | "id": "health_food", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-shop", 9 | "query": "nwr[shop=health_food];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/ice_cream_shop.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Ice cream", 5 | "id": "ice_cream_shop", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-ice-cream", 9 | "query": "nwr[shop=ice_cream];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/organic.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Organic", 5 | "id": "organic", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-grocery", 9 | "query": "nwr[shop~'supermarket|convenience'][organic][organic!=no];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/shop/food/supermarket.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Supermarket", 5 | "id": "supermarket", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-cart-shopping", 9 | "query": "nwr[shop=supermarket];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/apartment.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Apartment", 5 | "id": "apartment", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-lodging", 9 | "query": "nwr[tourism=apartment];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/artwork.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Artwork", 5 | "id": "artwork", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-art-gallery", 9 | "query": "nwr[tourism=artwork];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/attraction.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Attraction", 5 | "id": "attraction", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-star", 9 | "query": "nwr[tourism=attraction];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/camp_site.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Camp site", 5 | "id": "camp_site", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-campsite", 9 | "query": "nwr[tourism=camp_site];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/caravan_site.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Caravan site", 5 | "id": "caravan_site", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-camper_trailer", 9 | "query": "nwr[tourism=caravan_site];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/chalet.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Chalet", 5 | "id": "chalet", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-cabin", 9 | "query": "nwr[tourism=chalet];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/gallery.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Gallery", 5 | "id": "gallery", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-art-gallery", 9 | "query": "nwr[tourism=gallery];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/guest_house.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Guest house", 5 | "id": "guest_house", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-lodging", 9 | "query": "nwr[tourism=guest_house];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/hostel.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Hostel", 5 | "id": "hostel", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-bunk_beds", 9 | "query": "nwr[tourism=hostel];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/hotel.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Hotel", 5 | "id": "hotel", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "fas-bell-concierge", 9 | "query": "nwr[tourism=hotel];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/information.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Information", 5 | "id": "information", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-information", 9 | "query": "nwr[tourism=information];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/motel.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Motel", 5 | "id": "motel", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-lodging", 9 | "query": "nwr[tourism=motel];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/museum.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Museum", 5 | "id": "museum", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-museum", 9 | "query": "nwr[tourism=museum];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/picnic_site.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Picnic site", 5 | "id": "picnic_site", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "maki-picnic-site", 9 | "query": "nwr[tourism=picnic_site];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/shelter.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Shelter", 5 | "id": "shelter", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-picnic_shelter", 9 | "query": "nwr[amenity=shelter];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/viewpoint.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Viewpoint", 5 | "id": "viewpoint", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-spotting_scope", 9 | "query": "nwr[tourism=viewpoint];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /layers/overpass/tourism/wilderness_hut.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": null, 3 | "properties": { 4 | "name": "Wilderness hut", 5 | "id": "wilderness_hut", 6 | "overlay": true, 7 | "dataSource": "OverpassAPI", 8 | "icon": "temaki-cabin", 9 | "query": "nwr[tourism=wilderness_hut];" 10 | }, 11 | "type": "Feature" 12 | } 13 | -------------------------------------------------------------------------------- /locales/keys.js: -------------------------------------------------------------------------------- 1 | // this file contains translatable keys that are dynamic / not visible by i18n extractor tool 2 | 3 | i18next.t('about.tooltip'); 4 | 5 | i18next.t('footer.elevation-chart'); 6 | 7 | i18next.t('keyboard.backspace'); 8 | i18next.t('keyboard.escape'); 9 | i18next.t('keyboard.shift'); 10 | 11 | i18next.t('map.delete-last-point'); 12 | i18next.t('map.draw-poi-start'); 13 | i18next.t('map.draw-poi-stop'); 14 | i18next.t('map.draw-route-start'); 15 | i18next.t('map.draw-route-stop'); 16 | i18next.t('map.toggle-beeline'); 17 | i18next.t('map.geocoder'); 18 | i18next.t('map.locate-me'); 19 | i18next.t('map.nogo.cancel'); 20 | i18next.t('map.nogo.draw'); 21 | i18next.t('map.opacity-slider'); 22 | i18next.t('map.reverse-route'); 23 | i18next.t('map.route-quality-altitude'); 24 | i18next.t('map.route-quality-cost'); 25 | i18next.t('map.route-quality-incline'); 26 | i18next.t('map.strava-biking'); 27 | i18next.t('map.strava-running'); 28 | i18next.t('map.zoomInTitle'); 29 | i18next.t('map.zoomOutTitle'); 30 | 31 | i18next.t('navbar.export-tooltip'); 32 | i18next.t('navbar.profile.car-eco'); 33 | i18next.t('navbar.profile.car-fast'); 34 | i18next.t('navbar.profile.car-test'); 35 | i18next.t('navbar.profile.fastbike'); 36 | i18next.t('navbar.profile.fastbike-asia-pacific'); 37 | i18next.t('navbar.profile.fastbike-asia-pacific'); 38 | i18next.t('navbar.profile.fastbike-lowtraffic'); 39 | i18next.t('navbar.profile.hiking-mountain'); 40 | i18next.t('navbar.profile.moped'); 41 | i18next.t('navbar.profile.rail'); 42 | i18next.t('navbar.profile.river'); 43 | i18next.t('navbar.profile.safety'); 44 | i18next.t('navbar.profile.shortest'); 45 | i18next.t('navbar.profile.trekking'); 46 | i18next.t('navbar.profile.trekking-ignore-cr'); 47 | i18next.t('navbar.profile.trekking-noferries'); 48 | i18next.t('navbar.profile.trekking-nosteps'); 49 | i18next.t('navbar.profile.trekking-steep'); 50 | i18next.t('navbar.profile.vm-forum-liegerad-schnell'); 51 | i18next.t('navbar.profile.vm-forum-velomobil-schnell'); 52 | i18next.t('navbar.share-tooltip'); 53 | 54 | i18next.t('sidebar.analysis.tooltip'); 55 | i18next.t('sidebar.customize-profile.tooltip'); 56 | i18next.t('sidebar.data.tooltip'); 57 | i18next.t('sidebar.itinerary.tooltip'); 58 | i18next.t('sidebar.layers.category.base-layers', 'Base layers'); 59 | i18next.t('sidebar.layers.category.country', 'Country'); 60 | i18next.t('sidebar.layers.category.europe', 'Europe'); 61 | i18next.t('sidebar.layers.category.europe-monolingual', 'Europe monolingual'); 62 | i18next.t('sidebar.layers.category.overlays', 'Overlays'); 63 | i18next.t('sidebar.layers.category.worldwide', 'Worldwide'); 64 | i18next.t('sidebar.layers.category.worldwide-international', 'Worldwide international'); 65 | i18next.t('sidebar.layers.category.worldwide-monolingual', 'Worldwide monolingual'); 66 | i18next.t('sidebar.layers.category.overpass', 'Locations (POIs)'); 67 | i18next.t('sidebar.layers.tooltip'); 68 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "digest": { 4 | "enabled": false 5 | }, 6 | "labels": ["dependencies"], 7 | "packageRules": [ 8 | { 9 | "matchDepTypes": ["devDependencies"], 10 | "schedule": ["on Monday every 2 weeks"] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /resources/boundaries/README.md: -------------------------------------------------------------------------------- 1 | # boundaries 2 | 3 | Downloaded from https://osm-boundaries.com. 4 | 5 | TopoJSON (https://github.com/topojson/topojson) used to convert to topology, simplify and reduce precision: 6 | `npm install -g topojson` 7 | 8 | ## germany-states 9 | 10 | Currently only containing states that do not use the municipality boundary for the Corona 15 km allowed zone rule. 11 | 12 | 13 | ``` 14 | curl --remote-name --remote-header-name --location --max-redirs -1 "https://osm-boundaries.com/Download/Submit?apiKey=YOUR_API_KEY&db=osm20201109&osmIds=-28322,-62771,-62372,-62467,-62607,-62422,-62782,-62504&includeAllTags" 15 | 16 | geo2topo germany-states.geojson | toposimplify -s 3e-12 | topoquantize 1e6 > germany-states.topo.json 17 | ``` 18 | 19 | ## countries 20 | 21 | ``` 22 | curl --remote-name --remote-header-name --location --max-redirs -1 "https://osm-boundaries.com/Download/Submit?apiKey=YOUR_API_KEY&db=osm20201109&osmIds=-51477,-1403916" 23 | 24 | geo2topo countries.geojson | toposimplify -s 3e-12 | topoquantize 1e6 > countries.topo.json 25 | ``` 26 | -------------------------------------------------------------------------------- /resources/standalone/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | # start script for standalone zip 6 | 7 | # start BRouter server on localhost 8 | x-terminal-emulator -T "BRouter server" -e "./standalone/local.sh" 9 | 10 | # start HTTP server for current directory (for brouter-web and profiles) 11 | #x-terminal-emulator -T "BRouter-Web HTTP server" -e "python -m SimpleHTTPServer 8000" 12 | x-terminal-emulator -T "BRouter-Web HTTP server" -e "python3 -m http.server 8000 --bind localhost" 13 | 14 | # open BRouter-Web in default browser 15 | xdg-open http://localhost:8000/brouter-web/ 16 | -------------------------------------------------------------------------------- /resources/standalone/segments4/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /tests/control/Export.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | BR = {}; 6 | BR.conf = {}; 7 | $ = require('jquery'); 8 | require('leaflet'); 9 | turf = require('@turf/turf'); 10 | require('../../js/Browser.js'); 11 | require('../../js/control/Export.js'); 12 | const fs = require('fs'); 13 | 14 | const indexHtmlString = fs.readFileSync('index.html', 'utf8'); 15 | const indexHtml = new DOMParser().parseFromString(indexHtmlString, 'text/html'); 16 | 17 | // &lonlats=8.467712,49.488117;8.469354,49.488394;8.470556,49.488946;8.469982,49.489176 + turnInstructionMode=2 18 | const segments = require('./data/segments.json'); 19 | const brouterTotal = require('./data/brouterTotal.json'); 20 | 21 | // resolve intended/accepted differences before comparing 22 | function adopt(total, brouterTotal) { 23 | // BRouter total aggregates messages over segments, client total does not, 24 | // but that's Ok, so just fix for the test comparison 25 | const messages = total.features[0].properties.messages; 26 | const message = messages[4].slice(); 27 | messages[4] = message; 28 | message[3] = (+message[3] + +messages[2][3] + +messages[3][3]).toString(); 29 | message[6] = (+message[6] + +messages[2][6] + +messages[3][6]).toString(); 30 | messages.splice(2, 2); 31 | 32 | // fix minor float rounding difference 33 | total.features[0].properties.times[6] = 28.833; // 28.832 34 | 35 | total.features[0].properties.name = brouterTotal.features[0].properties.name; 36 | } 37 | 38 | let track; 39 | const getLngCoord = (i) => track.features[i].geometry.coordinates[0]; 40 | const getProperty = (i, p) => track.features[i].properties[p]; 41 | 42 | beforeEach(() => { 43 | document.body = indexHtml.body.cloneNode(true); 44 | 45 | track = turf.featureCollection([ 46 | turf.lineString([ 47 | [0, 0], 48 | [1, 1], 49 | [2, 2], 50 | ]), 51 | ]); 52 | }); 53 | 54 | test('total track', () => { 55 | const segmentsString = JSON.stringify(segments, null, 2); 56 | let total = BR.Export._concatTotalTrack(segments); 57 | adopt(total, brouterTotal); 58 | expect(total).toEqual(brouterTotal); 59 | 60 | // test original segments are not modified 61 | expect(JSON.stringify(segments, null, 2)).toEqual(segmentsString); 62 | 63 | // should be repeatable 64 | total = BR.Export._concatTotalTrack(segments); 65 | adopt(total, brouterTotal); 66 | expect(total).toEqual(brouterTotal); 67 | }); 68 | 69 | test('include route points', () => { 70 | const latLngs = [L.latLng(0, 0), L.latLng(1, 1), L.latLng(2, 2)]; 71 | const exportRoute = new BR.Export(); 72 | 73 | exportRoute.update(latLngs, null); 74 | exportRoute._addRouteWaypoints(track); 75 | 76 | expect(track.features[0].geometry.type).toEqual('LineString'); 77 | expect(getLngCoord(1)).toEqual(0); 78 | expect(getLngCoord(2)).toEqual(1); 79 | expect(getLngCoord(3)).toEqual(2); 80 | expect(getProperty(1, 'name')).toEqual('from'); 81 | expect(getProperty(2, 'name')).toEqual('via1'); 82 | expect(getProperty(3, 'name')).toEqual('to'); 83 | expect(getProperty(1, 'type')).toEqual('from'); 84 | expect(getProperty(2, 'type')).toEqual('via'); 85 | expect(getProperty(3, 'type')).toEqual('to'); 86 | }); 87 | 88 | test('pois', () => { 89 | const markers = [ 90 | { 91 | latlng: L.latLng(1, 1), 92 | name: 'poi 1', 93 | }, 94 | { 95 | latlng: L.latLng(2, 2), 96 | name: 'poi 2', 97 | }, 98 | ]; 99 | const pois = { getMarkers: () => markers }; 100 | const exportRoute = new BR.Export(null, pois, null); 101 | 102 | exportRoute._addPois(track); 103 | 104 | expect(track.features[0].geometry.type).toEqual('LineString'); 105 | expect(getLngCoord(1)).toEqual(1); 106 | expect(getLngCoord(2)).toEqual(2); 107 | expect(getProperty(1, 'name')).toEqual('poi 1'); 108 | expect(getProperty(2, 'name')).toEqual('poi 2'); 109 | expect(getProperty(1, 'type')).toEqual('poi'); 110 | expect(getProperty(2, 'type')).toEqual('poi'); 111 | }); 112 | -------------------------------------------------------------------------------- /tests/control/RoutingOptions.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | BR = {}; 6 | $ = require('jquery'); 7 | i18next = require('i18next'); 8 | require('bootstrap'); 9 | require('bootstrap-select'); 10 | require('leaflet'); 11 | 12 | require('../../config.template.js'); 13 | require('../../js/Util.js'); 14 | require('../../js/router/BRouter.js'); 15 | require('../../js/control/RoutingOptions.js'); 16 | 17 | const fs = require('fs'); 18 | const indexHtmlString = fs.readFileSync('index.html', 'utf8'); 19 | const indexHtml = new DOMParser().parseFromString(indexHtmlString, 'text/html'); 20 | 21 | let defaultProfile; 22 | 23 | beforeEach(() => { 24 | document.body = indexHtml.body.cloneNode(true); 25 | location.hash = ''; 26 | // as used in BRouter.getUrlParams 27 | defaultProfile = BR.conf.profiles[0]; 28 | }); 29 | 30 | describe('Profile selection', () => { 31 | test('sets default profile (trekking) when no hash and no localStorage entry', () => { 32 | let routingOptions = new BR.RoutingOptions(); 33 | routingOptions.setOptions({}); 34 | const profile = routingOptions.getOptions().profile; 35 | expect(profile).toEqual(defaultProfile); 36 | }); 37 | 38 | // defaults are not set in hash, see BRouter.getUrlParams 39 | test('sets default profile (trekking) when hash without `profile` param regardless of localStorage', () => { 40 | location.hash = '#map=5/50.986/9.822/standard'; 41 | localStorage.routingprofile = 'shortest'; 42 | 43 | let routingOptions = new BR.RoutingOptions(); 44 | routingOptions.setOptions({}); 45 | const profile = routingOptions.getOptions().profile; 46 | expect(profile).toEqual(defaultProfile); 47 | }); 48 | 49 | test('sets profile from hash', () => { 50 | location.hash = '#map=5/50.986/9.822/standard&profile=fastbike'; 51 | localStorage.routingprofile = 'shortest'; 52 | 53 | let routingOptions = new BR.RoutingOptions(); 54 | routingOptions.setOptions({ profile: 'fastbike' }); 55 | const profile = routingOptions.getOptions().profile; 56 | expect(profile).toEqual('fastbike'); 57 | }); 58 | 59 | test('sets profile from localStorage when no hash', () => { 60 | localStorage.routingprofile = 'shortest'; 61 | 62 | let routingOptions = new BR.RoutingOptions(); 63 | routingOptions.setOptions({}); 64 | const profile = routingOptions.getOptions().profile; 65 | expect(profile).toEqual('shortest'); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /tests/control/data/brouterTotal.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "creator": "BRouter-1.1", 8 | "name": "brouter_1615489279610_0", 9 | "track-length": "388", 10 | "filtered ascend": "1", 11 | "plain-ascend": "0", 12 | "total-time": "44", 13 | "total-energy": "4420", 14 | "cost": "703", 15 | "voicehints": [ 16 | [1,5,0,88.0,89], 17 | [6,2,0,99.0,-90], 18 | [7,2,0,10.0,-90] 19 | ], 20 | "messages": [ 21 | ["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"], 22 | ["8468340", "49488794", "101", "89", "1000", "0", "0", "0", "0", "highway=residential surface=asphalt cycleway=lane oneway=yes lcn=yes smoothness=good route_bicycle_icn=yes route_bicycle_ncn=yes route_bicycle_rcn=yes", ""], 23 | ["8469852", "49489230", "100", "299", "1150", "0", "270", "0", "0", "highway=residential surface=asphalt oneway=yes smoothness=good", ""] 24 | ], 25 | "times": [0,9.592,12.271,14.13,19.406,22.134,28.833,37.817,38.938,44.217] 26 | }, 27 | "geometry": { 28 | "type": "LineString", 29 | "coordinates": [ 30 | [8.467714, 49.488115, 101.5], 31 | [8.468340, 49.488794, 101.5], 32 | [8.468586, 49.488698, 101.5], 33 | [8.468743, 49.488636, 101.5], 34 | [8.469161, 49.488473, 101.75], 35 | [8.469355, 49.488395, 102.0], 36 | [8.469971, 49.488151, 103.5], 37 | [8.470671, 49.488909, 99.5], 38 | [8.470561, 49.488951, 99.5], 39 | [8.469984, 49.489178, 100.0] 40 | ] 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /tests/control/data/segments.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "feature": { 3 | "type": "Feature", 4 | "properties": { 5 | "creator": "BRouter-1.1", 6 | "name": "brouter_1615393581719_0", 7 | "track-length": "177", 8 | "filtered ascend": "0", 9 | "plain-ascend": "1", 10 | "total-time": "22", 11 | "total-energy": "2213", 12 | "cost": "280", 13 | "voicehints": [ 14 | [1,5,0,88.0,89] 15 | ], 16 | "messages": [ 17 | ["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"], 18 | ["8468340", "49488794", "101", "89", "1000", "0", "0", "0", "0", "highway=residential surface=asphalt cycleway=lane oneway=yes lcn=yes smoothness=good route_bicycle_icn=yes route_bicycle_ncn=yes route_bicycle_rcn=yes", ""], 19 | ["8469971", "49488151", "102", "88", "1150", "0", "90", "0", "0", "highway=residential surface=asphalt oneway=yes smoothness=good", ""] 20 | ], 21 | "times": [0,9.592,12.271,14.13,19.406,22.134] 22 | }, 23 | "geometry": { 24 | "type": "LineString", 25 | "coordinates": [ 26 | [8.467714, 49.488115, 101.5], 27 | [8.468340, 49.488794, 101.5], 28 | [8.468586, 49.488698, 101.5], 29 | [8.468743, 49.488636, 101.5], 30 | [8.469161, 49.488473, 101.75], 31 | [8.469355, 49.488395, 102.0] 32 | ] 33 | } 34 | } 35 | }, 36 | { 37 | "feature": { 38 | "type": "Feature", 39 | "properties": { 40 | "creator": "BRouter-1.1", 41 | "name": "brouter_1615393581719_0", 42 | "track-length": "162", 43 | "filtered ascend": "1", 44 | "plain-ascend": "-2", 45 | "total-time": "17", 46 | "total-energy": "1680", 47 | "cost": "367", 48 | "voicehints": [ 49 | [1,2,0,99.0,-90], 50 | [2,2,0,10.0,-90] 51 | ], 52 | "messages": [ 53 | ["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"], 54 | ["8469852", "49489230", "99", "162", "1150", "0", "180", "0", "0", "highway=residential surface=asphalt oneway=yes smoothness=good", ""] 55 | ], 56 | "times": [0,6.698,15.683,16.804] 57 | }, 58 | "geometry": { 59 | "type": "LineString", 60 | "coordinates": [ 61 | [8.469355, 49.488395, 102.0], 62 | [8.469971, 49.488151, 103.5], 63 | [8.470671, 49.488909, 99.5], 64 | [8.470561, 49.488951, 99.5] 65 | ] 66 | } 67 | } 68 | 69 | }, 70 | { 71 | "feature": { 72 | "type": "Feature", 73 | "properties": { 74 | "creator": "BRouter-1.1", 75 | "name": "brouter_1615393581719_0", 76 | "track-length": "49", 77 | "filtered ascend": "0", 78 | "plain-ascend": "1", 79 | "total-time": "5", 80 | "total-energy": "527", 81 | "cost": "56", 82 | "messages": [ 83 | ["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"], 84 | ["8469852", "49489230", "100", "49", "1150", "0", "0", "0", "0", "highway=residential surface=asphalt oneway=yes smoothness=good", ""] 85 | ], 86 | "times": [0,5.279] 87 | }, 88 | "geometry": { 89 | "type": "LineString", 90 | "coordinates": [ 91 | [8.470561, 49.488951, 99.5], 92 | [8.469984, 49.489178, 100.0] 93 | ] 94 | } 95 | } 96 | } 97 | ] -------------------------------------------------------------------------------- /tests/format/Gpx.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | BR = {}; 6 | BR.version = '1.5.1'; 7 | BR.conf = {}; 8 | turf = require('@turf/turf'); 9 | togpx = require('togpx'); 10 | require('leaflet'); 11 | require('../../js/Browser.js'); 12 | require('../../js/format/VoiceHints.js'); 13 | require('../../js/format/Xml.js'); 14 | require('../../js/format/Gpx.js'); 15 | 16 | const fs = require('fs'); 17 | 18 | // lonlats=8.467712,49.488117;8.470598,49.488849 + turnInstructionMode = 4 (comment-style) 19 | const geoJson = require('./data/track.json'); 20 | // lonlats=8.467712,49.488117;8.469354,49.488394;8.470556,49.488946;8.469982,49.489176 + turnInstructionMode = 5 21 | // console log in Export._formatTrack 22 | const waypointsGeoJson = require('./data/waypoints.json'); 23 | const path = 'tests/format/data/'; 24 | 25 | // resolve intended/accepted differences before comparing 26 | function adoptGpx(gpx, replaceCreator = true) { 27 | const creator = 'BRouter-Web 1.5.1'; 28 | const name = 'Track'; 29 | const newline = '\n'; 30 | 31 | gpx = gpx.replace('=.0', '=0.0'); 32 | if (replaceCreator) { 33 | gpx = gpx.replace(/creator="[^"]*"/, `creator="${creator}"`); 34 | } 35 | gpx = gpx.replace(/creator="([^"]*)" version="1.1"/, 'version="1.1" \n creator="$1"'); 36 | gpx = gpx.replace(/\n [^<]*<\/name>/, `\n ${name}`); 37 | gpx = gpx 38 | .split(newline) 39 | .map((line) => line.replace(/lon="([^"]*)" lat="([^"]*)"/, 'lat="$2" lon="$1"')) 40 | .join(newline); 41 | gpx = gpx.replace(/(lon|lat)="([-0-9]+\.[0-9]+?)0+"/g, '$1="$2"'); // remove trailing zeros 42 | gpx = gpx.replace(/>([-0-9]+\.\d*0+)<\//g, (match, p1) => `>${+p1}\n', '
'); 44 | 45 | return gpx; 46 | } 47 | 48 | function read(fileName, replaceCreator) { 49 | return adoptGpx(fs.readFileSync(path + fileName, 'utf8'), replaceCreator); 50 | } 51 | 52 | function format(geoJson, turnInstructionMode) { 53 | let gpx = BR.Gpx.format(geoJson, turnInstructionMode); 54 | gpx = gpx.replace(/\s*?/, ''); 55 | return gpx; 56 | } 57 | 58 | test('simple track', () => { 59 | const brouterGpx = read('track.gpx'); 60 | const gpx = format(geoJson); 61 | expect(gpx).toEqual(brouterGpx); 62 | }); 63 | 64 | test('waypoints', () => { 65 | const brouterGpx = BR.Xml.pretty(read('waypoints.gpx')); 66 | const gpx = format(waypointsGeoJson, 5); 67 | expect(gpx).toEqual(brouterGpx); 68 | }); 69 | 70 | describe('voice hints', () => { 71 | test('2-locus', () => { 72 | let brouterGpx = BR.Xml.pretty(read('2-locus.gpx')); 73 | // ignore float rounding differences 74 | brouterGpx = brouterGpx.replace( 75 | /:(rteTime|rteSpeed)>([\d.]*)<\//g, 76 | (match, p1, p2) => `:${p1}>${(+p2).toFixed(3)}9.361<', 'rteSpeed>9.360<'); 80 | 81 | const gpx = format(geoJson, 2); 82 | expect(gpx).toEqual(brouterGpx); 83 | }); 84 | 85 | test('3-osmand', () => { 86 | const brouterGpx = BR.Xml.pretty(read('3-osmand.gpx', false)); 87 | const gpx = format(geoJson, 3); 88 | expect(gpx).toEqual(brouterGpx); 89 | }); 90 | 91 | test('4-comment', () => { 92 | let brouterGpx = read('4-comment.gpx'); 93 | brouterGpx = brouterGpx.replace(/;\s*([-0-9]+.[0-9]+?)0+;/g, (match, p1) => `;${p1.padStart(10)};`); // remove trailing zeros 94 | 95 | const gpx = format(geoJson, 4); 96 | expect(gpx).toEqual(brouterGpx); 97 | }); 98 | 99 | test('5-gpsies', () => { 100 | const brouterGpx = read('5-gpsies.gpx'); 101 | const gpx = format(geoJson, 5); 102 | expect(gpx).toEqual(brouterGpx); 103 | }); 104 | 105 | test('6-orux', () => { 106 | let brouterGpx = BR.Xml.pretty(read('6-orux.gpx')); 107 | const gpx = format(geoJson, 6); 108 | expect(gpx).toEqual(brouterGpx); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /tests/format/data/2-locus.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 101.5right140.024.909946441650395.6202449221614787 10 | 103.5left90.09.6148529052734389.3605176165137064 11 | 12 | 2-locus 13 | 14 | 5 15 | 1 16 | 17 | 18 | 101.5 19 | 101.5 20 | 101.5 21 | 101.5 22 | 101.75 23 | 103.5 24 | 99.75 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/format/data/3-osmand.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | start 11 | 12 | 13 | 0 14 | 15 | 16 | 17 | right 18 | 19 | 20 | TR 21 | 89 22 | 1 23 | 24 | 25 | 26 | left 27 | 28 | 29 | TL 30 | -90 31 | 5 32 | 33 | 34 | 35 | destination 36 | 37 | 38 | 6 39 | 40 | 41 | 42 | 43 | 3-osmand 44 | 45 | 101.5 46 | 101.5 47 | 101.5 48 | 101.5 49 | 101.75 50 | 103.5 51 | 99.75 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /tests/format/data/4-comment.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 14 | 15 | 4-comment 16 | 17 | 101.5 18 | 101.5 19 | 101.5 20 | 101.5 21 | 101.75 22 | 103.5 23 | 99.75 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/format/data/5-gpsies.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | rightrightRight 9 | leftleftLeft 10 | 11 | 5-gpsies 12 | 13 | 101.5 14 | 101.5 15 | 101.5 16 | 101.5 17 | 101.75 18 | 103.5 19 | 99.75 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/format/data/6-orux.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 101.5 9 | 10 | 1001 11 | 12 | 13 | 103.5 14 | 15 | 1000 16 | 17 | 18 | 19 | 6-orux 20 | 21 | 101.5 22 | 101.5 23 | 101.5 24 | 101.5 25 | 101.75 26 | 103.5 27 | 99.75 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/format/data/track.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Track 10 | 11 | 101.5 12 | 101.5 13 | 101.5 14 | 101.5 15 | 101.75 16 | 103.5 17 | 99.75 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/format/data/track.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "creator": "BRouter-1.6.3", 8 | "name": "Track", 9 | "track-length": "319", 10 | "filtered ascend": "2", 11 | "plain-ascend": "-1", 12 | "total-time": "44", 13 | "total-energy": "4412", 14 | "cost": "533", 15 | "voicehints": [ 16 | [1,5,0,140.0,89," 6(90)6 (0)6 (-89)2"], 17 | [5,2,0,90.0,-90," 6(-89)6 (0)6 (89)6"] 18 | ], 19 | "messages": [ 20 | ["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags", "Time", "Energy"], 21 | ["8468340", "49488794", "101", "89", "1000", "0", "0", "0", "0", "highway=residential surface=asphalt cycleway=lane oneway=yes lcn=yes smoothness=good route_bicycle_icn=yes route_bicycle_ncn=yes route_bicycle_rcn=yes", "", "9", "959"], 22 | ["8470671", "49488909", "99", "230", "1150", "0", "180", "0", "0", "highway=residential surface=asphalt oneway=yes smoothness=good", "", "44", "4412"] 23 | ], 24 | "times": [0,9.592,12.271,14.13,19.406,34.502,44.117] 25 | }, 26 | "geometry": { 27 | "type": "LineString", 28 | "coordinates": [ 29 | [8.467714, 49.488115, 101.5], 30 | [8.468340, 49.488794, 101.5], 31 | [8.468586, 49.488698, 101.5], 32 | [8.468743, 49.488636, 101.5], 33 | [8.469161, 49.488473, 101.75], 34 | [8.469971, 49.488151, 103.5], 35 | [8.470610, 49.488842, 99.75] 36 | ] 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /tests/format/data/waypoints.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | rightrightRight 9 | leftleftLeft 10 | leftleftLeft 11 | 12 | poi 1 13 | 14 | 15 | poi 2 16 | 17 | 18 | from 19 | from 20 | 21 | 22 | via1 23 | via 24 | 25 | 26 | via2 27 | via 28 | 29 | 30 | to 31 | to 32 | 33 | 34 | waypoints + 5-gpsies 35 | 36 | 101.5 37 | 101.5 38 | 101.5 39 | 101.5 40 | 101.75 41 | 102.0 42 | 103.5 43 | 99.5 44 | 99.5 45 | 100.0 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/util/CheapRuler.test.js: -------------------------------------------------------------------------------- 1 | require('../../js/util/CheapRuler.js'); 2 | const geoJson = require('../format/data/track.json'); 3 | 4 | test('distance', () => { 5 | // https://github.com/abrensch/brouter/issues/3#issuecomment-440375918 6 | const lngLat1 = [2.3158, 48.8124]; 7 | const lngLat2 = [2.321, 48.8204]; 8 | 9 | const [ilon1, ilat1] = btools.util.CheapRuler.toIntegerLngLat(lngLat1); 10 | const [ilon2, ilat2] = btools.util.CheapRuler.toIntegerLngLat(lngLat2); 11 | 12 | const distance = btools.util.CheapRuler.distance(ilon1, ilat1, ilon2, ilat2); 13 | 14 | // 968.1670119067338 - issue #3 (App.java) 15 | // 968.0593622374572 - CheapRuler.java 16 | expect(distance).toBeCloseTo(968.0593622374572); 17 | }); 18 | 19 | test('total distance', () => { 20 | const coordinates = geoJson.features[0].geometry.coordinates; 21 | const properties = geoJson.features[0].properties; 22 | let totalDistance = 0; 23 | 24 | for (let i = 0; i < coordinates.length; i++) { 25 | if (i === 0) continue; 26 | 27 | const coord1 = coordinates[i - 1]; 28 | const coord2 = coordinates[i]; 29 | 30 | const [ilon1, ilat1] = btools.util.CheapRuler.toIntegerLngLat(coord1); 31 | const [ilon2, ilat2] = btools.util.CheapRuler.toIntegerLngLat(coord2); 32 | 33 | const distance = btools.util.CheapRuler.calcDistance(ilon1, ilat1, ilon2, ilat2); 34 | totalDistance += distance; 35 | } 36 | 37 | expect(Math.round(totalDistance)).toEqual(+properties['track-length']); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/util/StdPath.test.js: -------------------------------------------------------------------------------- 1 | BR = {}; 2 | require('../../js/util/CheapRuler.js'); 3 | require('../../js/util/StdPath.js'); 4 | 5 | const geoJson = require('../format/data/track.json'); 6 | 7 | test('simple track', () => { 8 | const coordinates = geoJson.features[0].geometry.coordinates; 9 | const properties = geoJson.features[0].properties; 10 | const dummyProfileVars = { 11 | getProfileVar(name) { 12 | const vars = { validForBikes: 1 }; 13 | return vars[name]; 14 | }, 15 | }; 16 | const rc = new BR.RoutingContext(dummyProfileVars); 17 | const stdPath = new BR.StdPath(); 18 | 19 | for (let i = 0; i < coordinates.length; i++) { 20 | if (i === 0) continue; 21 | 22 | const coord1 = coordinates[i - 1]; 23 | const coord2 = coordinates[i]; 24 | 25 | const [ilon1, ilat1] = btools.util.CheapRuler.toIntegerLngLat(coord1); 26 | const [ilon2, ilat2] = btools.util.CheapRuler.toIntegerLngLat(coord2); 27 | 28 | const distance = btools.util.CheapRuler.calcDistance(ilon1, ilat1, ilon2, ilat2); 29 | const deltaHeight = coord2[2] - coord1[2]; 30 | 31 | stdPath.computeKinematic(rc, distance, deltaHeight, true); 32 | } 33 | 34 | expect(Math.round(stdPath.getTotalEnergy())).toEqual(+properties['total-energy']); 35 | expect(Math.round(stdPath.getTotalTime())).toEqual(+properties['total-time']); 36 | }); 37 | --------------------------------------------------------------------------------