├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── dist └── spa │ ├── assets │ ├── KFOkCnqEu92Fr1MmgVxIIzQ-34e9582c.woff │ ├── KFOlCnqEu92Fr1MmEU9fBBc--9ce7f3ac.woff │ ├── KFOlCnqEu92Fr1MmSU5fBBc--bf14c7d7.woff │ ├── KFOlCnqEu92Fr1MmWUlfBBc--e0fd57c0.woff │ ├── KFOlCnqEu92Fr1MmYUtfBBc--f6537e32.woff │ ├── KFOmCnqEu92Fr1Mu4mxM-f2abf7fb.woff │ ├── MainLayout-b2302958.css │ ├── flUhRq6tzZclQEJ-Vdg-IuiaDsNa-fd84f88b.woff │ ├── flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-4a4dbc62.woff2 │ └── index-012a8cd7.css │ ├── favicon.ico │ ├── icon.png │ ├── icons │ ├── favicon-128x128.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon-96x96.png │ ├── index.html │ ├── multiple_tile.png │ ├── robots.txt │ ├── single_tile.png │ └── thirtytwo-9-82-180.png ├── index.html ├── jsconfig.json ├── mt-rainier1.png ├── mt-rainier2.png ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── icon.png ├── icons │ ├── favicon-128x128.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon-96x96.png ├── multiple_tile.png ├── robots.txt ├── single_tile.png └── thirtytwo-9-82-180.png ├── quasar.config.js ├── src-electron ├── electron-flag.d.ts ├── electron-main.js ├── electron-preload.js ├── icon.png └── icons │ ├── icon.icns │ ├── icon.ico │ └── icon.png └── src ├── App.vue ├── boot └── .gitkeep ├── components ├── ReloadPrompt.vue ├── help.vue ├── mapbox-map-viewer.vue ├── maptiler-map-viewer.vue ├── side-nav.vue └── wieghtmap-viewer.vue ├── css ├── app.scss ├── quasar.variables.scss └── styles.css ├── layouts └── MainLayout.vue ├── mapbox-gl-draw-assisted-rectangle-mode ├── .babelrc ├── .gitignore ├── README.md ├── dist │ ├── DrawAssistedRectangle.js │ └── DrawAssistedRectangle.min.js ├── draw-assisted-rectangle.gif ├── index.html ├── package.json └── src │ └── DrawAssistedRectangle.js ├── maptiler-gl-button-control ├── .ignore ├── Example │ ├── index.html │ ├── index.js │ ├── package.json │ └── styles.css ├── README.md ├── example.png ├── index.js └── package.json ├── pages ├── ErrorNotFound.vue └── IndexPage.vue ├── router ├── index.js └── routes.js └── utilities ├── combine-tiles-jimp.js ├── emitter.js ├── fs-helpers.js ├── idb-keyval-iife.js └── map-utils.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | package-lock.json 8 | 9 | 10 | # Cordova related directories and files 11 | /src-cordova/node_modules 12 | /src-cordova/platforms 13 | /src-cordova/plugins 14 | /src-cordova/www 15 | 16 | # Capacitor related directories and files 17 | /src-capacitor/www 18 | /src-capacitor/node_modules 19 | 20 | # Log files 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # Editor directories and files 26 | .idea 27 | *.suo 28 | *.ntvs* 29 | *.njsproj 30 | *.sln 31 | !/electron-builder.yml 32 | electron-builder.yml 33 | /electron-builder.yml 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Elebash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This app is no longer maintained please see the new app here https://github.com/delebash/unreal_map_bridge 2 | 3 | # New live site https://map.justgeektechs.com/ 4 | 5 | # Unreal Mapbox Bridge 6 | 7 | #### Use real world heightmap data from Mapbox and automatically convert it to an Unreal heightmap image 8 | 9 | # Live site [here](https://terrain.justgeektechs.com/) 10 | 11 | 12 | # Plugin version for Unreal Egine 5 UnrealMapboxBridge 13 | 14 | # Tutorial Playlist 15 | 16 | https://www.youtube.com/playlist?list=PLFCVXzupw1r_7ExUSGDxHGHU-gPRxOGeZ 17 | 18 | Get Help here Discord Server 19 | 20 | For what is coming next check the **RoadMap** 21 | 22 | Other free Unreal projects 23 | 24 | https://github.com/delebash/unreal_vault_organizer 25 | 26 | 27 | # Updates: 10/28/2022 28 | 29 | ## Added: 30 | 31 | **Blur Radius** -- This will apply a blur to the heightmap. It can be useful to reduce sharp edges and artifacts. The greater the number the greater the blur. 32 | 33 | ## Added Buttons: 34 | 35 | **Send to Terrain Magic** -- (only works with custom Terrain Magic code -- pm me on my discord channel if you want it). If you use the Terrain Magic plugin from the UE market place then this will load your landscape into Terrain Magic. 36 | 37 | **Copy Bounds for Blender Osm** -- If you use the Blender Osm plugin to generate real world landscapes, this will copy the coordinates you need to paste into the Blender Osm Plugin. Osm has it's own map app but if you use this map app you can still have that capability. 38 | 39 | **Copy Slippy Tile String** -- This will copy the Slippy Tile String Info. Slippy Tile is a format for identifying Tile Information. It is in the format x,y,z where z = zoom level. Terrain Magic uses the Slippy Tile String, so until Send To Terrain Magic is fixed you can use this to copy and paste into Terrain Magic. 40 | 41 | **Dist folder** -- contains compiled web app. You should be able to run it from any web server. 42 | 43 | 44 | ## Install the dependencies 45 | ```bash 46 | npm install 47 | ``` 48 | 49 | ### Start the app in development mode (hot-code reloading, error reporting, etc.) 50 | 51 | ```bash 52 | npm start 53 | ``` 54 | 55 | > NOTE: When running the development server you will need to disble cors for your browser or the api calls to Mapbox will not work. For Edge/Chrome there is a good plugin that I use to do this. See https://microsoftedge.microsoft.com/addons/detail/cors-unblock/hkjklmhkbkdhlgnnfbbcihcajofmjgbh 56 | > 57 | 58 | ## Build 59 | ```bash 60 | npm run build 61 | ``` 62 | 63 | ## Setup 64 | 65 | When you initially run the application you will have to set some data. 66 | 67 | **Settings Tab** 68 | 69 | 1) Enter a mapbox access token under the Settings Tab in the Mapbox Access Token File field 70 | 71 | To get an access token you can create a free [mapbox account](https://www.mapbox.com/). Then goto your account page and copy the default access token or create a new one. 72 | 73 | 2) Choose a download directory from the Settings screen. 74 | 75 | 3) Click the Save Settings button 76 | 77 | **Map Tab** 78 | 79 | Left click and hold to drag around the map 80 | Right click and hold to change the angle of the map 81 | 82 | Scroll wheel to zoom in out 83 | 84 | Game keys for navigation are also enabled WSAD 85 | 86 | Type in a name or coordinates in the search box 87 | 88 | Left click on the tile square you want to select. It will turn blue when selected. 89 | 90 | You will see a preview of what the heightmap will look like as well as some statistics. 91 | 92 | Click the download button to download the selected tile 16 bit heightmap file. 93 | 94 | Select Terrain size youu want to use for your Unreal Landscape size. 95 | 96 | The Scale number is correct but may look large in Unreal. You might want to use a different scale number when you import into Unreal. 97 | 98 | 99 | **Manually resize method if you want to edit your hegitmap in a photo editor** 100 | 101 | **Convert image to Unreal Landscape Size** 102 | 103 | See Unreal Recommended Landscape Sizes [here](https://docs.unrealengine.com/4.27/en-US/BuildingWorlds/Landscape/TechnicalGuide/) 104 | 105 | Resizing/Resampling an image for Unreal Landscapes has been added to the software. Just select your Landscape size before you download your heightmap. As an alternative to resizing in the application you can use other software to resize to custom sizes. 106 | 107 | Programs you can use to resample your image to the landscape size you are using. 108 | 109 | [GIMP](https://www.gimp.org/https://www.gimp.org/), [Affinity Photo](https://affinity.serif.com/en-us/photo/), [Photoshop](https://www.adobe.com/products/photoshop/landpa.html). 110 | [Terra Sculptor](http://www.demenzunmedia.com/home/terresculptor/) -- is an awesome free program for creating and manipulating heightmap images. It even has preset landscape sizes for Unreal. To enable UDK go to Settings > Dimensions and check UDK Landscape. 111 | 112 | The principle is the same for all. 113 | 114 | Example: **GIMP** 115 | 116 | 1) Choose File > Open then open the sixteen-x-x-x.png file for the tile you selected. The numbers indicate the selected tile. 117 | 118 | 2) Choose Layer > Scale Layer 119 | 3) Type in the width and height (should be the same as in height: 2017 width: 2017) 120 | 4) Set the Interpolation to NoHalo and click Scale 121 | 5) Choose File > Export as and name your converted file. 122 | 6) On the popup dialog box named "Export image as PNG" leave all defaults and click Export. 123 | 124 | 125 | Import the heightmap into Unreal per normal procedure. You will need to adjust the Z-scale to the Z-scale shown on the app. 126 | 127 | Example imported landscape standing on top of Mt. Rainier 128 | 129 | ![Mt. Rainier1](mt-rainier1.png) 130 | 131 | ![Mt. Rainier2](mt-rainier2.png) 132 | 133 | 134 | *Inspired by and some code used from this [project](https://github.com/colkassad/terrain-rgb-height). A Big thanks to Shane Brennan ([colkassad](https://github.com/colkassad)).* 135 | -------------------------------------------------------------------------------- /dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-34e9582c.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-34e9582c.woff -------------------------------------------------------------------------------- /dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--9ce7f3ac.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--9ce7f3ac.woff -------------------------------------------------------------------------------- /dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--bf14c7d7.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--bf14c7d7.woff -------------------------------------------------------------------------------- /dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--e0fd57c0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--e0fd57c0.woff -------------------------------------------------------------------------------- /dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--f6537e32.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--f6537e32.woff -------------------------------------------------------------------------------- /dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-f2abf7fb.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-f2abf7fb.woff -------------------------------------------------------------------------------- /dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-fd84f88b.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-fd84f88b.woff -------------------------------------------------------------------------------- /dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-4a4dbc62.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-4a4dbc62.woff2 -------------------------------------------------------------------------------- /dist/spa/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/favicon.ico -------------------------------------------------------------------------------- /dist/spa/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/icon.png -------------------------------------------------------------------------------- /dist/spa/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/icons/favicon-128x128.png -------------------------------------------------------------------------------- /dist/spa/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/icons/favicon-16x16.png -------------------------------------------------------------------------------- /dist/spa/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/icons/favicon-32x32.png -------------------------------------------------------------------------------- /dist/spa/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/icons/favicon-96x96.png -------------------------------------------------------------------------------- /dist/spa/index.html: -------------------------------------------------------------------------------- 1 | Unreal Mapbox Bridge 2 | 3 |
-------------------------------------------------------------------------------- /dist/spa/multiple_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/multiple_tile.png -------------------------------------------------------------------------------- /dist/spa/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /dist/spa/single_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/single_tile.png -------------------------------------------------------------------------------- /dist/spa/thirtytwo-9-82-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/dist/spa/thirtytwo-9-82-180.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Unreal Mapbox Bridge 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "src/*": [ 6 | "src/*" 7 | ], 8 | "app/*": [ 9 | "*" 10 | ], 11 | "components/*": [ 12 | "src/components/*" 13 | ], 14 | "layouts/*": [ 15 | "src/layouts/*" 16 | ], 17 | "pages/*": [ 18 | "src/pages/*" 19 | ], 20 | "assets/*": [ 21 | "src/assets/*" 22 | ], 23 | "boot/*": [ 24 | "src/boot/*" 25 | ], 26 | "stores/*": [ 27 | "src/stores/*" 28 | ], 29 | "vue$": [ 30 | "node_modules/vue/dist/vue.runtime.esm-bundler.js" 31 | ] 32 | } 33 | }, 34 | "exclude": [ 35 | "dist", 36 | ".quasar", 37 | "node_modules" 38 | ] 39 | } -------------------------------------------------------------------------------- /mt-rainier1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/mt-rainier1.png -------------------------------------------------------------------------------- /mt-rainier2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/mt-rainier2.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unreal-mapbox-bridge", 3 | "version": "2.8.0", 4 | "description": "Import real world locations into Unreal using Mapbox", 5 | "productName": "Unreal Mapbox Bridge", 6 | "author": "Daniel Elebash ", 7 | "private": true, 8 | "homepage": "http://delebash.github.io/unreal_mapbox_bridge", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/delebash/unreal_mapbox_bridge" 12 | }, 13 | "scripts": { 14 | "start": "quasar dev", 15 | "electron": "quasar dev -m electron", 16 | "build-spa": "quasar build", 17 | "build-electron": "quasar build -m electron", 18 | "publish-electron": "quasar build --debug -m electron -P always", 19 | "debug": "quasar dev --debug -m electron -t mat -- --remote-debugging-port=9222 --inspect-brk=5858" 20 | }, 21 | "dependencies": { 22 | "@delebash/mapbox-gl-button-control": "^1.0.4", 23 | "@geostarters/mapbox-gl-draw-rectangle-assisted-mode": "^3.0.4", 24 | "@mapbox/mapbox-gl-draw": "^1.4.1", 25 | "@mapbox/mapbox-gl-geocoder": "^5.0.1", 26 | "@mapbox/mapbox-sdk": "^0.15.2", 27 | "@mapbox/tilebelt": "^1.0.2", 28 | "@maptiler/client": "^1.5.0", 29 | "@maptiler/geocoding-control": "^0.0.90", 30 | "@maptiler/sdk": "^1.0.12", 31 | "@quasar/extras": "^1.16.4", 32 | "@turf/turf": "^6.5.0", 33 | "idb-keyval": "^6.2.1", 34 | "image-js": "^0.35.3", 35 | "jimp": "^0.22.8", 36 | "lodash": "^4.17.21", 37 | "mapbox-gl": "^2.15.0", 38 | "mapbox-gl-draw-rectangle-mode": "^1.0.4", 39 | "mitt": "^3.0.0", 40 | "quasar": "^2.12.0", 41 | "tiles-in-bbox": "^1.0.2", 42 | "vue": "^3.3.4", 43 | "vue-router": "^4.2.2" 44 | }, 45 | "devDependencies": { 46 | "@quasar/app-vite": "^1.4.3", 47 | "autoprefixer": "^10.4.14", 48 | "electron": "^25.1.0", 49 | "workbox-build": "^7.0.0", 50 | "workbox-cacheable-response": "^7.0.0", 51 | "workbox-core": "^7.0.0", 52 | "workbox-expiration": "^7.0.0", 53 | "workbox-precaching": "^7.0.0", 54 | "workbox-routing": "^7.0.0", 55 | "workbox-strategies": "^7.0.0" 56 | }, 57 | "overrides": { 58 | "vite": "^4.0.0", 59 | "@vitejs/plugin-vue": "^4.0.0" 60 | }, 61 | "engines": { 62 | "node": "^18 || ^16 || ^14.19", 63 | "npm": ">= 6.13.4", 64 | "yarn": ">= 1.21.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/michael-ciniawsky/postcss-load-config 3 | 4 | module.exports = { 5 | plugins: [ 6 | // https://github.com/postcss/autoprefixer 7 | require('autoprefixer')({ 8 | overrideBrowserslist: [ 9 | 'last 4 Chrome versions', 10 | 'last 4 Firefox versions', 11 | 'last 4 Edge versions', 12 | 'last 4 Safari versions', 13 | 'last 4 Android versions', 14 | 'last 4 ChromeAndroid versions', 15 | 'last 4 FirefoxAndroid versions', 16 | 'last 4 iOS versions' 17 | ] 18 | }) 19 | 20 | // https://github.com/elchininet/postcss-rtlcss 21 | // If you want to support RTL css, then 22 | // 1. yarn/npm install postcss-rtlcss 23 | // 2. optionally set quasar.config.js > framework > lang to an RTL language 24 | // 3. uncomment the following line: 25 | // require('postcss-rtlcss') 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/favicon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/icon.png -------------------------------------------------------------------------------- /public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /public/multiple_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/multiple_tile.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /public/single_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/single_tile.png -------------------------------------------------------------------------------- /public/thirtytwo-9-82-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/public/thirtytwo-9-82-180.png -------------------------------------------------------------------------------- /quasar.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | /* 4 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 5 | * the ES6 features that are supported by your Node version. https://node.green/ 6 | */ 7 | 8 | // Configuration for your app 9 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js 10 | 11 | 12 | const {configure} = require('quasar/wrappers'); 13 | 14 | 15 | module.exports = configure(function (/* ctx */) { 16 | return { 17 | 18 | 19 | // https://v2.quasar.dev/quasar-cli/prefetch-feature 20 | // preFetch: true, 21 | 22 | // app boot file (/src/boot) 23 | // --> boot files are part of "main.js" 24 | // https://v2.quasar.dev/quasar-cli/boot-files 25 | boot: [], 26 | 27 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css 28 | css: [ 29 | 'app.scss' 30 | ], 31 | 32 | // https://github.com/quasarframework/quasar/tree/dev/extras 33 | extras: [ 34 | // 'ionicons-v4', 35 | // 'mdi-v5', 36 | // 'fontawesome-v6', 37 | // 'eva-icons', 38 | // 'themify', 39 | // 'line-awesome', 40 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 41 | 42 | 'roboto-font', // optional, you are not bound to it 43 | 'material-icons', // optional, you are not bound to it 44 | ], 45 | 46 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build 47 | build: { 48 | target: { 49 | browser: ['esnext'], 50 | node: 'node16' 51 | }, 52 | 53 | vueRouterMode: 'hash', // available values: 'hash', 'history' 54 | // vueRouterBase, 55 | // vueDevtools, 56 | // vueOptionsAPI: false, 57 | 58 | // rebuildCache: true, // rebuilds Vite/linter/etc cache on startup 59 | 60 | // publicPath: '/', 61 | // analyze: true, 62 | // env: {browser:true}, 63 | // rawDefine: {} 64 | // ignorePublicFolder: true, 65 | // minify: false, 66 | // polyfillModulePreload: true, 67 | // distDir 68 | 69 | // extendViteConf (viteConf) {}, 70 | // viteVuePluginOptions: {}, 71 | 72 | 73 | // vitePlugins: [ 74 | // [ 'package-name', { ..options.. } ] 75 | // ] 76 | }, 77 | 78 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer 79 | devServer: { 80 | // https: true 81 | open: true // opens browser window automatically 82 | }, 83 | 84 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework 85 | framework: { 86 | config: { 87 | loading: { /* look at QuasarConfOptions from the API card */}, 88 | notify: { /* look at QuasarConfOptions from the API card */}, 89 | dialog: { /* look at QuasarConfOptions from the API card */} 90 | }, 91 | 92 | // iconSet: 'material-icons', // Quasar icon set 93 | // lang: 'en-US', // Quasar language pack 94 | 95 | // For special cases outside of where the auto-import strategy can have an impact 96 | // (like functional components as one of the examples), 97 | // you can manually specify Quasar components/directives to be available everywhere: 98 | // 99 | // components: [], 100 | // directives: [], 101 | 102 | // Quasar plugins 103 | plugins: [ 104 | 'Notify', 105 | 'Dialog', 106 | 'Loading' 107 | ] 108 | }, 109 | 110 | // animations: 'all', // --- includes all animations 111 | // https://v2.quasar.dev/options/animations 112 | animations: [], 113 | 114 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles 115 | // sourceFiles: { 116 | // rootComponent: 'src/App.vue', 117 | // router: 'src/router/index', 118 | // store: 'src/store/index', 119 | // registerServiceWorker: 'src-pwa/register-service-worker', 120 | // serviceWorker: 'src-pwa/custom-service-worker', 121 | // pwaManifestFile: 'src-pwa/manifest.json', 122 | // electronMain: 'src-electron/electron-main', 123 | // electronPreload: 'src-electron/electron-preload' 124 | // }, 125 | 126 | // https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr 127 | ssr: { 128 | // ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name! 129 | // will mess up SSR 130 | 131 | // extendSSRWebserverConf (esbuildConf) {}, 132 | // extendPackageJson (json) {}, 133 | 134 | pwa: false, 135 | 136 | // manualStoreHydration: true, 137 | // manualPostHydrationTrigger: true, 138 | 139 | prodPort: 3000, // The default port that the production server should use 140 | // (gets superseded if process.env.PORT is specified at runtime) 141 | 142 | middlewares: [ 143 | 'render' // keep this as last one 144 | ] 145 | }, 146 | 147 | // https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa 148 | pwa: { 149 | workboxOptions: { 150 | skipWaiting: true, 151 | clientsClaim: true 152 | }, 153 | workboxMode: 'generateSW', // or 'injectManifest' 154 | injectPwaMetaTags: true, 155 | swFilename: 'sw.js', 156 | manifestFilename: 'manifest.json', 157 | useCredentialsForManifestTag: false, 158 | // extendGenerateSWOptions (cfg) {} 159 | // extendInjectManifestOptions (cfg) {}, 160 | // extendManifestJson (json) {} 161 | // extendPWACustomSWConf (esbuildConf) {} 162 | }, 163 | 164 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova 165 | cordova: { 166 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 167 | }, 168 | 169 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor 170 | capacitor: { 171 | hideSplashscreen: true 172 | }, 173 | 174 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron 175 | electron: { 176 | // extendElectronMainConf (esbuildConf) 177 | // extendElectronPreloadConf (esbuildConf) 178 | 179 | inspectPort: 5858, 180 | 181 | bundler: 'builder', // 'packager' or 'builder' 182 | 183 | packager: { 184 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 185 | 186 | // OS X / Mac App Store 187 | // appBundleId: '', 188 | // appCategoryType: '', 189 | // osxSign: '', 190 | // protocol: 'myapp://path', 191 | 192 | // Windows only 193 | // win32metadata: { ... } 194 | }, 195 | builder: { 196 | // https://www.electron.build/configuration/configuration 197 | appId: 'unreal-mapbox-bridge', 198 | files: [ 199 | "!**/node_modules/*" 200 | ], 201 | publish: { 202 | 'provider': 'github' 203 | }, 204 | win: {// configuration parameters of installation software under Windows 205 | target: [ 206 | "portable" // single exe 207 | ] 208 | }, 209 | portable: { 210 | "artifactName": "Unreal Mapbox Bridge.exe" 211 | } 212 | // nsis: {// NSIS configuration parameters 213 | // oneClick: false, // click to open 214 | // deleteAppDataOnUninstall: true, 215 | // allowToChangeInstallationDirectory: true, // allows the user to choose the installation location 216 | // perMachine: true 217 | // } 218 | } 219 | }, 220 | 221 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex 222 | bex: { 223 | contentScripts: [ 224 | 'my-content-script' 225 | ], 226 | 227 | // extendBexScriptsConf (esbuildConf) {} 228 | // extendBexManifestJson (json) {} 229 | } 230 | } 231 | }); 232 | -------------------------------------------------------------------------------- /src-electron/electron-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | electron: true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src-electron/electron-main.js: -------------------------------------------------------------------------------- 1 | import {app, BrowserWindow, nativeTheme, Menu} from 'electron' 2 | import path from 'path' 3 | import os from 'os' 4 | 5 | app.commandLine.appendSwitch('enable-features', "SharedArrayBuffer") 6 | // needed in case process is undefined under Linux 7 | const platform = process.platform || os.platform() 8 | 9 | try { 10 | if (platform === 'win32' && nativeTheme.shouldUseDarkColors === true) { 11 | require('fs').unlinkSync(path.join(app.getPath('userData'), 'DevTools Extensions')) 12 | } 13 | } catch (_) { 14 | } 15 | 16 | let mainWindow 17 | const template = [ 18 | { 19 | label: 'File', 20 | submenu: [ 21 | { 22 | role: 'quit' 23 | } 24 | ] 25 | }, 26 | { 27 | label: 'Edit', 28 | submenu: [ 29 | { 30 | role: 'undo' 31 | }, 32 | { 33 | role: 'redo' 34 | }, 35 | { 36 | type: 'separator' 37 | }, 38 | { 39 | role: 'cut' 40 | }, 41 | { 42 | role: 'copy' 43 | }, 44 | { 45 | role: 'paste' 46 | } 47 | ] 48 | }, 49 | 50 | { 51 | label: 'View', 52 | submenu: [ 53 | { 54 | role: 'reload' 55 | }, 56 | { 57 | role: 'toggledevtools' 58 | }, 59 | { 60 | type: 'separator' 61 | }, 62 | { 63 | role: 'resetzoom' 64 | }, 65 | { 66 | role: 'zoomin' 67 | }, 68 | { 69 | role: 'zoomout' 70 | }, 71 | { 72 | type: 'separator' 73 | }, 74 | { 75 | role: 'togglefullscreen' 76 | } 77 | ] 78 | }, 79 | 80 | { 81 | role: 'window', 82 | submenu: [ 83 | { 84 | role: 'minimize' 85 | }, 86 | { 87 | role: 'close' 88 | } 89 | ] 90 | }, 91 | { 92 | role: 'help', 93 | submenu: [ 94 | { 95 | label: 'About', 96 | role: 'about' 97 | }, 98 | ] 99 | } 100 | ] 101 | function createWindow() { 102 | /** 103 | * Initial window options 104 | */ 105 | mainWindow = new BrowserWindow({ 106 | width: 1000, 107 | height: 600, 108 | useContentSize: true, 109 | autoHideMenuBar: false, 110 | icon: path.join(__dirname, 'icons/icon.ico'), 111 | webPreferences: { 112 | contextIsolation: true, 113 | // More info: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/electron-preload-script 114 | preload: path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD) 115 | } 116 | }) 117 | 118 | const menu = Menu.buildFromTemplate(template) 119 | Menu.setApplicationMenu(menu) 120 | 121 | mainWindow.loadURL(process.env.APP_URL) 122 | // mainWindow.webContents.openDevTools() 123 | mainWindow.webContents.closeDevTools() 124 | if (process.env.DEBUGGING) { 125 | // if on DEV or Production with debug enabled 126 | // mainWindow.webContents.closeDevTools() 127 | } else { 128 | // we're on production; no access to devtools pls 129 | mainWindow.webContents.on('devtools-opened', () => { 130 | //mainWindow.webContents.closeDevTools() 131 | }) 132 | } 133 | 134 | mainWindow.on('closed', () => { 135 | mainWindow = null 136 | }) 137 | 138 | } 139 | 140 | app.commandLine.appendSwitch('enable-experimental-web-platform-features'); 141 | app.whenReady().then(createWindow) 142 | 143 | app.on('window-all-closed', () => { 144 | if (platform !== 'darwin') { 145 | app.quit() 146 | } 147 | }) 148 | 149 | app.on('activate', () => { 150 | if (mainWindow === null) { 151 | createWindow() 152 | } 153 | }) 154 | -------------------------------------------------------------------------------- /src-electron/electron-preload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically for security reasons. 3 | * Here you can access Nodejs stuff and inject functionality into 4 | * the renderer thread (accessible there through the "window" object) 5 | * 6 | * WARNING! 7 | * If you import anything from node_modules, then make sure that the package is specified 8 | * in package.json > dependencies and NOT in devDependencies 9 | * 10 | * Example (injects window.myAPI.doAThing() into renderer thread): 11 | * 12 | * import { contextBridge } from 'electron' 13 | * 14 | * contextBridge.exposeInMainWorld('myAPI', { 15 | * doAThing: () => {} 16 | * }) 17 | */ 18 | -------------------------------------------------------------------------------- /src-electron/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/src-electron/icon.png -------------------------------------------------------------------------------- /src-electron/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/src-electron/icons/icon.icns -------------------------------------------------------------------------------- /src-electron/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/src-electron/icons/icon.ico -------------------------------------------------------------------------------- /src-electron/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/src-electron/icons/icon.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/src/boot/.gitkeep -------------------------------------------------------------------------------- /src/components/ReloadPrompt.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 53 | 54 | 80 | -------------------------------------------------------------------------------- /src/components/help.vue: -------------------------------------------------------------------------------- 1 | 230 | 231 | 283 | -------------------------------------------------------------------------------- /src/components/maptiler-map-viewer.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 56 | 57 | 659 | 660 | 661 | -------------------------------------------------------------------------------- /src/components/wieghtmap-viewer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 84 | 85 | 93 | -------------------------------------------------------------------------------- /src/css/app.scss: -------------------------------------------------------------------------------- 1 | // app global css in SCSS form 2 | -------------------------------------------------------------------------------- /src/css/quasar.variables.scss: -------------------------------------------------------------------------------- 1 | // Quasar SCSS (& Sass) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary : #1976D2; 16 | $secondary : #26A69A; 17 | $accent : #9C27B0; 18 | 19 | $dark : #1D1D1D; 20 | $dark-page : #121212; 21 | 22 | $positive : #21BA45; 23 | $negative : #C10015; 24 | $info : #31CCEC; 25 | $warning : #F2C037; 26 | -------------------------------------------------------------------------------- /src/css/styles.css: -------------------------------------------------------------------------------- 1 | 2 | /* // From: Pitch toggle control for Mapbox GL JS — http://fuzzytolerance.info/blog/2017/01/30/Pitch-toggle-control-for-Mapbox-GL-JS/ */ 3 | .mapboxgl-ctrl-pitchtoggle-3d { 4 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCI+ICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkeT0iLjM1ZW0iIHN0eWxlPSJmb250LXNpemU6IDE0cHg7IGZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLEFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1hbmNob3I6IG1pZGRsZTsiPjNEPC90ZXh0Pjwvc3ZnPg==); 5 | } 6 | 7 | .mapboxgl-ctrl-pitchtoggle-2d { 8 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCI+ICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkeT0iLjM1ZW0iIHN0eWxlPSJmb250LXNpemU6IDE0cHg7IGZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLEFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1hbmNob3I6IG1pZGRsZTsiPjJEPC90ZXh0Pjwvc3ZnPg==); 9 | } 10 | 11 | /* 12 | // the images for mapbox-gl-draw_* are from 13 | https://github.com/mapbox/mapbox-gl-draw/blob/master/dist/mapbox-gl-draw.css 14 | */ 15 | .mapbox-gl-draw_point { 16 | background-repeat: no-repeat; 17 | background-position: center; 18 | pointer-events: auto; 19 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJtYXJrZXIuc3ZnIj4gIDxkZWZzICAgICBpZD0iZGVmczE5MTY5IiAvPiAgPHNvZGlwb2RpOm5hbWVkdmlldyAgICAgaWQ9ImJhc2UiICAgICBwYWdlY29sb3I9IiNmZmZmZmYiICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIgICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIgICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiAgICAgaW5rc2NhcGU6em9vbT0iMTYiICAgICBpbmtzY2FwZTpjeD0iMTQuMTY0MjUzIiAgICAgaW5rc2NhcGU6Y3k9IjguODg1NzIiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0iZmFsc2UiICAgICB1bml0cz0icHgiICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEyODAiICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI3NTEiICAgICBpbmtzY2FwZTp3aW5kb3cteD0iMjA4IiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjE5MCIgICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiICAgICBpbmtzY2FwZTpvYmplY3Qtbm9kZXM9InRydWUiPiAgICA8aW5rc2NhcGU6Z3JpZCAgICAgICB0eXBlPSJ4eWdyaWQiICAgICAgIGlkPSJncmlkMTk3MTUiIC8+ICA8L3NvZGlwb2RpOm5hbWVkdmlldz4gIDxtZXRhZGF0YSAgICAgaWQ9Im1ldGFkYXRhMTkxNzIiPiAgICA8cmRmOlJERj4gICAgICA8Y2M6V29yayAgICAgICAgIHJkZjphYm91dD0iIj4gICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PiAgICAgICAgPGRjOnR5cGUgICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+ICAgICAgICA8ZGM6dGl0bGUgLz4gICAgICA8L2NjOldvcms+ICAgIDwvcmRmOlJERj4gIDwvbWV0YWRhdGE+ICA8ZyAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIiAgICAgaWQ9ImxheWVyMSIgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTEwMzIuMzYyMikiPiAgICA8cGF0aCAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtjbGlwLXJ1bGU6bm9uemVybztkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO3Zpc2liaWxpdHk6dmlzaWJsZTtvcGFjaXR5OjE7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2NvbG9yLWludGVycG9sYXRpb246c1JHQjtjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM6bGluZWFyUkdCO3NvbGlkLWNvbG9yOiMwMDAwMDA7c29saWQtb3BhY2l0eToxO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDtzdHJva2Utb3BhY2l0eToxO21hcmtlcjpub25lO2NvbG9yLXJlbmRlcmluZzphdXRvO2ltYWdlLXJlbmRlcmluZzphdXRvO3NoYXBlLXJlbmRlcmluZzphdXRvO3RleHQtcmVuZGVyaW5nOmF1dG87ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIgICAgICAgZD0ibSAzNiwxMDQwLjM2MjIgYyA2ZS02LDMuMzA5MyAtNS45ODg2MTIsMTAgLTUuOTg4NjEyLDEwIDAsMCAtNS45OTg3NzYsLTYuNjY4IC02LjAxMTM0NSwtOS45NzcyIC0wLjAxMjU3LC0zLjMwOTIgMi42NTY1NzYsLTYuMDAzOSA1Ljk2NTc5MiwtNi4wMjI3IDMuMzA5MTg5LC0wLjAxOSA2LjAwODg0LDIuNjQ1MiA2LjAzMzk5Miw1Ljk1NDMiICAgICAgIGlkPSJwYXRoMTI1NjEiICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2Nzc2MiIC8+ICAgIDxwYXRoICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2NsaXAtcnVsZTpub256ZXJvO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO29wYWNpdHk6MTtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7Y29sb3ItaW50ZXJwb2xhdGlvbjpzUkdCO2NvbG9yLWludGVycG9sYXRpb24tZmlsdGVyczpsaW5lYXJSR0I7c29saWQtY29sb3I6IzAwMDAwMDtzb2xpZC1vcGFjaXR5OjE7ZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO3N0cm9rZS1vcGFjaXR5OjE7bWFya2VyOm5vbmU7Y29sb3ItcmVuZGVyaW5nOmF1dG87aW1hZ2UtcmVuZGVyaW5nOmF1dG87c2hhcGUtcmVuZGVyaW5nOmF1dG87dGV4dC1yZW5kZXJpbmc6YXV0bztlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBkPSJtIDM0LjAwMDExNSwxMDQwLjM2MjIgYyAtNWUtNiwyLjIwNjIgLTMuOTkyNTIzLDcuMDAwMSAtMy45OTI1MjMsNy4wMDAxIDAsMCAtMy45OTkyOTEsLTQuNzc4NyAtNC4wMDc2NzksLTYuOTg0OSAtMC4wMDg0LC0yLjIwNjIgMS43NzEwODIsLTQuMDAyNyAzLjk3NzMxLC00LjAxNTMgMi4yMDYyMSwtMC4wMTMgNC4wMDYwMzcsMS43NjM1IDQuMDIyNzc3LDMuOTY5NyIgICAgICAgaWQ9InBhdGgxMjU2MyIgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2NzYyIgLz4gICAgPHBhdGggICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7Y2xpcC1ydWxlOm5vbnplcm87ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTt2aXNpYmlsaXR5OnZpc2libGU7b3BhY2l0eToxO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtjb2xvci1pbnRlcnBvbGF0aW9uOnNSR0I7Y29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzOmxpbmVhclJHQjtzb2xpZC1jb2xvcjojMDAwMDAwO3NvbGlkLW9wYWNpdHk6MTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7c3Ryb2tlLW9wYWNpdHk6MTttYXJrZXI6bm9uZTtjb2xvci1yZW5kZXJpbmc6YXV0bztpbWFnZS1yZW5kZXJpbmc6YXV0bztzaGFwZS1yZW5kZXJpbmc6YXV0bzt0ZXh0LXJlbmRlcmluZzphdXRvO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGQ9Ik0gOS45NjY3OTY5LDEwMTQuMzYyMiBDIDYuNjU3NTgwOSwxMDE0LjM4MSAzLjk4NzQzLDEwMTcuMDc2NCA0LDEwMjAuMzg1NiBjIDAuMDEyNTY5LDMuMzA5MiA2LjAxMTcxOSw4Ljk3NjYgNi4wMTE3MTksOC45NzY2IDAsMCA1Ljk4ODI4NywtNS42OTA3IDUuOTg4MjgxLC05IGwgMCwtMC4wNDUgYyAtMC4wMjUxNSwtMy4zMDkxIC0yLjcyNDAxNCwtNS45NzQxIC02LjAzMzIwMzEsLTUuOTU1MSB6IG0gMC4wMDk3NywyIGMgMi4yMDYyMDYxLC0wLjAxMyA0LjAwNjY5MzEsMS43NjI2IDQuMDIzNDMzMSwzLjk2ODggbCAwLDAuMDMxIGMgLTVlLTYsMi4yMDYyIC0zLjk5MjE4OCw2IC0zLjk5MjE4OCw2IDAsMCAtMy45OTk0MjQsLTMuNzc4MiAtNC4wMDc4MTIsLTUuOTg0NCAtMC4wMDg0LC0yLjIwNjIgMS43NzAzMzQ1LC00LjAwMyAzLjk3NjU2MjUsLTQuMDE1NiB6IiAgICAgICBpZD0icGF0aDEyNTY4IiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzY3NjY2Njc2NzYyIgLz4gICAgPHBhdGggICAgICAgc3R5bGU9Im9wYWNpdHk6MTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46YmV2ZWw7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDtzdHJva2Utb3BhY2l0eToxO21hcmtlcjpub25lIiAgICAgICBkPSJNIDEwIDIgQyA2LjY4NjI5MiAyIDQgNC42ODYzIDQgOCBDIDQgMTEuMzEzNyAxMCAxNyAxMCAxNyBDIDEwIDE3IDE2IDExLjMxMzcgMTYgOCBDIDE2IDQuNjg2MyAxMy4zMTM3MDggMiAxMCAyIHogTSAxMCA0IEMgMTIuMDcxMDY4IDQgMTMuNzUgNS42Nzg5IDEzLjc1IDcuNzUgQyAxMy43NSA5LjIwNTMyNzggMTEuOTMxMTEgMTEuNjQ0MzkzIDEwLjgzMDA3OCAxMyBMIDkuMTY5OTIxOSAxMyBDIDguMDY4ODkwMyAxMS42NDQzOTMgNi4yNSA5LjIwNTMyNzggNi4yNSA3Ljc1IEMgNi4yNSA1LjY3ODkgNy45Mjg5MzIgNCAxMCA0IHogIiAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLDEwMzIuMzYyMikiICAgICAgIGlkPSJwYXRoMTczMDUiIC8+ICA8L2c+PC9zdmc+); 20 | } 21 | 22 | .mapbox-gl-draw_line { 23 | background-repeat: no-repeat; 24 | background-position: center; 25 | pointer-events: auto; 26 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJsaW5lLnN2ZyI+ICA8ZGVmcyAgICAgaWQ9ImRlZnMxOTE2OSIgLz4gIDxzb2RpcG9kaTpuYW1lZHZpZXcgICAgIGlkPSJiYXNlIiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiICAgICBib3JkZXJvcGFjaXR5PSIxLjAiICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIgICAgIGlua3NjYXBlOnpvb209IjE2IiAgICAgaW5rc2NhcGU6Y3g9IjEyLjg5ODc3NSIgICAgIGlua3NjYXBlOmN5PSI5LjU4OTAxNTIiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0idHJ1ZSIgICAgIHVuaXRzPSJweCIgICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTI4MCIgICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc1MSIgICAgIGlua3NjYXBlOndpbmRvdy14PSIwIiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjIzIiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIgICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+ICAgIDxpbmtzY2FwZTpncmlkICAgICAgIHR5cGU9Inh5Z3JpZCIgICAgICAgaWQ9ImdyaWQxOTcxNSIgLz4gIDwvc29kaXBvZGk6bmFtZWR2aWV3PiAgPG1ldGFkYXRhICAgICBpZD0ibWV0YWRhdGExOTE3MiI+ICAgIDxyZGY6UkRGPiAgICAgIDxjYzpXb3JrICAgICAgICAgcmRmOmFib3V0PSIiPiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+ICAgICAgICA8ZGM6dHlwZSAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4gICAgICAgIDxkYzp0aXRsZSAvPiAgICAgIDwvY2M6V29yaz4gICAgPC9yZGY6UkRGPiAgPC9tZXRhZGF0YT4gIDxnICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIgICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiICAgICBpZD0ibGF5ZXIxIiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTAzMi4zNjIyKSI+ICAgIDxwYXRoICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MzttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBkPSJtIDEzLjUsMTAzNS44NjIyIGMgLTEuMzgwNzEyLDAgLTIuNSwxLjExOTMgLTIuNSwyLjUgMCwwLjMyMDggMC4wNDYxNCwwLjYyNDQgMC4xNTYyNSwwLjkwNjMgbCAtMy43NSwzLjc1IGMgLTAuMjgxODM2LC0wLjExMDIgLTAuNTg1NDIxLC0wLjE1NjMgLTAuOTA2MjUsLTAuMTU2MyAtMS4zODA3MTIsMCAtMi41LDEuMTE5MyAtMi41LDIuNSAwLDEuMzgwNyAxLjExOTI4OCwyLjUgMi41LDIuNSAxLjM4MDcxMiwwIDIuNSwtMS4xMTkzIDIuNSwtMi41IDAsLTAuMzIwOCAtMC4wNDYxNCwtMC42MjQ0IC0wLjE1NjI1LC0wLjkwNjIgbCAzLjc1LC0zLjc1IGMgMC4yODE4MzYsMC4xMTAxIDAuNTg1NDIxLDAuMTU2MiAwLjkwNjI1LDAuMTU2MiAxLjM4MDcxMiwwIDIuNSwtMS4xMTkzIDIuNSwtMi41IDAsLTEuMzgwNyAtMS4xMTkyODgsLTIuNSAtMi41LC0yLjUgeiIgICAgICAgaWQ9InJlY3Q2NDY3IiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPiAgPC9nPjwvc3ZnPg==); 27 | } 28 | 29 | .mapbox-gl-draw_polygon { 30 | background-repeat: no-repeat; 31 | background-position: center; 32 | pointer-events: auto; 33 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJzcXVhcmUuc3ZnIj4gIDxkZWZzICAgICBpZD0iZGVmczE5MTY5IiAvPiAgPHNvZGlwb2RpOm5hbWVkdmlldyAgICAgaWQ9ImJhc2UiICAgICBwYWdlY29sb3I9IiNmZmZmZmYiICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIgICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIgICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiAgICAgaW5rc2NhcGU6em9vbT0iMTEuMzEzNzA4IiAgICAgaW5rc2NhcGU6Y3g9IjExLjY4MTYzNCIgICAgIGlua3NjYXBlOmN5PSI5LjI4NTcxNDMiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0idHJ1ZSIgICAgIHVuaXRzPSJweCIgICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTI4MCIgICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc1MSIgICAgIGlua3NjYXBlOndpbmRvdy14PSIwIiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjIzIiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIgICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+ICAgIDxpbmtzY2FwZTpncmlkICAgICAgIHR5cGU9Inh5Z3JpZCIgICAgICAgaWQ9ImdyaWQxOTcxNSIgLz4gIDwvc29kaXBvZGk6bmFtZWR2aWV3PiAgPG1ldGFkYXRhICAgICBpZD0ibWV0YWRhdGExOTE3MiI+ICAgIDxyZGY6UkRGPiAgICAgIDxjYzpXb3JrICAgICAgICAgcmRmOmFib3V0PSIiPiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+ICAgICAgICA8ZGM6dHlwZSAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4gICAgICAgIDxkYzp0aXRsZSAvPiAgICAgIDwvY2M6V29yaz4gICAgPC9yZGY6UkRGPiAgPC9tZXRhZGF0YT4gIDxnICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIgICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiICAgICBpZD0ibGF5ZXIxIiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTAzMi4zNjIyKSI+ICAgIDxwYXRoICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC41O21hcmtlcjpub25lO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGQ9Im0gNSwxMDM5LjM2MjIgMCw2IDIsMiA2LDAgMiwtMiAwLC02IC0yLC0yIC02LDAgeiBtIDMsMCA0LDAgMSwxIDAsNCAtMSwxIC00LDAgLTEsLTEgMCwtNCB6IiAgICAgICBpZD0icmVjdDc3OTciICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2NjY2NjY2NjY2NjY2NjIiAvPiAgICA8Y2lyY2xlICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MS42MDAwMDAwMjttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBpZD0icGF0aDQzNjQiICAgICAgIGN4PSI2IiAgICAgICBjeT0iMTA0Ni4zNjIyIiAgICAgICByPSIyIiAvPiAgICA8Y2lyY2xlICAgICAgIGlkPSJwYXRoNDM2OCIgICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTt2aXNpYmlsaXR5OnZpc2libGU7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDoxLjYwMDAwMDAyO21hcmtlcjpub25lO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGN4PSIxNCIgICAgICAgY3k9IjEwNDYuMzYyMiIgICAgICAgcj0iMiIgLz4gICAgPGNpcmNsZSAgICAgICBpZD0icGF0aDQzNzAiICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MS42MDAwMDAwMjttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBjeD0iNiIgICAgICAgY3k9IjEwMzguMzYyMiIgICAgICAgcj0iMiIgLz4gICAgPGNpcmNsZSAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO3Zpc2liaWxpdHk6dmlzaWJsZTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjEuNjAwMDAwMDI7bWFya2VyOm5vbmU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIgICAgICAgaWQ9InBhdGg0MzcyIiAgICAgICBjeD0iMTQiICAgICAgIGN5PSIxMDM4LjM2MjIiICAgICAgIHI9IjIiIC8+ICA8L2c+PC9zdmc+); 34 | } 35 | 36 | .highlight { 37 | background-color: green; 38 | } 39 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 276 | 277 | 617 | -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/README.md: -------------------------------------------------------------------------------- 1 | ## Mapbox GL Draw Rectangle Assisted Mode 2 | [![npm version](https://badge.fury.io/js/%40geostarters%2Fmapbox-gl-draw-rectangle-assisted-mode.svg)](https://badge.fury.io/js/%40geostarters%2Fmapbox-gl-draw-rectangle-assisted-mode) 3 | 4 | This is a custom mode for (Mapbox GL Draw) [https://github.com/mapbox/mapbox-gl-draw] that adds the functionality to draw assisted rectangles. 5 | 6 | 7 | ![Assisted rectangle](draw-assisted-rectangle.gif) 8 | 9 | #### Changelog: 10 | 11 | ```bash 12 | Version 3.0.4: Change to Mapbox GL js v1.6.0 and fix minor dependencies 13 | Version 3.0.3: Change to Mapbox GL js v1.4.0 14 | Version 3.0.2: Add custom draw rectangle style 15 | Version 3.0.1: Add orientation angle calculation 16 | Version 3.0.0: Draw strict rentangle mode 17 | ``` 18 | Based on: 19 | 20 | https://github.com/thegisdev/mapbox-gl-draw-rectangle-mode 21 | 22 | https://github.com/mapbox/mapbox-gl-draw/blob/master/src/modes/draw_polygon.js 23 | 24 | 25 | ### Install 26 | 27 | `npm install @geostarters/mapbox-gl-draw-rectangle-assisted-mode` 28 | 29 | ### Page Demo 30 | 31 | https://geostarters.github.io/mapbox-gl-draw-assisted-rectangle-mode/index.html 32 | 33 | ### Usage 34 | 35 | ```js 36 | import DrawRectangle from 'mapbox-gl-draw-rectangle-assisted-mode'; 37 | 38 | mapboxgl.accessToken = ''; 39 | const map = new mapboxgl.Map({ 40 | container: 'map', 41 | style: 'https://tilemaps.icgc.cat/tileserver/styles/water.json', 42 | center: [-122.419518, 37.772995], 43 | zoom: 17, 44 | hash: true 45 | }); 46 | 47 | const modes = MapboxDraw.modes; 48 | modes.draw_assisted_rectangle = DrawAssistedRectangle.default; 49 | 50 | const draw = new MapboxDraw({ 51 | modes: modes, 52 | displayControlsDefault: false, 53 | controls: { 54 | polygon: true, 55 | trash: true 56 | }, 57 | userProperties: true, 58 | styles: STYLES_DRAW 59 | }); 60 | map.addControl(draw); 61 | map.on('draw.create', function (feature) { 62 | console.log(feature); 63 | }); 64 | 65 | ``` 66 | 67 | 68 | 69 | ### Build 70 | 71 | `npm build-web` with browsify 72 | 73 | `npm build-all` with babel 74 | 75 | ### License 76 | 77 | MIT 78 | 79 | ### Credits 80 | 81 | Developed by @ICGCAT 82 | 83 | More Info 84 | >[https://openicgc.github.io/](https://openicgc.github.io/) 85 | 86 | >[https://github.com/geostarters](https://github.com/geostarters) 87 | 88 | >[http://betaportal.icgc.cat/](http://betaportal.icgc.cat/) 89 | 90 | >[http://www.icgc.cat/en/](http://www.icgc.cat/en/) 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/dist/DrawAssistedRectangle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var doubleClickZoom = { 8 | enable: function enable(ctx) { 9 | setTimeout(function () { 10 | // First check we've got a map and some context. 11 | if (!ctx.map || !ctx.map.doubleClickZoom || !ctx._ctx || !ctx._ctx.store || !ctx._ctx.store.getInitialConfigValue) return; 12 | 13 | if (!ctx._ctx.store.getInitialConfigValue("doubleClickZoom")) return; 14 | ctx.map.doubleClickZoom.enable(); 15 | }, 0); 16 | }, 17 | disable: function disable(ctx) { 18 | setTimeout(function () { 19 | if (!ctx.map || !ctx.map.doubleClickZoom) return; 20 | 21 | ctx.map.doubleClickZoom.disable(); 22 | }, 0); 23 | } 24 | }; 25 | 26 | var DrawAssistedRectangle = { 27 | 28 | onSetup: function onSetup(opts) { 29 | var rectangle = this.newFeature({ 30 | type: "Feature", 31 | properties: {}, 32 | geometry: { 33 | type: "Polygon", 34 | coordinates: [[]] 35 | } 36 | }); 37 | this.addFeature(rectangle); 38 | 39 | this.clearSelectedFeatures(); 40 | doubleClickZoom.disable(this); 41 | this.updateUIClasses({ 42 | mouse: "add" 43 | }); 44 | this.setActionableState({ 45 | trash: true 46 | }); 47 | return { 48 | rectangle: rectangle, 49 | currentVertexPosition: 0 50 | }; 51 | }, 52 | 53 | onTap: function onTap(state, e) { 54 | 55 | this.onClick(state, e); 56 | }, 57 | 58 | onClick: function onClick(state, e) { 59 | 60 | if (state.currentVertexPosition === 2) { 61 | 62 | var getpXY3 = this.calculatepXY3(state, e, false); 63 | 64 | if (getpXY3) { 65 | state.rectangle.updateCoordinate("0." + (state.currentVertexPosition + 1), getpXY3[0], getpXY3[1]); 66 | } 67 | this.updateUIClasses({ 68 | mouse: "pointer" 69 | }); 70 | return this.changeMode("simple_select", { 71 | featuresId: state.rectangle.id 72 | }); 73 | } else { 74 | 75 | state.rectangle.updateCoordinate("0." + state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat); 76 | state.currentVertexPosition++; 77 | state.rectangle.updateCoordinate("0." + state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat); 78 | } 79 | }, 80 | onMouseMove: function onMouseMove(state, e) { 81 | 82 | state.rectangle.updateCoordinate("0." + state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat); 83 | if (state.currentVertexPosition && state.currentVertexPosition > 0) { 84 | 85 | this.calculateOrientedAnglePolygon(state); 86 | } 87 | 88 | if (state.currentVertexPosition === 2) { 89 | var getpXY3 = this.calculatepXY3(state, e, true); 90 | if (getpXY3) { 91 | state.rectangle.updateCoordinate("0." + (state.currentVertexPosition + 1), getpXY3[0], getpXY3[1]); 92 | } 93 | } 94 | }, 95 | 96 | deegrees2meters: function deegrees2meters(px) { 97 | 98 | //gist from https://gist.github.com/springmeyer/871897 99 | var x = px[0] * 20037508.34 / 180; 100 | var y = Math.log(Math.tan((90 + px[1]) * Math.PI / 360)) / (Math.PI / 180); 101 | y = y * 20037508.34 / 180; 102 | return [x, y]; 103 | }, 104 | meters2degress: function meters2degress(px) { 105 | //gist from https://gist.github.com/springmeyer/871897 106 | var lon = px[0] * 180 / 20037508.34; 107 | var lat = Math.atan(Math.exp(px[1] * Math.PI / 20037508.34)) * 360 / Math.PI - 90; 108 | return [lon, lat]; 109 | }, 110 | 111 | 112 | calculateOrientedAnglePolygon: function calculateOrientedAnglePolygon(state) { 113 | var pXY0 = state.rectangle.getCoordinate("0.0"); 114 | var pXY0_3857 = this.deegrees2meters(pXY0); 115 | var pXY1 = state.rectangle.getCoordinate("0.1"); 116 | var pXY1_3857 = this.deegrees2meters(pXY1); 117 | 118 | var angleStdGraus = Math.atan2(pXY1_3857[1] - pXY0_3857[1], pXY1_3857[0] - pXY0_3857[0]) * 180 / Math.PI; 119 | var angleSudGraus = -1.0 * (angleStdGraus + 90); 120 | var angle = angleSudGraus < 0 ? angleSudGraus + 360 : angleSudGraus; 121 | 122 | state.angle = parseFloat(angle.toFixed(2)); 123 | }, 124 | 125 | calculatepXY3: function calculatepXY3(state, e, tmp) { 126 | 127 | var pXY0 = state.rectangle.getCoordinate("0.0"); 128 | var pXY0_3857 = this.deegrees2meters(pXY0); 129 | var pXY1 = state.rectangle.getCoordinate("0.1"); 130 | var pXY1_3857 = this.deegrees2meters(pXY1); 131 | var pXY2_3857 = this.deegrees2meters([e.lngLat.lng, e.lngLat.lat]); 132 | var mouse_3857 = this.deegrees2meters([e.lngLat.lng, e.lngLat.lat]); 133 | 134 | if (pXY0_3857[0] === pXY1_3857[0]) { 135 | pXY2_3857 = [mouse_3857[0], pXY1_3857[1]]; 136 | } else if (pXY0_3857[1] === pXY1_3857[1]) { 137 | pXY2_3857 = [pXY1_3857[0], mouse_3857[1]]; 138 | } else { 139 | 140 | var vector1_3857 = (pXY1_3857[1] - pXY0_3857[1]) / (pXY1_3857[0] - pXY0_3857[0]); 141 | var vector2_3857 = -1.0 / vector1_3857; 142 | 143 | if (Math.abs(vector2_3857) < 1) { 144 | pXY2_3857[1] = vector2_3857 * (mouse_3857[0] - pXY1_3857[0]) + pXY1_3857[1]; 145 | } else { 146 | pXY2_3857[0] = pXY1_3857[0] + (pXY2_3857[1] - pXY1_3857[1]) / vector2_3857; 147 | } 148 | } 149 | 150 | var vector_3857 = [pXY1_3857[0] - pXY0_3857[0], pXY1_3857[1] - pXY0_3857[1]]; 151 | var pXY3_3857 = [pXY2_3857[0] - vector_3857[0], pXY2_3857[1] - vector_3857[1]]; 152 | var pXY2G = this.meters2degress(pXY2_3857); 153 | var pXY3G = this.meters2degress(pXY3_3857); 154 | state.rectangle.updateCoordinate("0.2", pXY2G[0], pXY2G[1]); 155 | state.rectangle.updateCoordinate("0.3", pXY3G[0], pXY3G[1]); 156 | 157 | return pXY3G; 158 | }, 159 | 160 | onKeyUp: function onKeyUp(state, e) { 161 | if (e.keyCode === 27) return this.changeMode("simple_select"); 162 | }, 163 | onStop: function onStop(state) { 164 | doubleClickZoom.enable(this); 165 | this.updateUIClasses({ 166 | mouse: "none" 167 | }); 168 | this.activateUIButton(); 169 | 170 | // check to see if we've deleted this feature 171 | if (this.getFeature(state.rectangle.id) === undefined) return; 172 | 173 | //remove last added coordinate 174 | state.rectangle.removeCoordinate("0.4"); 175 | if (state.rectangle.isValid()) { 176 | this.map.fire("draw.create", { 177 | features: [state.rectangle.toGeoJSON()] 178 | }); 179 | } else { 180 | this.deleteFeature([state.rectangle.id], { 181 | silent: true 182 | }); 183 | this.changeMode("simple_select", {}, { 184 | silent: true 185 | }); 186 | } 187 | }, 188 | toDisplayFeatures: function toDisplayFeatures(state, geojson, display) { 189 | var isActivePolygon = geojson.properties.id === state.rectangle.id; 190 | geojson.properties.active = isActivePolygon ? "true" : "false"; 191 | geojson.properties.angle = state.angle; 192 | geojson.angle = state.angle; 193 | if (!isActivePolygon) return display(geojson); 194 | 195 | var coordinateCount = geojson.geometry.coordinates[0].length; 196 | 197 | if (coordinateCount < 3) { 198 | 199 | var coordinates = geojson.geometry.coordinates[0][0]; 200 | 201 | var vertexPoint = { 202 | type: "Feature", 203 | properties: geojson.properties, 204 | angle: state.angle, 205 | geometry: { 206 | coordinates: geojson.geometry.coordinates[0][0], 207 | type: "Point" 208 | } 209 | }; 210 | 211 | if (coordinates) { 212 | display(vertexPoint); 213 | } 214 | 215 | return; 216 | } 217 | if (coordinateCount >= 3 && coordinateCount <= 4) { 218 | 219 | var lineCoordinates = [[geojson.geometry.coordinates[0][0][0], geojson.geometry.coordinates[0][0][1]], [geojson.geometry.coordinates[0][1][0], geojson.geometry.coordinates[0][1][1]]]; 220 | 221 | display({ 222 | type: "Feature", 223 | properties: geojson.properties, 224 | angle: state.angle, 225 | geometry: { 226 | coordinates: lineCoordinates, 227 | type: "LineString" 228 | } 229 | }); 230 | if (coordinateCount === 3) { 231 | return; 232 | } 233 | } 234 | 235 | return display(geojson); 236 | }, 237 | onTrash: function onTrash(state) { 238 | this.deleteFeature([state.rectangle.id], { 239 | silent: true 240 | }); 241 | this.changeMode("simple_select"); 242 | } 243 | }; 244 | 245 | exports.default = DrawAssistedRectangle; -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/dist/DrawAssistedRectangle.min.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.DrawAssistedRectangle = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0) { 85 | 86 | this.calculateOrientedAnglePolygon(state); 87 | } 88 | 89 | if (state.currentVertexPosition === 2) { 90 | var getpXY3 = this.calculatepXY3(state, e, true); 91 | if (getpXY3) { 92 | state.rectangle.updateCoordinate("0." + (state.currentVertexPosition + 1), getpXY3[0], getpXY3[1]); 93 | } 94 | } 95 | }, 96 | 97 | deegrees2meters: function deegrees2meters(px) { 98 | 99 | //gist from https://gist.github.com/springmeyer/871897 100 | var x = px[0] * 20037508.34 / 180; 101 | var y = Math.log(Math.tan((90 + px[1]) * Math.PI / 360)) / (Math.PI / 180); 102 | y = y * 20037508.34 / 180; 103 | return [x, y]; 104 | }, 105 | meters2degress: function meters2degress(px) { 106 | //gist from https://gist.github.com/springmeyer/871897 107 | var lon = px[0] * 180 / 20037508.34; 108 | var lat = Math.atan(Math.exp(px[1] * Math.PI / 20037508.34)) * 360 / Math.PI - 90; 109 | return [lon, lat]; 110 | }, 111 | 112 | 113 | calculateOrientedAnglePolygon: function calculateOrientedAnglePolygon(state) { 114 | var pXY0 = state.rectangle.getCoordinate("0.0"); 115 | var pXY0_3857 = this.deegrees2meters(pXY0); 116 | var pXY1 = state.rectangle.getCoordinate("0.1"); 117 | var pXY1_3857 = this.deegrees2meters(pXY1); 118 | 119 | var angleStdGraus = Math.atan2(pXY1_3857[1] - pXY0_3857[1], pXY1_3857[0] - pXY0_3857[0]) * 180 / Math.PI; 120 | var angleSudGraus = -1.0 * (angleStdGraus + 90); 121 | var angle = angleSudGraus < 0 ? angleSudGraus + 360 : angleSudGraus; 122 | 123 | state.angle = parseFloat(angle.toFixed(2)); 124 | }, 125 | 126 | calculatepXY3: function calculatepXY3(state, e, tmp) { 127 | 128 | var pXY0 = state.rectangle.getCoordinate("0.0"); 129 | var pXY0_3857 = this.deegrees2meters(pXY0); 130 | var pXY1 = state.rectangle.getCoordinate("0.1"); 131 | var pXY1_3857 = this.deegrees2meters(pXY1); 132 | var pXY2_3857 = this.deegrees2meters([e.lngLat.lng, e.lngLat.lat]); 133 | var mouse_3857 = this.deegrees2meters([e.lngLat.lng, e.lngLat.lat]); 134 | 135 | if (pXY0_3857[0] === pXY1_3857[0]) { 136 | pXY2_3857 = [mouse_3857[0], pXY1_3857[1]]; 137 | } else if (pXY0_3857[1] === pXY1_3857[1]) { 138 | pXY2_3857 = [pXY1_3857[0], mouse_3857[1]]; 139 | } else { 140 | 141 | var vector1_3857 = (pXY1_3857[1] - pXY0_3857[1]) / (pXY1_3857[0] - pXY0_3857[0]); 142 | var vector2_3857 = -1.0 / vector1_3857; 143 | 144 | if (Math.abs(vector2_3857) < 1) { 145 | pXY2_3857[1] = vector2_3857 * (mouse_3857[0] - pXY1_3857[0]) + pXY1_3857[1]; 146 | } else { 147 | pXY2_3857[0] = pXY1_3857[0] + (pXY2_3857[1] - pXY1_3857[1]) / vector2_3857; 148 | } 149 | } 150 | 151 | var vector_3857 = [pXY1_3857[0] - pXY0_3857[0], pXY1_3857[1] - pXY0_3857[1]]; 152 | var pXY3_3857 = [pXY2_3857[0] - vector_3857[0], pXY2_3857[1] - vector_3857[1]]; 153 | var pXY2G = this.meters2degress(pXY2_3857); 154 | var pXY3G = this.meters2degress(pXY3_3857); 155 | state.rectangle.updateCoordinate("0.2", pXY2G[0], pXY2G[1]); 156 | state.rectangle.updateCoordinate("0.3", pXY3G[0], pXY3G[1]); 157 | 158 | return pXY3G; 159 | }, 160 | 161 | onKeyUp: function onKeyUp(state, e) { 162 | if (e.keyCode === 27) return this.changeMode("simple_select"); 163 | }, 164 | onStop: function onStop(state) { 165 | doubleClickZoom.enable(this); 166 | this.updateUIClasses({ 167 | mouse: "none" 168 | }); 169 | this.activateUIButton(); 170 | 171 | // check to see if we've deleted this feature 172 | if (this.getFeature(state.rectangle.id) === undefined) return; 173 | 174 | //remove last added coordinate 175 | state.rectangle.removeCoordinate("0.4"); 176 | if (state.rectangle.isValid()) { 177 | this.map.fire("draw.create", { 178 | features: [state.rectangle.toGeoJSON()] 179 | }); 180 | } else { 181 | this.deleteFeature([state.rectangle.id], { 182 | silent: true 183 | }); 184 | this.changeMode("simple_select", {}, { 185 | silent: true 186 | }); 187 | } 188 | }, 189 | toDisplayFeatures: function toDisplayFeatures(state, geojson, display) { 190 | var isActivePolygon = geojson.properties.id === state.rectangle.id; 191 | geojson.properties.active = isActivePolygon ? "true" : "false"; 192 | geojson.properties.angle = state.angle; 193 | geojson.angle = state.angle; 194 | if (!isActivePolygon) return display(geojson); 195 | 196 | var coordinateCount = geojson.geometry.coordinates[0].length; 197 | 198 | if (coordinateCount < 3) { 199 | 200 | var coordinates = geojson.geometry.coordinates[0][0]; 201 | 202 | var vertexPoint = { 203 | type: "Feature", 204 | properties: geojson.properties, 205 | angle: state.angle, 206 | geometry: { 207 | coordinates: geojson.geometry.coordinates[0][0], 208 | type: "Point" 209 | } 210 | }; 211 | 212 | if (coordinates) { 213 | display(vertexPoint); 214 | } 215 | 216 | return; 217 | } 218 | if (coordinateCount >= 3 && coordinateCount <= 4) { 219 | 220 | var lineCoordinates = [[geojson.geometry.coordinates[0][0][0], geojson.geometry.coordinates[0][0][1]], [geojson.geometry.coordinates[0][1][0], geojson.geometry.coordinates[0][1][1]]]; 221 | 222 | display({ 223 | type: "Feature", 224 | properties: geojson.properties, 225 | angle: state.angle, 226 | geometry: { 227 | coordinates: lineCoordinates, 228 | type: "LineString" 229 | } 230 | }); 231 | if (coordinateCount === 3) { 232 | return; 233 | } 234 | } 235 | 236 | return display(geojson); 237 | }, 238 | onTrash: function onTrash(state) { 239 | this.deleteFeature([state.rectangle.id], { 240 | silent: true 241 | }); 242 | this.changeMode("simple_select"); 243 | } 244 | }; 245 | 246 | exports.default = DrawAssistedRectangle; 247 | },{}]},{},[1])(1) 248 | }); 249 | -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/draw-assisted-rectangle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/src/mapbox-gl-draw-assisted-rectangle-mode/draw-assisted-rectangle.gif -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mapbox GL Draw Assisted Rectangle 8 | 9 | 10 | 11 | 12 | 14 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geostarters/mapbox-gl-draw-rectangle-assisted-mode", 3 | "version": "3.0.4", 4 | "description": "A custom mode for MapboxGL Draw to draw a assisted rentangle", 5 | "main": "dist/DrawAssistedRectangle.js", 6 | "author": "Geostarters - ICGC", 7 | "license": "MIT", 8 | "repository": "https://github.com/geostarters/mapbox-gl-draw-assisted-rectangle-mode", 9 | "keywords": [ 10 | "MapBox GL", 11 | "Mapbox-gl-Draw", 12 | "Custom mode", 13 | "Assisted Rectangle", 14 | "Vector Tiles" 15 | ], 16 | "scripts": { 17 | "build": "babel src -d dist", 18 | "build-web": "browserify dist/DrawAssistedRectangle.js --standalone DrawAssistedRectangle --outfile dist/DrawAssistedRectangle.min.js", 19 | "build-all": "run-s build build-web" 20 | }, 21 | "devDependencies": { 22 | "babel": "^6.23.0", 23 | "babel-cli": "^6.26.0", 24 | "babel-core": "^6.25.0", 25 | "babel-preset-es2015": "^6.24.1", 26 | "babel-preset-stage-2": "^6.24.1", 27 | "npm-run-all": "^4.1.2" 28 | }, 29 | "dependencies": { 30 | "@babel/plugin-transform-modules-commonjs": "^7.6.0", 31 | "browserify": "^16.5.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/mapbox-gl-draw-assisted-rectangle-mode/src/DrawAssistedRectangle.js: -------------------------------------------------------------------------------- 1 | 2 | const doubleClickZoom = { 3 | enable: ctx => { 4 | setTimeout(() => { 5 | // First check we've got a map and some context. 6 | if ( 7 | !ctx.map || 8 | !ctx.map.doubleClickZoom || 9 | !ctx._ctx || 10 | !ctx._ctx.store || 11 | !ctx._ctx.store.getInitialConfigValue 12 | ) 13 | return; 14 | 15 | if (!ctx._ctx.store.getInitialConfigValue("doubleClickZoom")) return; 16 | ctx.map.doubleClickZoom.enable(); 17 | }, 0); 18 | }, 19 | disable(ctx) { 20 | setTimeout(() => { 21 | if (!ctx.map || !ctx.map.doubleClickZoom) return; 22 | 23 | ctx.map.doubleClickZoom.disable(); 24 | }, 0); 25 | } 26 | }; 27 | 28 | const DrawAssistedRectangle = { 29 | 30 | onSetup: function (opts) { 31 | const rectangle = this.newFeature({ 32 | type: "Feature", 33 | properties: {}, 34 | geometry: { 35 | type: "Polygon", 36 | coordinates: [ 37 | [] 38 | ] 39 | } 40 | }); 41 | this.addFeature(rectangle); 42 | 43 | this.clearSelectedFeatures(); 44 | doubleClickZoom.disable(this); 45 | this.updateUIClasses({ 46 | mouse: "add" 47 | }); 48 | this.setActionableState({ 49 | trash: true 50 | }); 51 | return { 52 | rectangle, 53 | currentVertexPosition: 0 54 | }; 55 | }, 56 | 57 | onTap: function (state, e) { 58 | 59 | this.onClick(state, e); 60 | }, 61 | 62 | onClick: function (state, e) { 63 | 64 | if (state.currentVertexPosition === 2) { 65 | 66 | const getpXY3 = this.calculatepXY3(state, e, false); 67 | 68 | if (getpXY3) { 69 | state.rectangle.updateCoordinate(`0.${state.currentVertexPosition + 1}`, getpXY3[0], getpXY3[1]); 70 | } 71 | this.updateUIClasses({ 72 | mouse: "pointer" 73 | }); 74 | return this.changeMode("simple_select", { 75 | featuresId: state.rectangle.id 76 | }); 77 | 78 | 79 | } else { 80 | 81 | state.rectangle.updateCoordinate(`0.${state.currentVertexPosition}`, e.lngLat.lng, e.lngLat.lat); 82 | state.currentVertexPosition++; 83 | state.rectangle.updateCoordinate(`0.${state.currentVertexPosition}`, e.lngLat.lng, e.lngLat.lat); 84 | 85 | } 86 | 87 | 88 | }, 89 | onMouseMove: function (state, e) { 90 | 91 | state.rectangle.updateCoordinate("0." + state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat); 92 | if (state.currentVertexPosition && state.currentVertexPosition > 0) { 93 | 94 | this.calculateOrientedAnglePolygon(state); 95 | 96 | } 97 | 98 | if (state.currentVertexPosition === 2) { 99 | const getpXY3 = this.calculatepXY3(state, e, true); 100 | 101 | if (getpXY3) { 102 | state.rectangle.updateCoordinate("0." + (state.currentVertexPosition + 1), getpXY3[0], getpXY3[1]); 103 | } 104 | } 105 | 106 | }, 107 | 108 | 109 | 110 | 111 | deegrees2meters(px) { 112 | 113 | //gist from https://gist.github.com/springmeyer/871897 114 | const x = px[0] * 20037508.34 / 180; 115 | let y = Math.log(Math.tan((90 + px[1]) * Math.PI / 360)) / (Math.PI / 180); 116 | y = y * 20037508.34 / 180; 117 | return [x, y] 118 | 119 | }, 120 | 121 | meters2degress(px) { 122 | //gist from https://gist.github.com/springmeyer/871897 123 | const lon = px[0] * 180 / 20037508.34; 124 | const lat = Math.atan(Math.exp(px[1] * Math.PI / 20037508.34)) * 360 / Math.PI - 90; 125 | return [lon, lat] 126 | }, 127 | 128 | calculateOrientedAnglePolygon: function (state) { 129 | const pXY0 = state.rectangle.getCoordinate("0.0"); 130 | const pXY0_3857 = this.deegrees2meters(pXY0); 131 | const pXY1 = state.rectangle.getCoordinate("0.1"); 132 | const pXY1_3857 = this.deegrees2meters(pXY1); 133 | const angleStdGraus = Math.atan2(pXY1_3857[1] - pXY0_3857[1], pXY1_3857[0] - pXY0_3857[0]) * 180 / Math.PI; 134 | 135 | let angleSudGraus = -1.0 * (angleStdGraus + 90); 136 | const angle = angleSudGraus < 0 ? angleSudGraus + 360 : angleSudGraus; 137 | 138 | state.angle = parseFloat((angle).toFixed(2)); 139 | 140 | }, 141 | 142 | calculatepXY3: function (state, e, tmp) { 143 | 144 | const pXY0 = state.rectangle.getCoordinate("0.0"); 145 | const pXY0_3857 = this.deegrees2meters(pXY0); 146 | const pXY1 = state.rectangle.getCoordinate("0.1"); 147 | const pXY1_3857 = this.deegrees2meters(pXY1); 148 | let pXY2_3857 = this.deegrees2meters([e.lngLat.lng, e.lngLat.lat]); 149 | const mouse_3857 = this.deegrees2meters([e.lngLat.lng, e.lngLat.lat]); 150 | 151 | if (pXY0_3857[0] === pXY1_3857[0]) { 152 | pXY2_3857 = [mouse_3857[0], pXY1_3857[1]]; 153 | } else if (pXY0_3857[1] === pXY1_3857[1]) { 154 | pXY2_3857 = [pXY1_3857[0], mouse_3857[1]]; 155 | 156 | } else { 157 | 158 | const vector1_3857 = (pXY1_3857[1] - pXY0_3857[1]) / (pXY1_3857[0] - pXY0_3857[0]); 159 | const vector2_3857 = -1.0 / vector1_3857; 160 | 161 | if (Math.abs(vector2_3857) < 1) { 162 | pXY2_3857[1] = vector2_3857 * (mouse_3857[0] - pXY1_3857[0]) + pXY1_3857[1]; 163 | } 164 | else { 165 | pXY2_3857[0] = pXY1_3857[0] + (pXY2_3857[1] - pXY1_3857[1]) / vector2_3857; 166 | } 167 | 168 | 169 | } 170 | 171 | const vector_3857 = [pXY1_3857[0] - pXY0_3857[0], pXY1_3857[1] - pXY0_3857[1]]; 172 | const pXY3_3857 = [pXY2_3857[0] - vector_3857[0], pXY2_3857[1] - vector_3857[1]]; 173 | const pXY2G = this.meters2degress(pXY2_3857); 174 | const pXY3G = this.meters2degress(pXY3_3857); 175 | state.rectangle.updateCoordinate("0.2", pXY2G[0], pXY2G[1]); 176 | state.rectangle.updateCoordinate("0.3", pXY3G[0], pXY3G[1]); 177 | 178 | return pXY3G; 179 | 180 | }, 181 | 182 | 183 | onKeyUp: function (state, e) { 184 | if (e.keyCode === 27) return this.changeMode("simple_select"); 185 | }, 186 | onStop: function (state) { 187 | doubleClickZoom.enable(this); 188 | this.updateUIClasses({ 189 | mouse: "none" 190 | }); 191 | this.activateUIButton(); 192 | 193 | // check to see if we've deleted this feature 194 | if (this.getFeature(state.rectangle.id) === undefined) return; 195 | 196 | //remove last added coordinate 197 | state.rectangle.removeCoordinate("0.4"); 198 | if (state.rectangle.isValid()) { 199 | this.map.fire("draw.create", { 200 | features: [state.rectangle.toGeoJSON()] 201 | }); 202 | } else { 203 | this.deleteFeature([state.rectangle.id], { 204 | silent: true 205 | }); 206 | this.changeMode("simple_select", {}, { 207 | silent: true 208 | }); 209 | } 210 | }, 211 | toDisplayFeatures: function (state, geojson, display) { 212 | const isActivePolygon = geojson.properties.id === state.rectangle.id; 213 | geojson.properties.active = isActivePolygon ? "true" : "false"; 214 | geojson.properties.angle = state.angle; 215 | geojson.angle = state.angle; 216 | if (!isActivePolygon) return display(geojson); 217 | 218 | const coordinateCount = geojson.geometry.coordinates[0].length; 219 | 220 | if (coordinateCount < 3) { 221 | 222 | const coordinates = geojson.geometry.coordinates[0][0]; 223 | 224 | const vertexPoint = { 225 | type: "Feature", 226 | properties: geojson.properties, 227 | angle: state.angle, 228 | geometry: { 229 | coordinates: geojson.geometry.coordinates[0][0], 230 | type: "Point" 231 | } 232 | }; 233 | 234 | if (coordinates) { 235 | display(vertexPoint); 236 | } 237 | 238 | 239 | return; 240 | } 241 | if (coordinateCount >= 3 && coordinateCount <= 4) { 242 | 243 | const lineCoordinates = [ 244 | [geojson.geometry.coordinates[0][0][0], geojson.geometry.coordinates[0][0][1]], 245 | [geojson.geometry.coordinates[0][1][0], geojson.geometry.coordinates[0][1][1]] 246 | ]; 247 | 248 | display({ 249 | type: "Feature", 250 | properties: geojson.properties, 251 | angle: state.angle, 252 | geometry: { 253 | coordinates: lineCoordinates, 254 | type: "LineString" 255 | } 256 | }); 257 | if (coordinateCount === 3) { 258 | return; 259 | } 260 | } 261 | 262 | return display(geojson); 263 | }, 264 | onTrash: function (state) { 265 | this.deleteFeature([state.rectangle.id], { 266 | silent: true 267 | }); 268 | this.changeMode("simple_select"); 269 | } 270 | }; 271 | 272 | export default DrawAssistedRectangle; 273 | -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/.ignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/Example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mapbox Add Control 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/Example/index.js: -------------------------------------------------------------------------------- 1 | import mapboxgl from 'mapbox-gl'; 2 | import 'mapbox-gl/dist/mapbox-gl.css' 3 | import './styles.css' 4 | import MapboxGLButtonControl from '@delebash/mapbox-gl-button-control' 5 | 6 | mapboxgl.accessToken = 7 | "your mapbox access token"; 8 | 9 | let map = new mapboxgl.Map({ 10 | container: "map", // container id 11 | style: "mapbox://styles/mapbox/streets-v9", // stylesheet location 12 | center: [-64.75, 32.3], // starting position [lng, lat] 13 | zoom: 10 // starting zoom 14 | }); 15 | 16 | /* Event Handlers */ 17 | function one(event) { 18 | alert("Event handler when clicking on \r\n" + event.target.className); 19 | console.log("event number 1", event); 20 | } 21 | 22 | function two(event) { 23 | alert("Event handler when clicking on \r\n" + event.target.className); 24 | console.log("event number 2", event); 25 | } 26 | 27 | function three(event) { 28 | alert("Event handler when clicking on \r\n" + event.target.className); 29 | console.log("event number 3", event); 30 | } 31 | 32 | /* Instantiate new controls with custom event handlers */ 33 | const ctrlPoint = new MapboxGLButtonControl({ 34 | className: "mapbox-gl-draw_point", 35 | title: "Draw Point", 36 | eventHandler: one 37 | }); 38 | 39 | const ctrlLine = new MapboxGLButtonControl({ 40 | className: "mapbox-gl-draw_line", 41 | title: "Draw Line", 42 | eventHandler: two 43 | }); 44 | 45 | const ctrlPolygon = new MapboxGLButtonControl({ 46 | className: "mapbox-gl-draw_polygon", 47 | title: "Draw Polygon", 48 | eventHandler: three 49 | }); 50 | 51 | /* Add Controls to the Map */ 52 | map.addControl(new mapboxgl.NavigationControl(), "top-left"); 53 | map.addControl(ctrlPoint, "bottom-left"); 54 | map.addControl(ctrlLine, "bottom-right"); 55 | map.addControl(ctrlPolygon, "top-right"); 56 | -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/Example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "start": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "vite": "^3.1.0" 12 | }, 13 | "dependencies": { 14 | "@delebash/mapbox-gl-button-control": "^1.0.3", 15 | "mapbox-gl": "^2.10.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/Example/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | #map { 7 | position: absolute; 8 | top: 0; 9 | bottom: 0; 10 | width: 100%; 11 | } 12 | 13 | /* // From: Pitch toggle control for Mapbox GL JS — http://fuzzytolerance.info/blog/2017/01/30/Pitch-toggle-control-for-Mapbox-GL-JS/ */ 14 | .mapboxgl-ctrl-pitchtoggle-3d { 15 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCI+ICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkeT0iLjM1ZW0iIHN0eWxlPSJmb250LXNpemU6IDE0cHg7IGZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLEFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1hbmNob3I6IG1pZGRsZTsiPjNEPC90ZXh0Pjwvc3ZnPg==); 16 | } 17 | 18 | .mapboxgl-ctrl-pitchtoggle-2d { 19 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCI+ICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkeT0iLjM1ZW0iIHN0eWxlPSJmb250LXNpemU6IDE0cHg7IGZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLEFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1hbmNob3I6IG1pZGRsZTsiPjJEPC90ZXh0Pjwvc3ZnPg==); 20 | } 21 | 22 | /* 23 | // the images for mapbox-gl-draw_* are from 24 | https://github.com/mapbox/mapbox-gl-draw/blob/master/dist/mapbox-gl-draw.css 25 | */ 26 | .mapbox-gl-draw_point { 27 | background-repeat: no-repeat; 28 | background-position: center; 29 | pointer-events: auto; 30 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJtYXJrZXIuc3ZnIj4gIDxkZWZzICAgICBpZD0iZGVmczE5MTY5IiAvPiAgPHNvZGlwb2RpOm5hbWVkdmlldyAgICAgaWQ9ImJhc2UiICAgICBwYWdlY29sb3I9IiNmZmZmZmYiICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIgICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIgICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiAgICAgaW5rc2NhcGU6em9vbT0iMTYiICAgICBpbmtzY2FwZTpjeD0iMTQuMTY0MjUzIiAgICAgaW5rc2NhcGU6Y3k9IjguODg1NzIiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0iZmFsc2UiICAgICB1bml0cz0icHgiICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEyODAiICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI3NTEiICAgICBpbmtzY2FwZTp3aW5kb3cteD0iMjA4IiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjE5MCIgICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiICAgICBpbmtzY2FwZTpvYmplY3Qtbm9kZXM9InRydWUiPiAgICA8aW5rc2NhcGU6Z3JpZCAgICAgICB0eXBlPSJ4eWdyaWQiICAgICAgIGlkPSJncmlkMTk3MTUiIC8+ICA8L3NvZGlwb2RpOm5hbWVkdmlldz4gIDxtZXRhZGF0YSAgICAgaWQ9Im1ldGFkYXRhMTkxNzIiPiAgICA8cmRmOlJERj4gICAgICA8Y2M6V29yayAgICAgICAgIHJkZjphYm91dD0iIj4gICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PiAgICAgICAgPGRjOnR5cGUgICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+ICAgICAgICA8ZGM6dGl0bGUgLz4gICAgICA8L2NjOldvcms+ICAgIDwvcmRmOlJERj4gIDwvbWV0YWRhdGE+ICA8ZyAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIiAgICAgaWQ9ImxheWVyMSIgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTEwMzIuMzYyMikiPiAgICA8cGF0aCAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtjbGlwLXJ1bGU6bm9uemVybztkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO3Zpc2liaWxpdHk6dmlzaWJsZTtvcGFjaXR5OjE7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2NvbG9yLWludGVycG9sYXRpb246c1JHQjtjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM6bGluZWFyUkdCO3NvbGlkLWNvbG9yOiMwMDAwMDA7c29saWQtb3BhY2l0eToxO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDtzdHJva2Utb3BhY2l0eToxO21hcmtlcjpub25lO2NvbG9yLXJlbmRlcmluZzphdXRvO2ltYWdlLXJlbmRlcmluZzphdXRvO3NoYXBlLXJlbmRlcmluZzphdXRvO3RleHQtcmVuZGVyaW5nOmF1dG87ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIgICAgICAgZD0ibSAzNiwxMDQwLjM2MjIgYyA2ZS02LDMuMzA5MyAtNS45ODg2MTIsMTAgLTUuOTg4NjEyLDEwIDAsMCAtNS45OTg3NzYsLTYuNjY4IC02LjAxMTM0NSwtOS45NzcyIC0wLjAxMjU3LC0zLjMwOTIgMi42NTY1NzYsLTYuMDAzOSA1Ljk2NTc5MiwtNi4wMjI3IDMuMzA5MTg5LC0wLjAxOSA2LjAwODg0LDIuNjQ1MiA2LjAzMzk5Miw1Ljk1NDMiICAgICAgIGlkPSJwYXRoMTI1NjEiICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2Nzc2MiIC8+ICAgIDxwYXRoICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2NsaXAtcnVsZTpub256ZXJvO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO29wYWNpdHk6MTtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7Y29sb3ItaW50ZXJwb2xhdGlvbjpzUkdCO2NvbG9yLWludGVycG9sYXRpb24tZmlsdGVyczpsaW5lYXJSR0I7c29saWQtY29sb3I6IzAwMDAwMDtzb2xpZC1vcGFjaXR5OjE7ZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO3N0cm9rZS1vcGFjaXR5OjE7bWFya2VyOm5vbmU7Y29sb3ItcmVuZGVyaW5nOmF1dG87aW1hZ2UtcmVuZGVyaW5nOmF1dG87c2hhcGUtcmVuZGVyaW5nOmF1dG87dGV4dC1yZW5kZXJpbmc6YXV0bztlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBkPSJtIDM0LjAwMDExNSwxMDQwLjM2MjIgYyAtNWUtNiwyLjIwNjIgLTMuOTkyNTIzLDcuMDAwMSAtMy45OTI1MjMsNy4wMDAxIDAsMCAtMy45OTkyOTEsLTQuNzc4NyAtNC4wMDc2NzksLTYuOTg0OSAtMC4wMDg0LC0yLjIwNjIgMS43NzEwODIsLTQuMDAyNyAzLjk3NzMxLC00LjAxNTMgMi4yMDYyMSwtMC4wMTMgNC4wMDYwMzcsMS43NjM1IDQuMDIyNzc3LDMuOTY5NyIgICAgICAgaWQ9InBhdGgxMjU2MyIgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2NzYyIgLz4gICAgPHBhdGggICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7Y2xpcC1ydWxlOm5vbnplcm87ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTt2aXNpYmlsaXR5OnZpc2libGU7b3BhY2l0eToxO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtjb2xvci1pbnRlcnBvbGF0aW9uOnNSR0I7Y29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzOmxpbmVhclJHQjtzb2xpZC1jb2xvcjojMDAwMDAwO3NvbGlkLW9wYWNpdHk6MTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7c3Ryb2tlLW9wYWNpdHk6MTttYXJrZXI6bm9uZTtjb2xvci1yZW5kZXJpbmc6YXV0bztpbWFnZS1yZW5kZXJpbmc6YXV0bztzaGFwZS1yZW5kZXJpbmc6YXV0bzt0ZXh0LXJlbmRlcmluZzphdXRvO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGQ9Ik0gOS45NjY3OTY5LDEwMTQuMzYyMiBDIDYuNjU3NTgwOSwxMDE0LjM4MSAzLjk4NzQzLDEwMTcuMDc2NCA0LDEwMjAuMzg1NiBjIDAuMDEyNTY5LDMuMzA5MiA2LjAxMTcxOSw4Ljk3NjYgNi4wMTE3MTksOC45NzY2IDAsMCA1Ljk4ODI4NywtNS42OTA3IDUuOTg4MjgxLC05IGwgMCwtMC4wNDUgYyAtMC4wMjUxNSwtMy4zMDkxIC0yLjcyNDAxNCwtNS45NzQxIC02LjAzMzIwMzEsLTUuOTU1MSB6IG0gMC4wMDk3NywyIGMgMi4yMDYyMDYxLC0wLjAxMyA0LjAwNjY5MzEsMS43NjI2IDQuMDIzNDMzMSwzLjk2ODggbCAwLDAuMDMxIGMgLTVlLTYsMi4yMDYyIC0zLjk5MjE4OCw2IC0zLjk5MjE4OCw2IDAsMCAtMy45OTk0MjQsLTMuNzc4MiAtNC4wMDc4MTIsLTUuOTg0NCAtMC4wMDg0LC0yLjIwNjIgMS43NzAzMzQ1LC00LjAwMyAzLjk3NjU2MjUsLTQuMDE1NiB6IiAgICAgICBpZD0icGF0aDEyNTY4IiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzY3NjY2Njc2NzYyIgLz4gICAgPHBhdGggICAgICAgc3R5bGU9Im9wYWNpdHk6MTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46YmV2ZWw7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDtzdHJva2Utb3BhY2l0eToxO21hcmtlcjpub25lIiAgICAgICBkPSJNIDEwIDIgQyA2LjY4NjI5MiAyIDQgNC42ODYzIDQgOCBDIDQgMTEuMzEzNyAxMCAxNyAxMCAxNyBDIDEwIDE3IDE2IDExLjMxMzcgMTYgOCBDIDE2IDQuNjg2MyAxMy4zMTM3MDggMiAxMCAyIHogTSAxMCA0IEMgMTIuMDcxMDY4IDQgMTMuNzUgNS42Nzg5IDEzLjc1IDcuNzUgQyAxMy43NSA5LjIwNTMyNzggMTEuOTMxMTEgMTEuNjQ0MzkzIDEwLjgzMDA3OCAxMyBMIDkuMTY5OTIxOSAxMyBDIDguMDY4ODkwMyAxMS42NDQzOTMgNi4yNSA5LjIwNTMyNzggNi4yNSA3Ljc1IEMgNi4yNSA1LjY3ODkgNy45Mjg5MzIgNCAxMCA0IHogIiAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLDEwMzIuMzYyMikiICAgICAgIGlkPSJwYXRoMTczMDUiIC8+ICA8L2c+PC9zdmc+); 31 | } 32 | 33 | .mapbox-gl-draw_line { 34 | background-repeat: no-repeat; 35 | background-position: center; 36 | pointer-events: auto; 37 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJsaW5lLnN2ZyI+ICA8ZGVmcyAgICAgaWQ9ImRlZnMxOTE2OSIgLz4gIDxzb2RpcG9kaTpuYW1lZHZpZXcgICAgIGlkPSJiYXNlIiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiICAgICBib3JkZXJvcGFjaXR5PSIxLjAiICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIgICAgIGlua3NjYXBlOnpvb209IjE2IiAgICAgaW5rc2NhcGU6Y3g9IjEyLjg5ODc3NSIgICAgIGlua3NjYXBlOmN5PSI5LjU4OTAxNTIiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0idHJ1ZSIgICAgIHVuaXRzPSJweCIgICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTI4MCIgICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc1MSIgICAgIGlua3NjYXBlOndpbmRvdy14PSIwIiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjIzIiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIgICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+ICAgIDxpbmtzY2FwZTpncmlkICAgICAgIHR5cGU9Inh5Z3JpZCIgICAgICAgaWQ9ImdyaWQxOTcxNSIgLz4gIDwvc29kaXBvZGk6bmFtZWR2aWV3PiAgPG1ldGFkYXRhICAgICBpZD0ibWV0YWRhdGExOTE3MiI+ICAgIDxyZGY6UkRGPiAgICAgIDxjYzpXb3JrICAgICAgICAgcmRmOmFib3V0PSIiPiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+ICAgICAgICA8ZGM6dHlwZSAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4gICAgICAgIDxkYzp0aXRsZSAvPiAgICAgIDwvY2M6V29yaz4gICAgPC9yZGY6UkRGPiAgPC9tZXRhZGF0YT4gIDxnICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIgICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiICAgICBpZD0ibGF5ZXIxIiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTAzMi4zNjIyKSI+ICAgIDxwYXRoICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MzttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBkPSJtIDEzLjUsMTAzNS44NjIyIGMgLTEuMzgwNzEyLDAgLTIuNSwxLjExOTMgLTIuNSwyLjUgMCwwLjMyMDggMC4wNDYxNCwwLjYyNDQgMC4xNTYyNSwwLjkwNjMgbCAtMy43NSwzLjc1IGMgLTAuMjgxODM2LC0wLjExMDIgLTAuNTg1NDIxLC0wLjE1NjMgLTAuOTA2MjUsLTAuMTU2MyAtMS4zODA3MTIsMCAtMi41LDEuMTE5MyAtMi41LDIuNSAwLDEuMzgwNyAxLjExOTI4OCwyLjUgMi41LDIuNSAxLjM4MDcxMiwwIDIuNSwtMS4xMTkzIDIuNSwtMi41IDAsLTAuMzIwOCAtMC4wNDYxNCwtMC42MjQ0IC0wLjE1NjI1LC0wLjkwNjIgbCAzLjc1LC0zLjc1IGMgMC4yODE4MzYsMC4xMTAxIDAuNTg1NDIxLDAuMTU2MiAwLjkwNjI1LDAuMTU2MiAxLjM4MDcxMiwwIDIuNSwtMS4xMTkzIDIuNSwtMi41IDAsLTEuMzgwNyAtMS4xMTkyODgsLTIuNSAtMi41LC0yLjUgeiIgICAgICAgaWQ9InJlY3Q2NDY3IiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPiAgPC9nPjwvc3ZnPg==); 38 | } 39 | 40 | .mapbox-gl-draw_polygon { 41 | background-repeat: no-repeat; 42 | background-position: center; 43 | pointer-events: auto; 44 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJzcXVhcmUuc3ZnIj4gIDxkZWZzICAgICBpZD0iZGVmczE5MTY5IiAvPiAgPHNvZGlwb2RpOm5hbWVkdmlldyAgICAgaWQ9ImJhc2UiICAgICBwYWdlY29sb3I9IiNmZmZmZmYiICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIgICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIgICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiAgICAgaW5rc2NhcGU6em9vbT0iMTEuMzEzNzA4IiAgICAgaW5rc2NhcGU6Y3g9IjExLjY4MTYzNCIgICAgIGlua3NjYXBlOmN5PSI5LjI4NTcxNDMiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0idHJ1ZSIgICAgIHVuaXRzPSJweCIgICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTI4MCIgICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc1MSIgICAgIGlua3NjYXBlOndpbmRvdy14PSIwIiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjIzIiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIgICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+ICAgIDxpbmtzY2FwZTpncmlkICAgICAgIHR5cGU9Inh5Z3JpZCIgICAgICAgaWQ9ImdyaWQxOTcxNSIgLz4gIDwvc29kaXBvZGk6bmFtZWR2aWV3PiAgPG1ldGFkYXRhICAgICBpZD0ibWV0YWRhdGExOTE3MiI+ICAgIDxyZGY6UkRGPiAgICAgIDxjYzpXb3JrICAgICAgICAgcmRmOmFib3V0PSIiPiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+ICAgICAgICA8ZGM6dHlwZSAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4gICAgICAgIDxkYzp0aXRsZSAvPiAgICAgIDwvY2M6V29yaz4gICAgPC9yZGY6UkRGPiAgPC9tZXRhZGF0YT4gIDxnICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIgICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiICAgICBpZD0ibGF5ZXIxIiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTAzMi4zNjIyKSI+ICAgIDxwYXRoICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC41O21hcmtlcjpub25lO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGQ9Im0gNSwxMDM5LjM2MjIgMCw2IDIsMiA2LDAgMiwtMiAwLC02IC0yLC0yIC02LDAgeiBtIDMsMCA0LDAgMSwxIDAsNCAtMSwxIC00LDAgLTEsLTEgMCwtNCB6IiAgICAgICBpZD0icmVjdDc3OTciICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2NjY2NjY2NjY2NjY2NjIiAvPiAgICA8Y2lyY2xlICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MS42MDAwMDAwMjttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBpZD0icGF0aDQzNjQiICAgICAgIGN4PSI2IiAgICAgICBjeT0iMTA0Ni4zNjIyIiAgICAgICByPSIyIiAvPiAgICA8Y2lyY2xlICAgICAgIGlkPSJwYXRoNDM2OCIgICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTt2aXNpYmlsaXR5OnZpc2libGU7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDoxLjYwMDAwMDAyO21hcmtlcjpub25lO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGN4PSIxNCIgICAgICAgY3k9IjEwNDYuMzYyMiIgICAgICAgcj0iMiIgLz4gICAgPGNpcmNsZSAgICAgICBpZD0icGF0aDQzNzAiICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MS42MDAwMDAwMjttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBjeD0iNiIgICAgICAgY3k9IjEwMzguMzYyMiIgICAgICAgcj0iMiIgLz4gICAgPGNpcmNsZSAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO3Zpc2liaWxpdHk6dmlzaWJsZTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjEuNjAwMDAwMDI7bWFya2VyOm5vbmU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIgICAgICAgaWQ9InBhdGg0MzcyIiAgICAgICBjeD0iMTQiICAgICAgIGN5PSIxMDM4LjM2MjIiICAgICAgIHI9IjIiIC8+ICA8L2c+PC9zdmc+); 45 | } 46 | -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/README.md: -------------------------------------------------------------------------------- 1 | # mapbox-gl-button-control 2 | 3 | add button or html element to Mapbox Control Bar 4 | 5 | Npm module created from https://codepen.io/roblabs/pen/zJjPzX 6 | 7 | See Example folder 8 | 9 | you will need to add your own mapbox access token for the demo in the index.js 10 | 11 | 12 | ```csharp 13 | cd Example 14 | 15 | npm install 16 | 17 | npm start 18 | ``` 19 | 20 | 21 | 22 | ![](example.png) -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delebash/unreal_mapbox_bridge/63d2de18aef09b7ddba6c625675ed1cfb2ee6c5b/src/maptiler-gl-button-control/example.png -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/index.js: -------------------------------------------------------------------------------- 1 | /* Npm module created from https://codepen.io/roblabs/pen/zJjPzX */ 2 | class MapTilerGLButtonControl { 3 | constructor({ 4 | className = "", 5 | title = "", 6 | eventHandler = evtHndlr 7 | }) { 8 | this._className = className; 9 | this._title = title; 10 | this._eventHandler = eventHandler; 11 | } 12 | 13 | onAdd(map) { 14 | this._btn = document.createElement("button"); 15 | this._btn.className = "maplibregl-ctrl-icon" + " " + this._className; 16 | this._btn.type = "button"; 17 | this._btn.title = this._title; 18 | this._btn.onclick = this._eventHandler; 19 | 20 | this._container = document.createElement("div"); 21 | this._container.className = "maplibregl-ctrl-group maplibregl-ctrl"; 22 | this._container.appendChild(this._btn); 23 | 24 | return this._container; 25 | } 26 | 27 | onRemove() { 28 | this._container.parentNode.removeChild(this._container); 29 | this._map = undefined; 30 | } 31 | } 32 | 33 | export default MapTilerGLButtonControl 34 | -------------------------------------------------------------------------------- /src/maptiler-gl-button-control/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@delebash/mapbox-gl-button-control", 3 | "version": "1.0.4", 4 | "description": "Easily add custom buttons to Mapbox Control Panel", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Daniel Elebash", 10 | "license": "ISC", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/delebash/mapbox-gl-button-control.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/delebash/mapbox-gl-button-control/issues" 17 | }, 18 | "homepage": "https://github.com/delebash/mapbox-gl-button-control#readme" 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/ErrorNotFound.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 32 | -------------------------------------------------------------------------------- /src/pages/IndexPage.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers' 2 | import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' 3 | import routes from './routes' 4 | 5 | /* 6 | * If not building with SSR mode, you can 7 | * directly export the Router instantiation; 8 | * 9 | * The function below can be async too; either use 10 | * async/await or return a Promise which resolves 11 | * with the Router instance. 12 | */ 13 | 14 | export default route(function (/* { store, ssrContext } */) { 15 | const createHistory = process.env.SERVER 16 | ? createMemoryHistory 17 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) 18 | 19 | const Router = createRouter({ 20 | scrollBehavior: () => ({ left: 0, top: 0 }), 21 | routes, 22 | 23 | // Leave this as is and make changes in quasar.conf.js instead! 24 | // quasar.conf.js -> build -> vueRouterMode 25 | // quasar.conf.js -> build -> publicPath 26 | history: createHistory(process.env.VUE_ROUTER_BASE) 27 | }) 28 | 29 | return Router 30 | }) 31 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | 2 | const routes = [ 3 | { 4 | path: '/', 5 | component: () => import('layouts/MainLayout.vue'), 6 | children: [ 7 | { path: '', component: () => import('pages/IndexPage.vue') } 8 | ] 9 | }, 10 | 11 | // Always leave this as last one, 12 | // but you can also remove it 13 | { 14 | path: '/:catchAll(.*)*', 15 | component: () => import('pages/ErrorNotFound.vue') 16 | } 17 | ] 18 | 19 | export default routes 20 | -------------------------------------------------------------------------------- /src/utilities/combine-tiles-jimp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import minBy from 'lodash/minBy'; 4 | import maxBy from 'lodash/maxBy'; 5 | import sortBy from 'lodash/sortBy'; 6 | 7 | // import Jimp from 'jimp/browser/lib/jimp'; 8 | 9 | 10 | export async function combineTilesJimp(tiles, tWidth, tHeight) { 11 | const offsetX = minBy(tiles, tile => tile.x).x 12 | const offsetY = minBy(tiles, tile => tile.y).y 13 | 14 | const makeRelative = (tile) => ({ 15 | x: tile.x - offsetX, 16 | y: tile.y - offsetY, 17 | buffer: tile.buffer 18 | }) 19 | 20 | const index = sortBy(tiles.map(makeRelative), ['y', 'x']) 21 | const cols = 1 + maxBy(index, tile => tile.x).x 22 | const rows = 1 + maxBy(index, tile => tile.y).y 23 | const w = tWidth * cols 24 | const h = tHeight * rows 25 | 26 | async function CompositeImg(image) { 27 | for (let data of index) { 28 | let buffer = Buffer.from(data.buffer) 29 | let y = data.y * tHeight 30 | let x = data.x * tWidth 31 | let newImage = await Jimp.read(buffer) 32 | image.composite(newImage, x, y) 33 | } 34 | return image 35 | } 36 | 37 | let image = await Jimp.read(Buffer.from(index[0].buffer)) 38 | image.background(0xFFFFFFFF) 39 | image.resize(w, h); 40 | let compImage = await CompositeImg(image) 41 | 42 | let buffer = await compImage.getBufferAsync(Jimp.MIME_PNG); 43 | 44 | return buffer 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/utilities/emitter.js: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | export default mitt(); 3 | -------------------------------------------------------------------------------- /src/utilities/fs-helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Open a handle to an existing file on the local file system. 3 | * 4 | * @return {!Promise} Handle to the existing file. 5 | */ 6 | function getFileHandle() { 7 | // For Chrome 86 and later... 8 | if ('showOpenFilePicker' in window) { 9 | return window.showOpenFilePicker().then((handles) => handles[0]); 10 | } 11 | } 12 | 13 | /** 14 | * Open a handle to a directory on the local file system. 15 | * 16 | * @return {!Promise} Handle to the existing file. 17 | */ 18 | function getDirHandle() { 19 | // For Chrome 86 and later... 20 | if ('showDirectoryPicker' in window) { 21 | return window.showDirectoryPicker().then((handles) => handles); 22 | } 23 | } 24 | 25 | /** 26 | * Create a handle to a new (text) file on the local file system. 27 | * 28 | * @return {!Promise} Handle to the new file. 29 | */ 30 | function getNewFileHandle() { 31 | // For Chrome 86 and later... 32 | if ('showSaveFilePicker' in window) { 33 | const opts = { 34 | types: [{ 35 | description: 'Text file', accept: {'text/plain': ['.txt']}, 36 | }], 37 | }; 38 | return window.showSaveFilePicker(opts); 39 | } 40 | } 41 | 42 | /** 43 | * Reads the raw text from a file. 44 | * 45 | * @param {File} file 46 | * @return {!Promise} A promise that resolves to the parsed string. 47 | */ 48 | function readFile(file) { 49 | // If the new .text() reader is available, use it. 50 | if (file.text) { 51 | return file.text(); 52 | } 53 | } 54 | 55 | /** 56 | * Writes the contents to disk. 57 | * 58 | * @param {FileSystemFileHandle} fileHandle File handle to write to. 59 | * @param {string} contents Contents to write. 60 | */ 61 | async function writeFile(fileHandle, contents) { 62 | // For Chrome 83 and later. 63 | // Create a FileSystemWritableFileStream to write to. 64 | const writable = await fileHandle.createWritable(); 65 | // Write the contents of the file to the stream. 66 | await writable.write(contents); 67 | // Close the file and write the contents to disk. 68 | await writable.close(); 69 | } 70 | 71 | /** 72 | * Verify the user has granted permission to read or write to the file, if 73 | * permission hasn't been granted, request permission. 74 | * 75 | * @param {FileSystemDirHandle} dirHandle Dir handle to check. 76 | * @param {boolean} withWrite True if write permission should be checked. 77 | * @return {boolean} True if permission granted. 78 | 79 | */ 80 | async function verifyPermission(dirHandle, withWrite) { 81 | const opts = {}; 82 | if (withWrite) { 83 | opts.writable = true; 84 | opts.mode = 'readwrite'; 85 | } 86 | // Check if we already have permission, if so, return true. 87 | if (await dirHandle.queryPermission(opts) === 'granted') { 88 | return true; 89 | } 90 | // Request permission to the file, if the user grants permission, return true. 91 | if (await dirHandle.requestPermission(opts) === 'granted') { 92 | return true; 93 | } 94 | // The user did nt grant permission, return false. 95 | return false; 96 | } 97 | 98 | /** 99 | * Writes the contents to disk. 100 | * 101 | * @param {FileSystemDirHandle} dirHandle Dir handle to write to. 102 | * @param {string} fileName File name including extension. 103 | * @param {string, ArrayBuffer} contents Contents to write. 104 | */ 105 | async function writeFileToDisk(dirHandle, fileName, contents) { 106 | let writeFileHandle = await dirHandle.getFileHandle(fileName, {create: true}) 107 | let writable = await writeFileHandle.createWritable() 108 | await writable.write(contents) 109 | await writable.close(); 110 | } 111 | 112 | /** 113 | * Check if file exists on disk. 114 | * 115 | * @param {FileSystemDirHandle} dirHandle Dir handle to write to. 116 | * @param {string} fileName File name including extension. 117 | * @return {boolean} True if file exists. 118 | */ 119 | async function fileExists(dirHandle, fileName) { 120 | try { 121 | await dirHandle.getFileHandle(fileName) 122 | // console.log(fileName + ' file already exists -- using cached file') 123 | return true 124 | } catch (e) { 125 | if (e.name === "NotFoundError") { 126 | // console.log(fileName + ' File not found try to download') 127 | return false 128 | } 129 | if (e.name === "NotAllowedError") { 130 | console.log('Please select directory to verify permissions') 131 | return false 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * Writes the contents to disk. 138 | * 139 | * @param {FileSystemDirHandle} dirHandle Dir handle to write to. 140 | * @param {string} fileName File name including extension. 141 | * @return {ArrayBuffer} arrBuffer of image information. 142 | */ 143 | 144 | async function readFileFromDisk(dirHandle, fileName) { 145 | let fileHandle = await dirHandle.getFileHandle(fileName) 146 | const file = await fileHandle.getFile(); 147 | const imageArrayBuffer = await file.arrayBuffer(); 148 | return imageArrayBuffer 149 | } 150 | 151 | function checkFileApiSupport() { 152 | let bEnabled 153 | if ('showDirectoryPicker' in window) { 154 | bEnabled = true 155 | }else{ 156 | bEnabled = false 157 | } 158 | // let bEnabled = true 159 | // try { 160 | // if(window.showDirectoryPicker()) 161 | // const dirHandle = window.showDirectoryPicker() 162 | // } catch (e) { 163 | // if (e.message === 'window.showDirectoryPicker is not a function') { 164 | // bEnabled = false 165 | // } 166 | // } 167 | return bEnabled 168 | } 169 | 170 | export default { 171 | getDirHandle, verifyPermission, writeFileToDisk, fileExists, getFileHandle, readFileFromDisk, checkFileApiSupport 172 | } 173 | -------------------------------------------------------------------------------- /src/utilities/idb-keyval-iife.js: -------------------------------------------------------------------------------- 1 | /* From https://github.com/jakearchibald/idb-keyval */ 2 | 3 | /* Retreived 2020-04-10 */ 4 | class Store { 5 | constructor(dbName = 'unreal-mapbox', storeName = 'keyval') { 6 | this.storeName = storeName; 7 | this._dbp = new Promise((resolve, reject) => { 8 | const openreq = indexedDB.open(dbName, 3); 9 | openreq.onerror = () => reject(openreq.error); 10 | openreq.onsuccess = () => resolve(openreq.result); 11 | // First time setup: create an empty object store 12 | openreq.onupgradeneeded = () => { 13 | try { 14 | openreq.result.deleteObjectStore(storeName); 15 | } catch (e) { 16 | //ignore 17 | } 18 | openreq.result.createObjectStore(storeName); 19 | }; 20 | }); 21 | } 22 | 23 | _withIDBStore(type, callback) { 24 | return this._dbp.then(db => new Promise((resolve, reject) => { 25 | const transaction = db.transaction(this.storeName, type); 26 | transaction.oncomplete = () => resolve(); 27 | transaction.onabort = transaction.onerror = () => reject(transaction.error); 28 | callback(transaction.objectStore(this.storeName)); 29 | })); 30 | } 31 | } 32 | 33 | let store; 34 | 35 | function getDefaultStore() { 36 | if (!store) 37 | store = new Store(); 38 | return store; 39 | } 40 | 41 | function get(key, store = getDefaultStore()) { 42 | let req; 43 | return store._withIDBStore('readonly', store => { 44 | req = store.get(key); 45 | }).then(() => req.result); 46 | } 47 | 48 | function set(key, value, store = getDefaultStore()) { 49 | return store._withIDBStore('readwrite', store => { 50 | store.put(value, key); 51 | }); 52 | } 53 | 54 | function del(key, store = getDefaultStore()) { 55 | return store._withIDBStore('readwrite', store => { 56 | store.delete(key); 57 | }); 58 | } 59 | 60 | function clear(store = getDefaultStore()) { 61 | return store._withIDBStore('readwrite', store => { 62 | store.clear(); 63 | }); 64 | } 65 | 66 | function keys(store = getDefaultStore()) { 67 | const keys = []; 68 | return store._withIDBStore('readonly', store => { 69 | // This would be store.getAllKeys(), but it isn't supported by Edge or Safari. 70 | // And openKeyCursor isn't supported by Safari. 71 | (store.openKeyCursor || store.openCursor).call(store).onsuccess = function () { 72 | if (!this.result) 73 | return; 74 | keys.push(this.result.key); 75 | this.result.continue(); 76 | }; 77 | }).then(() => keys); 78 | } 79 | 80 | export default { 81 | keys, 82 | clear, 83 | del, 84 | set, 85 | get 86 | } 87 | -------------------------------------------------------------------------------- /src/utilities/map-utils.js: -------------------------------------------------------------------------------- 1 | import mapboxgl from "mapbox-gl"; 2 | import tilebelt from '@mapbox/tilebelt' 3 | import * as turf from '@turf/turf' 4 | import {Image} from "image-js"; 5 | import idbKeyval from "../utilities/idb-keyval-iife"; 6 | 7 | function getTileInfo(lng, lat, multiple, x, y, z, bbox) { 8 | let tileInfo = {} 9 | let xyzpoint 10 | 11 | 12 | if (multiple === false) { 13 | xyzpoint = tilebelt.pointToTile(lng, lat, z) 14 | x = xyzpoint[0] 15 | y = xyzpoint[1] 16 | } 17 | 18 | let widthInMeters = 40075016.686 * Math.abs(Math.cos(lat)) / Math.pow(2, z); 19 | let metersPerPixel = widthInMeters / 512; 20 | 21 | tileInfo.z = z 22 | tileInfo.x = x 23 | tileInfo.y = y 24 | tileInfo.pointLng = lng 25 | tileInfo.pointLat = lat 26 | tileInfo.tileWidthInMeters = widthInMeters 27 | tileInfo.metersPerPixel = metersPerPixel 28 | tileInfo.mapboxTileName = tileInfo.z + "-" + tileInfo.x + "-" + tileInfo.y 29 | tileInfo.tile = [tileInfo.x, tileInfo.y, tileInfo.z] // x,y,z 30 | if (multiple === false) { 31 | tileInfo.bbox = tilebelt.tileToBBOX(tileInfo.tile); 32 | } else { 33 | tileInfo.bbox = bbox; 34 | } 35 | //tileInfo.bbox = tilebelt.tileToBBOX(tileInfo.tile); 36 | tileInfo.polygon_bb = getTileGeoJsonBB(tileInfo.bbox) 37 | tileInfo.area_bb = getAreaBB(tileInfo.bbox) 38 | 39 | const llb = new mapboxgl.LngLatBounds(tileInfo.bbox); 40 | 41 | //Corners of bbox 42 | tileInfo.bboxCT = llb.getCenter(); 43 | tileInfo.bboxSW = llb.getSouthWest() 44 | tileInfo.bboxNE = llb.getNorthEast() 45 | tileInfo.bboxNW = llb.getNorthWest() 46 | tileInfo.bboxSE = llb.getSouthEast() 47 | 48 | //Edge of bbox 49 | tileInfo.bboxW = llb.getWest().toFixed(5) 50 | tileInfo.bboxS = llb.getSouth().toFixed(5) 51 | tileInfo.bboxE = llb.getEast().toFixed(5) 52 | tileInfo.bboxN = llb.getNorth().toFixed(5) 53 | 54 | tileInfo.topLeft = tileInfo.bboxNW 55 | tileInfo.bottomLeft = tileInfo.bboxSW 56 | tileInfo.topRight = tileInfo.bboxNE 57 | tileInfo.bottomRight = tileInfo.bboxSE 58 | tileInfo.center = tileInfo.bboxCT 59 | 60 | 61 | const topLeft = turf.point([tileInfo.bboxNE.lng, tileInfo.bboxNE.lat]); 62 | const topRight = turf.point([tileInfo.bboxSW.lng, tileInfo.bboxNE.lat]); 63 | const bottomLeft = turf.point([tileInfo.bboxNE.lng, tileInfo.bboxSW.lat]); 64 | const bottomRight = turf.point([tileInfo.bboxSW.lng, tileInfo.bboxSW.lat]); 65 | const middleLeft = turf.midpoint(topLeft, bottomLeft); 66 | const middleRight = turf.midpoint(topRight, bottomRight); 67 | tileInfo.distance = turf.distance(middleLeft, middleRight, 'kilometers').toFixed(2); 68 | 69 | tileInfo.maxPngValue = 65535 70 | tileInfo.rgbFileName = 'terrain-rgb' + '-' + tileInfo.mapboxTileName + '.png' 71 | tileInfo.mapFileName = 'map' + '-' + tileInfo.mapboxTileName + '.png' 72 | tileInfo.satFileName = 'sat' + '-' + tileInfo.mapboxTileName + '.png' 73 | tileInfo.thirtyTwoFileName = 'thirtytwo' + '-' + tileInfo.mapboxTileName + '.png' 74 | tileInfo.tileInfoFileName = 'tile-info' + '-' + tileInfo.mapboxTileName + '.json' 75 | tileInfo.geoJsonFileName = 'geojson' + '-' + tileInfo.mapboxTileName + '.json' 76 | 77 | return tileInfo 78 | } 79 | 80 | // function traverseArray(arr) { 81 | // let i = 0 82 | // let lng 83 | // let lat 84 | // 85 | // arr.forEach((element, index) => { 86 | // if (Array.isArray(element)) { 87 | // traverseArray(element); 88 | // } else { 89 | // if (i === 0) { 90 | // lng = element 91 | // } 92 | // if (i === 1) { 93 | // lat = element 94 | // let convUtm = converLatLngTotUtm(lat, lng) 95 | // arr[0] = convUtm.northing 96 | // arr[1] = convUtm.easting 97 | // } 98 | // i = i + 1 99 | // if (i === 2) { 100 | // i = 0 101 | // } 102 | // } 103 | // }); 104 | // } 105 | 106 | function getAreaBB(bbox) { 107 | 108 | let poly = turf.bboxPolygon(bbox); 109 | let area = turf.area(poly); 110 | return area 111 | } 112 | 113 | function getTileGeoJsonBB(bbox) { 114 | let poly = turf.bboxPolygon(bbox); 115 | let geoJson = { 116 | 'type': 'Feature', 'geometry': { 117 | 'type': poly.geometry.type, 'coordinates': poly.geometry.coordinates 118 | } 119 | }; 120 | return geoJson; 121 | } 122 | 123 | function getFeaturesFromBB(map, tile_info, combine) { 124 | tile_info.swPt = map.project(tile_info.bboxSW) 125 | tile_info.nePt = map.project(tile_info.bboxNE) 126 | tile_info.nwPt = map.project(tile_info.bboxNW) 127 | tile_info.sePt = map.project(tile_info.bboxSE) 128 | let features = map.queryRenderedFeatures([tile_info.swPt, tile_info.nePt]) 129 | 130 | if (combine === true) { 131 | features = getUniqueFeatures(features) 132 | } 133 | return features 134 | 135 | } 136 | 137 | // Because features come from tiled vector data, 138 | // feature geometries may be split 139 | // or duplicated across tile boundaries. 140 | // As a result, features may appear 141 | // multiple times in query results. 142 | function getUniqueFeatures(features) { 143 | const uniqueIds = new Set(); 144 | const uniqueFeatures = []; 145 | for (const feature of features) { 146 | const name = feature.properties["name"]; 147 | const type = feature.geometry["type"]; 148 | let id = name + '-' + type 149 | if (!uniqueIds.has(id)) { 150 | uniqueIds.add(id); 151 | uniqueFeatures.push(feature); 152 | } 153 | } 154 | return uniqueFeatures; 155 | } 156 | 157 | /** 158 | * Load image-js image from array 159 | * 160 | * @param {ArrayBuffer} imageArray ArrayBuffer representing image values 161 | * @return {image-js} Image-js image. 162 | */ 163 | async function loadImageFromArray(imageArray) { 164 | try { 165 | let image = await Image.load(imageArray) 166 | return image 167 | } catch (e) { 168 | console.log(e) 169 | } 170 | } 171 | 172 | /** 173 | * Calculate height in meters from RGB height encoded pixels using Mapbox formula. 174 | * 175 | * @param {int} r Red pixel channel 176 | * @param {int} g Green pixel channel 177 | * @param {int} b Blue pixel channel 178 | * @return {height} New height value. 179 | */ 180 | function getHeightFromRgb(r, g, b) { 181 | return -10000 + ((r * 256 * 256 + g * 256 + b) * 0.1); 182 | } 183 | 184 | /** 185 | * Decode rgb height from image. 186 | * 187 | * @param {image-js} image Image created from image-js 188 | * @return {int[]} Decoded height array. 189 | */ 190 | function getHeightArray(image) { 191 | let decodedHeightArray = [] 192 | let pixelsArray = image.getPixelsArray() 193 | for (const pixel of pixelsArray) { 194 | let r = pixel[0] 195 | let g = pixel[1] 196 | let b = pixel[2] 197 | let height = getHeightFromRgb(r, g, b) 198 | height = parseFloat(height) 199 | decodedHeightArray.push(height) 200 | } 201 | 202 | return decodedHeightArray 203 | } 204 | 205 | /** 206 | * Calculate Medium value of array 207 | * 208 | * @param {int[]} array of integers. 209 | * @return {int} integer Median value. 210 | */ 211 | function getMedianArray(array) { 212 | const arr = array.filter(val => !!val); 213 | const sum = arr.reduce((sum, val) => (sum += val)); 214 | const len = arr.length; 215 | 216 | const arrSort = arr.sort(); 217 | const mid = Math.ceil(len / 2); 218 | 219 | const median = len % 2 == 0 ? (arrSort[mid] + arrSort[mid - 1]) / 2 : arrSort[mid - 1]; 220 | 221 | return median 222 | } 223 | 224 | /** 225 | * Download RGB terrain png from Mapbox api 226 | * 227 | * @param {string} mapbox_rgb_image_url Request url to Mapbox api. 228 | * @return {ArrayBuffer} arrBuffer of image information. 229 | */ 230 | async function downloadTerrainRgb(mapbox_rgb_image_url) { 231 | //Fetch png tile from mapbox 232 | const response = await fetch(mapbox_rgb_image_url); 233 | let arrBuffer = await response.arrayBuffer() 234 | 235 | return arrBuffer 236 | } 237 | 238 | async function unrealRemoteControl(data, url) { 239 | let response, dataJson = {}, error = '' 240 | 241 | const requestOptions = { 242 | method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) 243 | }; 244 | 245 | try { 246 | response = await fetch(url, requestOptions); 247 | } catch (e) { 248 | error = e 249 | } 250 | 251 | dataJson.response = response 252 | dataJson.error = error 253 | 254 | return dataJson 255 | 256 | } 257 | 258 | /** 259 | * Check if RGB exists else initiate download 260 | * 261 | * @param {FileSystemDirHandle} dirHandle Dir handle to write to. 262 | * @param {object} tile_info Tile information for file name. 263 | * @param {string} mapbox_rgb_image_url Request url to Mapbox api. 264 | * @return {image-js} Image-js image. 265 | */ 266 | async function getMapboxTerrainRgb(mapbox_rgb_image_url) { 267 | let rgbImageArrayBuffer 268 | let image 269 | 270 | rgbImageArrayBuffer = await downloadTerrainRgb(mapbox_rgb_image_url) 271 | image = await loadImageFromArray(rgbImageArrayBuffer) 272 | idbKeyval.set('rgbImageArrayBuffer', rgbImageArrayBuffer) 273 | return image 274 | } 275 | 276 | /** 277 | * Convert image to specified bit and color 278 | * 279 | * @param {int} width Image width. 280 | * @param {int} height Image width. 281 | * @param {int[]} imageArray Uint16Array representing image values 282 | * @param {int} bitDepth image-js bit depth example 8,16,32 283 | * @param {string} colorModel image-js color madel string. 284 | * @return {image-js} Image-js image. 285 | */ 286 | function convertImage(width, height, imageArray, bitDepth, colorModel) { 287 | let newImage = new Image(width, height, imageArray, {kind: colorModel, bitDepth: bitDepth}) 288 | return newImage 289 | } 290 | 291 | /** 292 | * Calculate Medium value of array 293 | * 294 | * @param {image-js} image Image created from image-js 295 | * @param {int} bitDepth image-js bit depth example 8,16,32 296 | * @param {string} colorModel image-js color madel string. 297 | * @return {object} image_info Obect containing image and stats 298 | */ 299 | function createHeightMapImage(image, bitDepth, colorModel) { 300 | let image_info = {} 301 | let decodedHeightArray = getHeightArray(image) 302 | idbKeyval.set('decodedHeightArray', decodedHeightArray) 303 | let out_image = convertImage(image.width, image.height, decodedHeightArray, bitDepth, colorModel) 304 | 305 | 306 | image_info.minElevation = out_image.min[0]; 307 | image_info.maxElevation = out_image.max[0]; 308 | image_info.image = out_image 309 | image_info.decodedHeightArray = decodedHeightArray 310 | 311 | return image_info 312 | } 313 | 314 | export default { 315 | getTileInfo, 316 | getFeaturesFromBB, 317 | getMapboxTerrainRgb, 318 | createHeightMapImage, 319 | loadImageFromArray, 320 | unrealRemoteControl, 321 | downloadTerrainRgb, 322 | convertImage, 323 | getTileGeoJsonBB 324 | } 325 | --------------------------------------------------------------------------------