diff --git a/dist/samples/map-drawing-terradraw/app/.eslintsrc.json b/dist/samples/map-drawing-terradraw/app/.eslintsrc.json new file mode 100644 index 00000000..4c44dab0 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/app/.eslintsrc.json @@ -0,0 +1,13 @@ +{ + "extends": [ + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "rules": { + "@typescript-eslint/ban-ts-comment": 0, + "@typescript-eslint/no-this-alias": 1, + "@typescript-eslint/no-empty-function": 1, + "@typescript-eslint/explicit-module-boundary-types": 1, + "@typescript-eslint/no-unused-vars": 1 + } +} diff --git a/dist/samples/map-drawing-terradraw/app/README.md b/dist/samples/map-drawing-terradraw/app/README.md new file mode 100644 index 00000000..8626a48b --- /dev/null +++ b/dist/samples/map-drawing-terradraw/app/README.md @@ -0,0 +1,40 @@ +# Basic Terra Draw with Google Maps API Sample + +This sample demonstrates a basic implementation of Terra Draw with the Google Maps JavaScript API. It includes various drawing modes such as Point, LineString, Polygon, Rectangle, Circle, and Freehand. + +![Roadmap View](./screenshots/draw-roadmap.png) +![Satellite View](./screenshots/draw-satellite.png) + +# Google Maps JavaScript Sample + +This sample is generated from @googlemaps/js-samples located at +https://github.com/googlemaps-samples/js-api-samples. + +## Setup + +### Before starting run: + +`$npm i` + +### Run an example on a local web server + +First `cd` to the folder for the sample to run, then: + +`$npm start` + +### Build an individual example + +From `samples/`: + +`$npm run build --workspace=sample-name/` + +### Build all of the examples. + +From `samples/`: +`$npm run build-all` + +## Feedback + +For feedback related to this sample, please open a new issue on +[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). + diff --git a/dist/samples/map-drawing-terradraw/app/index.html b/dist/samples/map-drawing-terradraw/app/index.html new file mode 100644 index 00000000..48c18039 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/app/index.html @@ -0,0 +1,41 @@ + + + + + + Terra Draw with Google Maps API Sample + + + + + +
+ + +
+ + + + + + + + + + + + + + + +
+ + + + + + diff --git a/dist/samples/map-drawing-terradraw/app/index.ts b/dist/samples/map-drawing-terradraw/app/index.ts new file mode 100644 index 00000000..51c00895 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/app/index.ts @@ -0,0 +1,509 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START maps_map_drawing_terradraw] +// [START maps_map_drawing_terradraw_libraries] +import { Loader } from '@googlemaps/js-api-loader'; + +import { + TerraDraw, + TerraDrawSelectMode, + TerraDrawPointMode, + TerraDrawLineStringMode, + TerraDrawPolygonMode, + TerraDrawRectangleMode, + TerraDrawCircleMode, + TerraDrawFreehandMode +} from 'terra-draw'; +import { TerraDrawGoogleMapsAdapter } from 'terra-draw-google-maps-adapter'; + +// [END maps_map_drawing_terradraw_libraries] + +const colorPalette = [ + "#E74C3C", + "#FF0066", + "#9B59B6", + "#673AB7", + "#3F51B5", + "#3498DB", + "#03A9F4", + "#00BCD4", + "#009688", + "#27AE60", + "#8BC34A", + "#CDDC39", + "#F1C40F", + "#FFC107", + "#F39C12", + "#FF5722", + "#795548" +]; + +const getRandomColor = () => colorPalette[Math.floor(Math.random() * colorPalette.length)] as `#${string}`; + +function processSnapshotForUndo(snapshot: any[]): any[] { + // console.log("Processing snapshot for undo:", snapshot); + return snapshot.map(feature => { + const newFeature = JSON.parse(JSON.stringify(feature)); + + if (newFeature.properties.mode === 'rectangle') { + // console.log("Processing rectangle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + newFeature.properties.mode = 'polygon'; + } else if (newFeature.properties.mode === 'circle') { + // console.log("Processing circle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + // The radius is already in properties, so we just need to ensure the mode is correct for re-creation + newFeature.properties.mode = 'circle'; + } + return newFeature; + }); +} + +function setupModeButtons(): void { + const modeUI = document.getElementById('mode-ui'); + if (!modeUI) { + return; + } + + const modeButtons: { [key: string]: string } = { + 'select-mode': 'select', + 'point-mode': 'point', + 'linestring-mode': 'linestring', + 'polygon-mode': 'polygon', + 'rectangle-mode': 'rectangle', + 'circle-mode': 'circle', + 'freehand-mode': 'freehand', + 'clear-mode': 'static' + }; + + for (const buttonId in modeButtons) { + const button = document.getElementById(buttonId); + if (button) { + button.onclick = () => { + setActiveButton(buttonId); + const modeName = modeButtons[buttonId]; + + if (!draw) { + return; + } + if (modeName === 'static') { + draw.clear(); + draw.setMode('static'); + } else if (modeName) { + draw.setMode(modeName); + } + }; + } + } +} + +function setActiveButton(buttonId: string): void { + const buttons = document.querySelectorAll('.mode-button'); + const resizeButton = document.getElementById('resize-button'); + const isResizeActive = resizeButton?.classList.contains('active'); + + buttons.forEach(button => { + if (button.id !== 'resize-button') { + button.classList.remove('active'); + } + }); + + const activeButton = document.getElementById(buttonId); + if (activeButton) { + activeButton.classList.add('active'); + } + + if (isResizeActive) { + resizeButton?.classList.add('active'); + } +} + +function initUI(): void { + setActiveButton('point-mode'); +} + +let map: google.maps.Map; +let draw: TerraDraw; +let currentMode: string = 'static'; +let history: any[] = []; +let redoHistory: any[] = []; +let selectedFeatureId: string | null = null; +let isRestoring = false; +let resizingEnabled = false; +let debounceTimeout: number | undefined; + +const loader = new Loader({ + apiKey: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", + version: "weekly", + libraries: ["maps", "drawing", "marker"] +}); + +loader.load().then(async () => { + try { + const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary; + const { LatLngBounds } = await google.maps.importLibrary("core") as google.maps.CoreLibrary; + const { Data } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary; + + const mapOptions: google.maps.MapOptions = { + center: { lat: 48.862, lng: 2.342 }, + zoom: 12, + mapId:'c306b3c6dd3ed8d9', // raster '6a17c323f461e521', + mapTypeId: 'roadmap', + zoomControl:false, + tilt: 45, + mapTypeControl: true, + clickableIcons:false, + streetViewControl:false, + fullscreenControl:false, + }; + + const mapDiv = document.getElementById("map") as HTMLElement; + map = new Map(mapDiv, mapOptions); + + map.addListener("click", () => { + if (draw) { + console.log("Current draw mode on map click:", draw.getMode()); + } + }); + + map.addListener("projection_changed", () => { + + // [START maps_drawing_terradraw_modes] + + draw = new TerraDraw({ + adapter: new TerraDrawGoogleMapsAdapter({ map, lib: google.maps, coordinatePrecision: 9 }), + modes: [ + new TerraDrawSelectMode({ + flags: { + polygon: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + linestring: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + point: { + feature: { + draggable: true, + rotateable: true, + }, + }, + rectangle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + circle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + freehand: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + }, + }), + + new TerraDrawPointMode({ + editable: true, + styles: { pointColor: getRandomColor() }, + }), + new TerraDrawLineStringMode({ + editable: true, + styles: { lineStringColor: getRandomColor() }, + }), + new TerraDrawPolygonMode({ + editable: true, + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawRectangleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawCircleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawFreehandMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + ], + }); + + draw.start(); + + + draw.on('ready', () => { + console.log("TerraDraw is ready!"); + initUI(); + setupModeButtons(); + draw.setMode('point'); + currentMode = 'point'; + setActiveButton('point-mode'); + + draw.on("select", (id) => { + // console.log(`Feature selected: ${id}`); + if (selectedFeatureId && selectedFeatureId !== id) { + draw.deselectFeature(selectedFeatureId); + } + selectedFeatureId = id as string; + }); + + draw.on("deselect", () => { + // console.log("Feature deselected"); + selectedFeatureId = null; + }); + + history.push(processSnapshotForUndo(draw.getSnapshot())); // Push initial empty state + + draw.on("change", (ids, type) => { + if (isRestoring) { + return; + } + + if (debounceTimeout) { + clearTimeout(debounceTimeout); + } + + debounceTimeout = window.setTimeout(() => { + const snapshot = draw.getSnapshot(); + const processedSnapshot = processSnapshotForUndo(snapshot); + const filteredSnapshot = processedSnapshot.filter( + (f) => !f.properties.midPoint && !f.properties.selectionPoint + ); + history.push(filteredSnapshot); + redoHistory = []; + }, 200); + }); + + // [END maps_drawing_terradraw_modes] + + const exportButton = document.getElementById('export-button'); + if (exportButton) { + exportButton.onclick = () => { + const features = draw.getSnapshot(); + const geojson = { + type: "FeatureCollection", + features: features, + }; + const data = JSON.stringify(geojson, null, 2); + const blob = new Blob([data], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "drawing.geojson"; + link.click(); + URL.revokeObjectURL(url); + }; + } + + const uploadButton = document.getElementById('upload-button'); + const uploadInput = document.getElementById('upload-input') as HTMLInputElement; + + if (uploadButton && uploadInput) { + uploadButton.onclick = () => { + uploadInput.click(); + }; + + uploadInput.onchange = (event) => { + const file = (event.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const geojson = JSON.parse(e.target?.result as string); + if (geojson.type === "FeatureCollection") { + draw.addFeatures(geojson.features); + } else { + alert("Invalid GeoJSON file: must be a FeatureCollection."); + } + } catch (error) { + alert("Error parsing GeoJSON file."); + } + }; + reader.readAsText(file); + } + }; + } + + const resizeButton = document.getElementById('resize-button'); + if (resizeButton) { + resizeButton.onclick = () => { + resizingEnabled = !resizingEnabled; + resizeButton.classList.toggle('active', resizingEnabled); + + const flags = { + polygon: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + linestring: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + rectangle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + circle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + freehand: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + }; + + console.log("Updating flags:", flags); + draw.updateModeOptions('select', { flags }); + }; + } + + const deleteSelectedButton = document.getElementById('delete-selected-button'); + if (deleteSelectedButton) { + deleteSelectedButton.onclick = () => { + if (selectedFeatureId) { + draw.removeFeatures([selectedFeatureId]); + } else { + const features = draw.getSnapshot(); + if (features.length > 0) { + const lastFeature = features[features.length - 1]; + if (lastFeature.id) { + draw.removeFeatures([lastFeature.id]); + } + } + } + }; + } + + const undoButton = document.getElementById('undo-button'); + if (undoButton) { + undoButton.onclick = () => { + if (history.length > 1) { + redoHistory.push(history.pop()); + const snapshotToRestore = history[history.length - 1]; + console.log("Restoring snapshot (undo):", snapshotToRestore); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshotToRestore); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + + const redoButton = document.getElementById('redo-button'); + if (redoButton) { + redoButton.onclick = () => { + if (redoHistory.length > 0) { + const snapshot = redoHistory.pop(); + console.log("Restoring snapshot (redo):", snapshot); + history.push(snapshot); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshot); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + }); + + function rotateFeature(feature, angle) { + const newFeature = JSON.parse(JSON.stringify(feature)); + const coordinates = newFeature.geometry.coordinates; + const center = getCenter(coordinates); + + const rotatedCoordinates = coordinates.map(ring => { + return ring.map(point => { + const x = point[0] - center[0]; + const y = point[1] - center[1]; + const newX = x * Math.cos(angle * Math.PI / 180) - y * Math.sin(angle * Math.PI / 180); + const newY = x * Math.sin(angle * Math.PI / 180) + y * Math.cos(angle * Math.PI / 180); + return [newX + center[0], newY + center[1]]; + }); + }); + + newFeature.geometry.coordinates = rotatedCoordinates; + return newFeature; + } + + function getCenter(coordinates) { + let x = 0; + let y = 0; + let count = 0; + coordinates.forEach(ring => { + ring.forEach(point => { + x += point[0]; + y += point[1]; + count++; + }); + }); + return [x / count, y / count]; + } + + document.addEventListener('keydown', (event) => { + if (event.key === 'r' && selectedFeatureId) { + const features = draw.getSnapshot(); + const selectedFeature = features.find(f => f.id === selectedFeatureId); + + if (selectedFeature) { + const newFeature = rotateFeature(selectedFeature, 15); + draw.addFeatures([newFeature]); + } + } + }); + }); + + } catch (e) { + console.error("Error loading Google Maps API:", e); + } +}).catch(e => { + console.error("Error loading Google Maps API:", e); +}); +// [END maps_map_drawing_terradraw] diff --git a/dist/samples/map-drawing-terradraw/app/package.json b/dist/samples/map-drawing-terradraw/app/package.json new file mode 100644 index 00000000..c45a36a9 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/app/package.json @@ -0,0 +1,22 @@ +{ + "name": "@js-api-samples/map-drawing-terradraw", + "version": "1.0.0", + "description": "Basic sample for Terra Draw with Google Maps API.", + "scripts": { + "build": "tsc && bash ../jsfiddle.sh map-drawing-terradraw && bash ../app.sh map-drawing-terradraw && bash ../docs.sh map-drawing-terradraw && npm run build:vite --workspace=. && bash ../dist.sh map-drawing-terradraw", + "test": "tsc && npm run build:vite --workspace=.", + "start": "tsc && vite build --base './' && vite", + "build:vite": "vite build --base './'", + "preview": "vite preview" + }, + "dependencies": { + "@googlemaps/js-api-loader": "^1.16.8", + "terra-draw": "latest", + "terra-draw-google-maps-adapter": "latest" + }, + "devDependencies": { + "@types/google.maps": "latest", + "typescript": "^5.0.0", + "vite": "^5.0.0" + } +} diff --git a/dist/samples/map-drawing-terradraw/app/style.css b/dist/samples/map-drawing-terradraw/app/style.css new file mode 100644 index 00000000..2584b672 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/app/style.css @@ -0,0 +1,94 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_map_drawing_terradraw] */ +html, +body { + height: 100%; + margin: 0; + padding: 0; + font-family: Arial, sans-serif; +} +#map { + height: 100%; + width: 100%; +} +#mode-ui { + position: absolute; + top: 10px; + right: 10px; + background: white; + padding: 10px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + z-index: 1000; + display: flex; + flex-direction: column; +} +#mode-ui button { + margin: 5px 0; + cursor: pointer; +} + +.mode-button { + width: 30px; + height: 30px; + border: 1px solid #ccc; + background-color: white; + padding: 2px; + box-sizing: border-box; +} + +.mode-button img { + width: 100%; + height: 100%; + display: block; + user-select: none; +} + +/* Active state for shape modes */ +.mode-button.active { + background-color: #e0e0e0; /* light grey */ +} + +/* Special buttons default state */ +#select-mode, +#clear-mode, +#delete-selected-button, +#undo-button, +#redo-button, +#export-button, +#upload-button, +#resize-button { + background-color: #000000; +} + +/* Special buttons icon default state */ +#select-mode img, +#clear-mode img, +#delete-selected-button img, +#undo-button img, +#redo-button img, +#export-button img, +#upload-button img, +#resize-button img { + filter: brightness(0) invert(1); +} + +/* Special buttons active/click states */ +#select-mode.active { + background-color: #A9A9A9; /* dark grey */ +} + +#clear-mode:active, +#delete-selected-button:active, +#undo-button:active, +#redo-button:active, +#export-button:active, +#upload-button:active, +#resize-button.active { + background-color: #A9A9A9; /* dark grey */ +} +/* [END maps_map_drawing_terradraw] */ diff --git a/dist/samples/map-drawing-terradraw/app/tsconfig.json b/dist/samples/map-drawing-terradraw/app/tsconfig.json new file mode 100644 index 00000000..c543450e --- /dev/null +++ b/dist/samples/map-drawing-terradraw/app/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "strict": true, + "noImplicitAny": false, + "lib": [ + "es2015", + "esnext", + "es6", + "dom", + "dom.iterable" + ], + "moduleResolution": "Bundler", + "jsx": "preserve", + "types": ["@types/google.maps"] + } +} \ No newline at end of file diff --git a/dist/samples/map-drawing-terradraw/dist/assets/index-CIrp2hh-.js b/dist/samples/map-drawing-terradraw/dist/assets/index-CIrp2hh-.js new file mode 100644 index 00000000..f61827e4 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/dist/assets/index-CIrp2hh-.js @@ -0,0 +1,5 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))i(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const n of s.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&i(n)}).observe(document,{childList:!0,subtree:!0});function e(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function i(o){if(o.ep)return;o.ep=!0;const s=e(o);fetch(o.href,s)}})();function Pe(r,t,e,i){function o(s){return s instanceof e?s:new e(function(n){n(s)})}return new(e||(e=Promise))(function(s,n){function a(h){try{l(i.next(h))}catch(c){n(c)}}function d(h){try{l(i.throw(h))}catch(c){n(c)}}function l(h){h.done?s(h.value):o(h.value).then(a,d)}l((i=i.apply(r,[])).next())})}function Ie(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var It,zt;function xe(){return zt||(zt=1,It=function r(t,e){if(t===e)return!0;if(t&&e&&typeof t=="object"&&typeof e=="object"){if(t.constructor!==e.constructor)return!1;var i,o,s;if(Array.isArray(t)){if(i=t.length,i!=e.length)return!1;for(o=i;o--!==0;)if(!r(t[o],e[o]))return!1;return!0}if(t.constructor===RegExp)return t.source===e.source&&t.flags===e.flags;if(t.valueOf!==Object.prototype.valueOf)return t.valueOf()===e.valueOf();if(t.toString!==Object.prototype.toString)return t.toString()===e.toString();if(s=Object.keys(t),i=s.length,i!==Object.keys(e).length)return!1;for(o=i;o--!==0;)if(!Object.prototype.hasOwnProperty.call(e,s[o]))return!1;for(o=i;o--!==0;){var n=s[o];if(!r(t[n],e[n]))return!1}return!0}return t!==t&&e!==e}),It}var Ee=xe(),Me=Ie(Ee);const Kt="__googleMapsScriptId";var tt;(function(r){r[r.INITIALIZED=0]="INITIALIZED",r[r.LOADING=1]="LOADING",r[r.SUCCESS=2]="SUCCESS",r[r.FAILURE=3]="FAILURE"})(tt||(tt={}));class H{constructor({apiKey:t,authReferrerPolicy:e,channel:i,client:o,id:s=Kt,language:n,libraries:a=[],mapIds:d,nonce:l,region:h,retries:c=3,url:u="https://maps.googleapis.com/maps/api/js",version:p}){if(this.callbacks=[],this.done=!1,this.loading=!1,this.errors=[],this.apiKey=t,this.authReferrerPolicy=e,this.channel=i,this.client=o,this.id=s||Kt,this.language=n,this.libraries=a,this.mapIds=d,this.nonce=l,this.region=h,this.retries=c,this.url=u,this.version=p,H.instance){if(!Me(this.options,H.instance.options))throw new Error(`Loader must not be called again with different options. ${JSON.stringify(this.options)} !== ${JSON.stringify(H.instance.options)}`);return H.instance}H.instance=this}get options(){return{version:this.version,apiKey:this.apiKey,channel:this.channel,client:this.client,id:this.id,libraries:this.libraries,language:this.language,region:this.region,mapIds:this.mapIds,nonce:this.nonce,url:this.url,authReferrerPolicy:this.authReferrerPolicy}}get status(){return this.errors.length?tt.FAILURE:this.done?tt.SUCCESS:this.loading?tt.LOADING:tt.INITIALIZED}get failed(){return this.done&&!this.loading&&this.errors.length>=this.retries+1}createUrl(){let t=this.url;return t+="?callback=__googleMapsCallback&loading=async",this.apiKey&&(t+=`&key=${this.apiKey}`),this.channel&&(t+=`&channel=${this.channel}`),this.client&&(t+=`&client=${this.client}`),this.libraries.length>0&&(t+=`&libraries=${this.libraries.join(",")}`),this.language&&(t+=`&language=${this.language}`),this.region&&(t+=`®ion=${this.region}`),this.version&&(t+=`&v=${this.version}`),this.mapIds&&(t+=`&map_ids=${this.mapIds.join(",")}`),this.authReferrerPolicy&&(t+=`&auth_referrer_policy=${this.authReferrerPolicy}`),t}deleteScript(){const t=document.getElementById(this.id);t&&t.remove()}load(){return this.loadPromise()}loadPromise(){return new Promise((t,e)=>{this.loadCallback(i=>{i?e(i.error):t(window.google)})})}importLibrary(t){return this.execute(),google.maps.importLibrary(t)}loadCallback(t){this.callbacks.push(t),this.execute()}setScript(){var t,e;if(document.getElementById(this.id)){this.callback();return}const i={key:this.apiKey,channel:this.channel,client:this.client,libraries:this.libraries.length&&this.libraries,v:this.version,mapIds:this.mapIds,language:this.language,region:this.region,authReferrerPolicy:this.authReferrerPolicy};Object.keys(i).forEach(s=>!i[s]&&delete i[s]),!((e=(t=window==null?void 0:window.google)===null||t===void 0?void 0:t.maps)===null||e===void 0)&&e.importLibrary||(s=>{let n,a,d,l="The Google Maps JavaScript API",h="google",c="importLibrary",u="__ib__",p=document,g=window;g=g[h]||(g[h]={});const y=g.maps||(g.maps={}),v=new Set,m=new URLSearchParams,C=()=>n||(n=new Promise((f,P)=>Pe(this,void 0,void 0,function*(){var x;yield a=p.createElement("script"),a.id=this.id,m.set("libraries",[...v]+"");for(d in s)m.set(d.replace(/[A-Z]/g,w=>"_"+w[0].toLowerCase()),s[d]);m.set("callback",h+".maps."+u),a.src=this.url+"?"+m,y[u]=f,a.onerror=()=>n=P(Error(l+" could not load.")),a.nonce=this.nonce||((x=p.querySelector("script[nonce]"))===null||x===void 0?void 0:x.nonce)||"",p.head.append(a)})));y[c]?console.warn(l+" only loads once. Ignoring:",s):y[c]=(f,...P)=>v.add(f)&&C().then(()=>y[c](f,...P))})(i);const o=this.libraries.map(s=>this.importLibrary(s));o.length||o.push(this.importLibrary("core")),Promise.all(o).then(()=>this.callback(),s=>{const n=new ErrorEvent("error",{error:s});this.loadErrorCallback(n)})}reset(){this.deleteScript(),this.done=!1,this.loading=!1,this.errors=[],this.onerrorEvent=null}resetIfRetryingFailed(){this.failed&&this.reset()}loadErrorCallback(t){if(this.errors.push(t),this.errors.length<=this.retries){const e=this.errors.length*Math.pow(2,this.errors.length);console.error(`Failed to load Google Maps script, retrying in ${e} ms.`),setTimeout(()=>{this.deleteScript(),this.setScript()},e)}else this.onerrorEvent=t,this.callback()}callback(){this.done=!0,this.loading=!1,this.callbacks.forEach(t=>{t(this.onerrorEvent)}),this.callbacks=[]}execute(){if(this.resetIfRetryingFailed(),!this.loading)if(this.done)this.callback();else{if(window.google&&window.google.maps&&window.google.maps.version){console.warn("Google Maps already loaded outside @googlemaps/js-api-loader. This may result in undesirable behavior as options and script parameters may not match."),this.callback();return}this.loading=!0,this.setScript()}}}function M(){return M=Object.assign?Object.assign.bind():function(r){for(var t=1;tl*Math.PI/180,i=e(r[1]),o=e(r[0]),s=e(t[1]),n=s-i,a=e(t[0])-o,d=Math.sin(n/2)*Math.sin(n/2)+Math.cos(i)*Math.cos(s)*Math.sin(a/2)*Math.sin(a/2);return 2*Math.atan2(Math.sqrt(d),Math.sqrt(1-d))*6371e3/1e3}const ce=63710088e-1;function _(r){return r%360*Math.PI/180}function ue(r){return r/6371.0088}function V(r){return r%(2*Math.PI)*180/Math.PI}function b(r,t=9){const e=Math.pow(10,t);return Math.round(r*e)/e}const $t=57.29577951308232,Ht=.017453292519943295,vt=6378137,S=(r,t)=>({x:r===0?0:r*Ht*vt,y:t===0?0:Math.log(Math.tan(Math.PI/4+t*Ht/2))*vt}),ot=(r,t)=>({lng:r===0?0:$t*(r/vt),lat:t===0?0:(2*Math.atan(Math.exp(t/vt))-Math.PI/2)*$t});function _e(r,t,e){const i=_(r[0]),o=_(r[1]),s=_(e),n=ue(t),a=Math.asin(Math.sin(o)*Math.cos(n)+Math.cos(o)*Math.sin(n)*Math.cos(s));return[V(i+Math.atan2(Math.sin(s)*Math.sin(n)*Math.cos(o),Math.cos(n)-Math.sin(o)*Math.sin(a))),V(a)]}function qt(r){const{center:t,radiusKilometers:e,coordinatePrecision:i}=r,o=r.steps?r.steps:64,s=[];for(let n=0;n0;function i(s){return s<0||s>1}function o(s,n,a,d){const l=t[s][n],h=t[s][n+1],c=t[a][d],u=t[a][d+1],p=function(v,m,C,f){if(ct(v,C)||ct(v,f)||ct(m,C)||ct(f,C))return null;const P=v[0],x=v[1],w=m[0],F=m[1],O=C[0],B=C[1],D=f[0],W=f[1],K=(P-w)*(B-W)-(x-F)*(O-D);return K===0?null:[((P*F-x*w)*(O-D)-(P-w)*(O*W-B*D))/K,((P*F-x*w)*(B-W)-(x-F)*(O*W-B*D))/K]}(l,h,c,u);if(p===null)return;let g,y;g=h[0]!==l[0]?(p[0]-l[0])/(h[0]-l[0]):(p[1]-l[1])/(h[1]-l[1]),y=u[0]!==c[0]?(p[0]-c[0])/(u[0]-c[0]):(p[1]-c[1])/(u[1]-c[1]),i(g)||i(y)||(p.toString(),e.push(p))}}function ct(r,t){return r[0]===t[0]&&r[1]===t[1]}function Pt(r,t){return Jt(r[0])<=t&&Jt(r[1])<=t}function Bt(r){return r.length===2&&typeof r[0]=="number"&&typeof r[1]=="number"&&r[0]!==1/0&&r[1]!==1/0&&(e=r[0])>=-180&&e<=180&&(t=r[1])>=-90&&t<=90;var t,e}function Jt(r){let t=1,e=0;for(;Math.round(r*t)/t!==r;)t*=10,e++;return e}const De="Feature has holes",ke="Feature has less than 4 coordinates",Oe="Feature has invalid coordinates",Be="Feature coordinates are not closed";function jt(r,t){if(r.geometry.type!=="Polygon")return{valid:!1,reason:"Feature is not a Polygon"};if(r.geometry.coordinates.length!==1)return{valid:!1,reason:De};if(r.geometry.coordinates[0].length<4)return{valid:!1,reason:ke};for(let o=0;oge(e,this.coordinatePrecision))}updateCircle(t){if(this.clickCount===1&&this.center&&this.currentCircleId){const e=T(this.center,[t.lng,t.lat]);let i;if(this.projection==="web-mercator"){const o=function(s,n){const a=1e3*T(s,n);if(a===0)return 1;const{x:d,y:l}=S(s[0],s[1]),{x:h,y:c}=S(n[0],n[1]);return Math.sqrt(Math.pow(h-d,2)+Math.pow(c-l,2))/a}(this.center,[t.lng,t.lat]);i=function(s){const{center:n,radiusKilometers:a,coordinatePrecision:d}=s,l=s.steps?s.steps:64,h=1e3*a,[c,u]=n,{x:p,y:g}=S(c,u),y=[];for(let v=0;v{const{x:e,y:i}=r,{x:o,y:s}=t,n=o-e,a=s-i;return Math.sqrt(a*a+n*n)};function Lt(r){if(!function(e){const i=e.coordinates[0];let o=0;for(let s=0;s{this.preventNewFeature=!1},this.autoCloseTimeout),this.close()),this.setCursor(this.cursors.close),this.preventPointsNearClose)return}else this.hasLeftStartingPoint=!0,this.setCursor(this.cursors.start);if(djt(e,this.coordinatePrecision))}}class k{constructor({store:t,mode:e,project:i,unproject:o,pointerDistance:s,coordinatePrecision:n,projection:a}){this.store=void 0,this.mode=void 0,this.project=void 0,this.unproject=void 0,this.pointerDistance=void 0,this.coordinatePrecision=void 0,this.projection=void 0,this.store=t,this.mode=e,this.project=i,this.unproject=o,this.pointerDistance=s,this.coordinatePrecision=n,this.projection=a}}function ye({unproject:r,point:t,pointerDistance:e}){const i=e/2,{x:o,y:s}=t;return{type:"Feature",properties:{},geometry:{type:"Polygon",coordinates:[[r(o-i,s-i),r(o+i,s-i),r(o+i,s+i),r(o-i,s+i),r(o-i,s-i)].map(n=>[n.lng,n.lat])]}}}class lt extends k{constructor(t){super(t)}create(t){const{containerX:e,containerY:i}=t;return ye({unproject:this.unproject,point:{x:e,y:i},pointerDistance:this.pointerDistance})}}class ht extends k{constructor(t){super(t)}measure(t,e){const{x:i,y:o}=this.project(e[0],e[1]);return G({x:i,y:o},{x:t.containerX,y:t.containerY})}}class Ct extends k{constructor(t,e,i){super(t),this.config=void 0,this.pixelDistance=void 0,this.clickBoundingBox=void 0,this.getSnappableCoordinateFirstClick=o=>this.getSnappable(o,s=>!!(s.properties&&s.properties.mode===this.mode)).coordinate,this.getSnappableCoordinate=(o,s)=>this.getSnappable(o,n=>!!(n.properties&&n.properties.mode===this.mode&&n.id!==s)).coordinate,this.config=t,this.pixelDistance=e,this.clickBoundingBox=i}getSnappable(t,e){const i=this.clickBoundingBox.create(t),o=this.store.search(i,e),s={featureId:void 0,featureCoordinateIndex:void 0,coordinate:void 0,minDist:1/0};return o.forEach(n=>{let a;if(n.geometry.type==="Polygon")a=n.geometry.coordinates[0];else{if(n.geometry.type!=="LineString")return;a=n.geometry.coordinates}a.forEach((d,l)=>{const h=this.pixelDistance.measure(t,d);h180?n-=360:n<-180&&(n+=360),n}function Re(r,t,e){const i=[],o=r.length;let s,n,a,d=0;for(let h=0;h=d&&h===r.length-1);h++){if(d>t&&i.length===0){if(s=t-d,!s)return i.push(r[h]),i;n=Qt(r[h],r[h-1])-180,a=Zt(r[h],s,n),i.push(a)}if(d>=e)return s=e-d,s?(n=Qt(r[h],r[h-1])-180,a=Zt(r[h],s,n),i.push(a),i):(i.push(r[h]),i);if(d>=t&&i.push(r[h]),h===r.length-1)return i;d+=T(r[h],r[h+1])}if(d[b(e[0],this.config.coordinatePrecision),b(e[1],this.config.coordinatePrecision)])}}function mt(r,t){return r[0]===t[0]&&r[1]===t[1]}function Ve(r,t){if(r.geometry.type!=="LineString")return{valid:!1,reason:"Feature is not a LineString"};if(r.geometry.coordinates.length<2)return{valid:!1,reason:"Feature has less than 2 coordinates"};for(let e=0;ex||X(o,D)>x?T(Y(D),Y(i))<=T(Y(D),Y(o))?[Y(i),!0,!1]:[Y(o),!1,!0]:[Y(D),!1,!1]}function Ke(r,t,e){const i=t.x-r.x,o=t.y-r.y,s=Math.max(0,Math.min(1,((e.x-r.x)*i+(e.y-r.y)*o)/(i*i+o*o)));return{x:r.x+s*i,y:r.y+s*o}}class Wt extends k{constructor(t,e,i){super(t),this.config=void 0,this.pixelDistance=void 0,this.clickBoundingBox=void 0,this.getSnappableCoordinateFirstClick=o=>{const s=this.getSnappable(o,n=>!!(n.properties&&n.properties.mode===this.mode));return s.coordinate?[b(s.coordinate[0],this.config.coordinatePrecision),b(s.coordinate[1],this.config.coordinatePrecision)]:void 0},this.getSnappableCoordinate=(o,s)=>{const n=this.getSnappable(o,a=>!!(a.properties&&a.properties.mode===this.mode&&a.id!==s));return n.coordinate?[b(n.coordinate[0],this.config.coordinatePrecision),b(n.coordinate[1],this.config.coordinatePrecision)]:void 0},this.config=t,this.pixelDistance=e,this.clickBoundingBox=i}getSnappable(t,e){const i=this.clickBoundingBox.create(t),o=this.store.search(i,e),s={featureId:void 0,featureCoordinateIndex:void 0,coordinate:void 0,minDistance:1/0};return o.forEach(n=>{let a;if(n.geometry.type==="Polygon")a=n.geometry.coordinates[0];else{if(n.geometry.type!=="LineString")return;a=n.geometry.coordinates}const d=[];for(let u=0;uthis.lineStringFilter(n));if(!e||i===void 0)return;const o=this.store.getGeometryCopy(e);let s;if(o.type==="LineString"&&(s=o.coordinates,!(s.length<=2))){if(s.splice(i,1),this.validate&&!this.validate({id:e,type:"Feature",geometry:o,properties:{}},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:I.Commit}).valid)return;this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0),this.store.updateGeometry([{id:e,geometry:o}]),this.onFinish(e,{mode:this.mode,action:"edit"})}}onLeftClick(t){this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0);const e=this.snapCoordinate(t)||[t.lng,t.lat];this.currentCoordinate===0?this.createLine(e):this.currentCoordinate===1&&this.currentId?this.firstUpdateToLine(e):this.currentId&&this.updateToLine(e,{x:t.containerX,y:t.containerY})}onClick(t){(t.button==="right"&&this.allowPointerEvent(this.pointerEvents.rightClick,t)||t.button==="left"&&this.allowPointerEvent(this.pointerEvents.leftClick,t)||t.isContextMenu&&this.allowPointerEvent(this.pointerEvents.contextMenu,t))&&(this.currentCoordinate>0&&!this.mouseMove&&this.onMouseMove(t),this.mouseMove=!1,t.button==="right"?this.onRightClick(t):t.button==="left"&&this.onLeftClick(t))}onKeyDown(){}onKeyUp(t){t.key===this.keyEvents.cancel&&this.cleanUp(),t.key===this.keyEvents.finish&&this.close()}onDragStart(t,e){if(!this.allowPointerEvent(this.pointerEvents.onDragStart,t)||!this.editable)return;let i;if(this.state==="started"){const o=this.lineSnapping.getSnappable(t,n=>this.lineStringFilter(n));o.coordinate&&(this.editedSnapType="line",this.editedFeatureCoordinateIndex=o.featureCoordinateIndex,this.editedFeatureId=o.featureId,i=o.coordinate);const s=this.coordinateSnapping.getSnappable(t,n=>this.lineStringFilter(n));s.coordinate&&(this.editedSnapType="coordinate",this.editedFeatureCoordinateIndex=s.featureCoordinateIndex,this.editedFeatureId=s.featureId,i=s.coordinate)}if(this.editedFeatureId&&i){if(!this.editedPointId){const[o]=this.store.create([{geometry:{type:"Point",coordinates:i},properties:{mode:this.mode,[R]:!0}}]);this.editedPointId=o}this.setCursor(this.cursors.dragStart),e(!1)}}onDrag(t,e){if(!this.allowPointerEvent(this.pointerEvents.onDrag,t)||this.editedFeatureId===void 0||this.editedFeatureCoordinateIndex===void 0)return;const i=this.store.getGeometryCopy(this.editedFeatureId);this.editedSnapType==="coordinate"||this.editedSnapType==="line"&&this.editedInsertIndex!==void 0?i.coordinates[this.editedFeatureCoordinateIndex]=[t.lng,t.lat]:this.editedSnapType==="line"&&this.editedInsertIndex===void 0&&(this.editedInsertIndex=this.editedFeatureCoordinateIndex+1,i.coordinates.splice(this.editedInsertIndex,0,[t.lng,t.lat]),this.editedFeatureCoordinateIndex++);const o={type:"LineString",coordinates:i.coordinates};this.validate&&!this.validate({type:"Feature",geometry:o,properties:this.store.getPropertiesCopy(this.editedFeatureId)},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:I.Provisional}).valid||(this.snapping&&this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0),this.store.updateGeometry([{id:this.editedFeatureId,geometry:o}]),this.editedPointId&&this.store.updateGeometry([{id:this.editedPointId,geometry:{type:"Point",coordinates:[t.lng,t.lat]}}]),this.store.updateProperty([{id:this.editedFeatureId,property:R,value:!0}]))}onDragEnd(t,e){this.allowPointerEvent(this.pointerEvents.onDragEnd,t)&&this.editedFeatureId!==void 0&&(this.setCursor(this.cursors.dragEnd),this.editedPointId&&(this.store.delete([this.editedPointId]),this.editedPointId=void 0),this.store.updateProperty([{id:this.editedFeatureId,property:R,value:!1}]),this.onFinish(this.editedFeatureId,{mode:this.mode,action:"edit"}),this.editedFeatureId=void 0,this.editedFeatureCoordinateIndex=void 0,this.editedInsertIndex=void 0,this.editedSnapType=void 0,e(!0))}cleanUp(){const t=this.currentId,e=this.closingPointId,i=this.snappedPointId;this.closingPointId=void 0,this.snappedPointId=void 0,this.currentId=void 0,this.currentCoordinate=0,this.state==="drawing"&&this.setStarted();try{t!==void 0&&this.store.delete([t]),i!==void 0&&this.store.delete([i]),e!==void 0&&this.store.delete([e])}catch{}}styleFeature(t){const e=M({},{polygonFillColor:"#3f97e0",polygonOutlineColor:"#3f97e0",polygonOutlineWidth:4,polygonFillOpacity:.3,pointColor:"#3f97e0",pointOutlineColor:"#ffffff",pointOutlineWidth:0,pointWidth:6,lineStringColor:"#3f97e0",lineStringWidth:4,zIndex:0});if(t.type==="Feature"&&t.geometry.type==="LineString"&&t.properties.mode===this.mode)return e.lineStringColor=this.getHexColorStylingValue(this.styles.lineStringColor,e.lineStringColor,t),e.lineStringWidth=this.getNumericStylingValue(this.styles.lineStringWidth,e.lineStringWidth,t),e.zIndex=U,e;if(t.type==="Feature"&&t.geometry.type==="Point"&&t.properties.mode===this.mode){const i=t.properties[et];return e.pointColor=this.getHexColorStylingValue(i?this.styles.closingPointColor:this.styles.snappingPointColor,e.pointColor,t),e.pointWidth=this.getNumericStylingValue(i?this.styles.closingPointWidth:this.styles.snappingPointWidth,e.pointWidth,t),e.pointOutlineColor=this.getHexColorStylingValue(i?this.styles.closingPointOutlineColor:this.styles.snappingPointOutlineColor,"#ffffff",t),e.pointOutlineWidth=this.getNumericStylingValue(i?this.styles.closingPointOutlineWidth:this.styles.snappingPointOutlineWidth,2,t),e.zIndex=50,e}return e}validateFeature(t){return this.validateModeFeature(t,e=>Ve(e,this.coordinatePrecision))}lineStringFilter(t){return!!(t.geometry.type==="LineString"&&t.properties&&t.properties.mode===this.mode)}snapCoordinate(t){var e,i,o;let s;if((e=this.snapping)!=null&&e.toLine){let n;n=this.currentId?this.lineSnapping.getSnappableCoordinate(t,this.currentId):this.lineSnapping.getSnappableCoordinateFirstClick(t),n&&(s=n)}return(i=this.snapping)!=null&&i.toCoordinate&&(s=this.currentId?this.coordinateSnapping.getSnappableCoordinate(t,this.currentId):this.coordinateSnapping.getSnappableCoordinateFirstClick(t)),(o=this.snapping)!=null&&o.toCustom&&(s=this.snapping.toCustom(t,{currentCoordinate:this.currentCoordinate,currentId:this.currentId,getCurrentGeometrySnapshot:this.currentId?()=>this.store.getGeometryCopy(this.currentId):()=>null,project:this.project,unproject:this.unproject})),s}}const He="Feature is not a Point",qe="Feature has invalid coordinates",Je="Feature has coordinates with excessive precision";function Ze(r,t){return r.geometry.type!=="Point"?{valid:!1,reason:He}:Bt(r.geometry.coordinates)?Pt(r.geometry.coordinates,t)?{valid:!0}:{valid:!1,reason:Je}:{valid:!1,reason:qe}}const Qe={create:"crosshair",dragStart:"grabbing",dragEnd:"crosshair"};class ti extends z{constructor(t){super(t,!0),this.mode="point",this.cursors=Qe,this.editable=!1,this.editedFeatureId=void 0,this.pixelDistance=void 0,this.clickBoundingBox=void 0,this.updateOptions(t)}updateOptions(t){super.updateOptions(t),t!=null&&t.cursors&&(this.cursors=M({},this.cursors,t.cursors)),t!=null&&t.editable&&(this.editable=t.editable)}start(){this.setStarted(),this.setCursor(this.cursors.create)}stop(){this.cleanUp(),this.setStopped(),this.setCursor("unset")}onClick(t){t.button==="right"&&this.allowPointerEvent(this.pointerEvents.rightClick,t)||t.isContextMenu&&this.allowPointerEvent(this.pointerEvents.contextMenu,t)?this.onRightClick(t):t.button==="left"&&this.allowPointerEvent(this.pointerEvents.leftClick,t)&&this.onLeftClick(t)}onMouseMove(){}onKeyDown(){}onKeyUp(){}cleanUp(){this.editedFeatureId=void 0}onDragStart(t,e){if(this.allowPointerEvent(this.pointerEvents.onDragStart,t)){if(this.editable){const i=this.getNearestPointFeature(t);this.editedFeatureId=i==null?void 0:i.id}this.editedFeatureId&&(this.setCursor(this.cursors.dragStart),e(!1))}}onDrag(t,e){this.allowPointerEvent(this.pointerEvents.onDrag,t)&&this.editedFeatureId!==void 0&&(this.validate&&!this.validate({type:"Feature",geometry:{type:"Point",coordinates:[t.lng,t.lat]},properties:this.store.getPropertiesCopy(this.editedFeatureId)},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:I.Finish}).valid||(this.store.updateGeometry([{id:this.editedFeatureId,geometry:{type:"Point",coordinates:[t.lng,t.lat]}}]),this.store.updateProperty([{id:this.editedFeatureId,property:R,value:!0}])))}onDragEnd(t,e){this.allowPointerEvent(this.pointerEvents.onDragEnd,t)&&this.editedFeatureId!==void 0&&(this.onFinish(this.editedFeatureId,{mode:this.mode,action:"edit"}),this.setCursor(this.cursors.dragEnd),this.store.updateProperty([{id:this.editedFeatureId,property:R,value:!1}]),this.editedFeatureId=void 0,e(!0))}registerBehaviors(t){this.pixelDistance=new ht(t),this.clickBoundingBox=new lt(t)}styleFeature(t){const e=M({},{polygonFillColor:"#3f97e0",polygonOutlineColor:"#3f97e0",polygonOutlineWidth:4,polygonFillOpacity:.3,pointColor:"#3f97e0",pointOutlineColor:"#ffffff",pointOutlineWidth:0,pointWidth:6,lineStringColor:"#3f97e0",lineStringWidth:4,zIndex:0});if(t.type==="Feature"&&t.geometry.type==="Point"&&t.properties.mode===this.mode){const i=!!(t.id&&this.editedFeatureId===t.id);e.pointWidth=this.getNumericStylingValue(i?this.styles.editedPointWidth:this.styles.pointWidth,e.pointWidth,t),e.pointColor=this.getHexColorStylingValue(i?this.styles.editedPointColor:this.styles.pointColor,e.pointColor,t),e.pointOutlineColor=this.getHexColorStylingValue(i?this.styles.editedPointOutlineColor:this.styles.pointOutlineColor,e.pointOutlineColor,t),e.pointOutlineWidth=this.getNumericStylingValue(i?this.styles.editedPointOutlineWidth:this.styles.pointOutlineWidth,2,t),e.zIndex=30}return e}validateFeature(t){return this.validateModeFeature(t,e=>Ze(e,this.coordinatePrecision))}onLeftClick(t){const e={type:"Point",coordinates:[t.lng,t.lat]},i={mode:this.mode};if(this.validate&&!this.validate({type:"Feature",geometry:e,properties:i},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:I.Finish}).valid)return;const[o]=this.store.create([{geometry:e,properties:i}]);this.onFinish(o,{mode:this.mode,action:"draw"})}onRightClick(t){if(!this.editable)return;const e=this.getNearestPointFeature(t);e&&this.store.delete([e.id])}getNearestPointFeature(t){const e=this.clickBoundingBox.create(t),i=this.store.search(e);let o,s=1/0;for(let n=0;ns||d>this.pointerDistance||(s=d,o=a)}return o}}class ei extends k{constructor(t,e){super(t),this.config=void 0,this.pixelDistance=void 0,this._startEndPoints=[],this.config=t,this.pixelDistance=e}get ids(){return this._startEndPoints.concat()}set ids(t){}create(t,e){if(this.ids.length)throw new Error("Opening and closing points already created");if(t.length<=3)throw new Error("Requires at least 4 coordinates");this._startEndPoints=this.store.create([{geometry:{type:"Point",coordinates:t[0]},properties:{mode:e,[et]:!0}},{geometry:{type:"Point",coordinates:t[t.length-2]},properties:{mode:e,[et]:!0}}])}delete(){this.ids.length&&(this.store.delete(this.ids),this._startEndPoints=[])}update(t){if(this.ids.length!==2)throw new Error("No closing points to update");this.store.updateGeometry([{id:this.ids[0],geometry:{type:"Point",coordinates:t[0]}},{id:this.ids[1],geometry:{type:"Point",coordinates:t[t.length-3]}}])}isClosingPoint(t){const e=this.store.getGeometryCopy(this.ids[0]),i=this.store.getGeometryCopy(this.ids[1]),o=this.pixelDistance.measure(t,e.coordinates),s=this.pixelDistance.measure(t,i.coordinates);return{isClosing:othis.store.has(a))){const a=s.coordinatePointIds,d=a.map(l=>this.store.getGeometryCopy(l).coordinates);if(a.length!==o.length){this.deleteCoordinatePoints(a);const l=this.createPoints(o,i.mode,t);this.setFeatureCoordinatePoints(t,l)}else o.forEach((l,h)=>{l[0]===d[h][0]&&l[1]===d[h][1]||this.store.updateGeometry([{id:a[h],geometry:{type:"Point",coordinates:l}}])})}else{const a=n.filter(l=>this.store.has(l));a.length&&this.deleteCoordinatePoints(a);const d=this.createPoints(o,i.mode,t);this.setFeatureCoordinatePoints(t,d)}else{const a=this.createPoints(o,i.mode,t);this.setFeatureCoordinatePoints(t,a)}}deletePointsByFeatureIds(t){for(const e of t)this.deleteIfPresent(e)}getUpdated(t,e){const i=this.store.getPropertiesCopy(t);if(i.coordinatePointIds)return i.coordinatePointIds.map((o,s)=>({id:o,geometry:M({},this.store.getGeometryCopy(o),{coordinates:e[s]})}))}createPoints(t,e,i){return this.store.create(t.map((o,s)=>({geometry:{type:"Point",coordinates:o},properties:{mode:e,[Ot]:!0,[Se]:i,index:s}})))}setFeatureCoordinatePoints(t,e){this.store.updateProperty([{id:t,property:dt,value:e}])}deleteCoordinatePoints(t){const e=t.filter(i=>this.store.has(i));this.store.delete(e)}deleteIfPresent(t){const e=this.store.getPropertiesCopy(t).coordinatePointIds;e&&(this.deleteCoordinatePoints(e),this.setFeatureCoordinatePoints(t,null))}}const ii={cancel:"Escape",finish:"Enter"},oi={start:"crosshair",close:"pointer",dragStart:"grabbing",dragEnd:"crosshair"};class si extends z{constructor(t){super(t,!0),this.mode="polygon",this.currentCoordinate=0,this.currentId=void 0,this.keyEvents=ii,this.cursors=oi,this.mouseMove=!1,this.showCoordinatePoints=!1,this.snapping=void 0,this.snappedPointId=void 0,this.editable=!1,this.editedFeatureId=void 0,this.editedFeatureCoordinateIndex=void 0,this.editedSnapType=void 0,this.editedInsertIndex=void 0,this.editedPointId=void 0,this.coordinatePoints=void 0,this.lineSnapping=void 0,this.coordinateSnapping=void 0,this.pixelDistance=void 0,this.closingPoints=void 0,this.clickBoundingBox=void 0,this.updateOptions(t)}updateOptions(t){if(super.updateOptions(t),t!=null&&t.cursors&&(this.cursors=M({},this.cursors,t.cursors)),(t==null?void 0:t.keyEvents)===null?this.keyEvents={cancel:null,finish:null}:t!=null&&t.keyEvents&&(this.keyEvents=M({},this.keyEvents,t.keyEvents)),t!=null&&t.snapping&&(this.snapping=t.snapping),(t==null?void 0:t.editable)!==void 0&&(this.editable=t.editable),(t==null?void 0:t.pointerEvents)!==void 0&&(this.pointerEvents=t.pointerEvents),(t==null?void 0:t.showCoordinatePoints)!==void 0){if(this.showCoordinatePoints=t.showCoordinatePoints,this.coordinatePoints&&t.showCoordinatePoints===!0)this.store.copyAllWhere(e=>e.mode===this.mode).map(e=>e.id).forEach(e=>{this.coordinatePoints.createOrUpdate(e)});else if(this.coordinatePoints&&this.showCoordinatePoints===!1){const e=this.store.copyAllWhere(i=>i.mode===this.mode&&!!i[dt]);this.coordinatePoints.deletePointsByFeatureIds(e.map(i=>i.id))}}}close(){if(this.currentId===void 0)return;const t=this.store.getGeometryCopy(this.currentId).coordinates[0];if(t.length<5||!this.updatePolygonGeometry([...t.slice(0,-2),t[0]],I.Finish))return;const e=this.currentId;if(this.currentId){const i=Lt(this.store.getGeometryCopy(this.currentId));i&&this.store.updateGeometry([{id:this.currentId,geometry:i}]),this.store.updateProperty([{id:this.currentId,property:N,value:void 0}])}this.snappedPointId&&this.store.delete([this.snappedPointId]),this.currentCoordinate=0,this.currentId=void 0,this.snappedPointId=void 0,this.closingPoints.delete(),this.state==="drawing"&&this.setStarted(),this.onFinish(e,{mode:this.mode,action:"draw"})}registerBehaviors(t){this.clickBoundingBox=new lt(t),this.pixelDistance=new ht(t),this.lineSnapping=new Wt(t,this.pixelDistance,this.clickBoundingBox),this.coordinateSnapping=new Ct(t,this.pixelDistance,this.clickBoundingBox),this.closingPoints=new ei(t,this.pixelDistance),this.coordinatePoints=new fe(t)}start(){this.setStarted(),this.setCursor(this.cursors.start)}stop(){this.cleanUp(),this.setStopped(),this.setCursor("unset")}onMouseMove(t){this.mouseMove=!0,this.setCursor(this.cursors.start);const e=this.snapCoordinate(t);if(e){if(this.snappedPointId)this.store.updateGeometry([{id:this.snappedPointId,geometry:{type:"Point",coordinates:e}}]);else{const[s]=this.store.create([{geometry:{type:"Point",coordinates:e},properties:{mode:this.mode,[Ft]:!0}}]);this.snappedPointId=s}t.lng=e[0],t.lat=e[1]}else this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0);if(this.currentId===void 0||this.currentCoordinate===0)return;const i=this.store.getGeometryCopy(this.currentId).coordinates[0];let o;if(this.currentCoordinate===1){const s=1/Math.pow(10,this.coordinatePrecision-1),n=Math.max(1e-6,s);o=[i[0],[t.lng,t.lat],[t.lng,t.lat-n],i[0]]}else if(this.currentCoordinate===2)o=[i[0],i[1],[t.lng,t.lat],i[0]];else{const{isClosing:s,isPreviousClosing:n}=this.closingPoints.isClosingPoint(t);n||s?(this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0),this.setCursor(this.cursors.close),o=[...i.slice(0,-2),i[0],i[0]]):o=[...i.slice(0,-2),[t.lng,t.lat],i[0]]}this.updatePolygonGeometry(o,I.Provisional)}updatePolygonGeometry(t,e){if(!this.currentId)return!1;const i={type:"Polygon",coordinates:[t]};return!(this.validate&&!this.validate({type:"Feature",geometry:i},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:e}).valid||(this.store.updateGeometry([{id:this.currentId,geometry:i}]),this.showCoordinatePoints&&this.coordinatePoints.createOrUpdate(this.currentId),0))}snapCoordinate(t){var e,i,o;let s;if((e=this.snapping)!=null&&e.toLine){let n;n=this.currentId?this.lineSnapping.getSnappableCoordinate(t,this.currentId):this.lineSnapping.getSnappableCoordinateFirstClick(t),n&&(s=n)}if((i=this.snapping)!=null&&i.toCoordinate){let n;n=this.currentId?this.coordinateSnapping.getSnappableCoordinate(t,this.currentId):this.coordinateSnapping.getSnappableCoordinateFirstClick(t),n&&(s=n)}return(o=this.snapping)!=null&&o.toCustom&&(s=this.snapping.toCustom(t,{currentCoordinate:this.currentCoordinate,currentId:this.currentId,getCurrentGeometrySnapshot:this.currentId?()=>this.store.getGeometryCopy(this.currentId):()=>null,project:this.project,unproject:this.unproject})),s}polygonFilter(t){return!!(t.geometry.type==="Polygon"&&t.properties&&t.properties.mode===this.mode)}onRightClick(t){if(!this.editable||this.state!=="started")return;const{featureId:e,featureCoordinateIndex:i}=this.coordinateSnapping.getSnappable(t,n=>this.polygonFilter(n));if(!e||i===void 0)return;const o=this.store.getGeometryCopy(e);let s;o.type==="Polygon"&&(s=o.coordinates[0],s.length<=4||(o.type!=="Polygon"||i!==0&&i!==s.length-1?s.splice(i,1):(s.shift(),s.pop(),s.push([s[0][0],s[0][1]])),(!this.validate||this.validate({id:e,type:"Feature",geometry:o,properties:{}},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:I.Commit}).valid)&&(this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0),this.store.updateGeometry([{id:e,geometry:o}]),this.showCoordinatePoints&&this.coordinatePoints.createOrUpdate(e),this.onFinish(e,{mode:this.mode,action:"edit"}))))}onLeftClick(t){if(this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0),this.currentCoordinate===0){const e=this.snapCoordinate(t);e&&(t.lng=e[0],t.lat=e[1]);const[i]=this.store.create([{geometry:{type:"Polygon",coordinates:[[[t.lng,t.lat],[t.lng,t.lat],[t.lng,t.lat],[t.lng,t.lat]]]},properties:{mode:this.mode,[N]:!0}}]);this.currentId=i,this.currentCoordinate++,this.showCoordinatePoints&&this.coordinatePoints.createOrUpdate(i),this.setDrawing()}else if(this.currentCoordinate===1&&this.currentId){const e=this.snapCoordinate(t);e&&(t.lng=e[0],t.lat=e[1]);const i=this.store.getGeometryCopy(this.currentId);if(mt([t.lng,t.lat],i.coordinates[0][0])||!this.updatePolygonGeometry([i.coordinates[0][0],[t.lng,t.lat],[t.lng,t.lat],i.coordinates[0][0]],I.Commit))return;this.currentCoordinate++}else if(this.currentCoordinate===2&&this.currentId){const e=this.snapCoordinate(t);e&&(t.lng=e[0],t.lat=e[1]);const i=this.store.getGeometryCopy(this.currentId).coordinates[0];if(mt([t.lng,t.lat],i[1])||!this.updatePolygonGeometry([i[0],i[1],[t.lng,t.lat],[t.lng,t.lat],i[0]],I.Commit))return;this.currentCoordinate===2&&this.closingPoints.create(i,"polygon"),this.currentCoordinate++}else if(this.currentId){const e=this.store.getGeometryCopy(this.currentId).coordinates[0],{isClosing:i,isPreviousClosing:o}=this.closingPoints.isClosingPoint(t);if(o||i)this.close();else{const s=this.snapCoordinate(t);if(s&&(t.lng=s[0],t.lat=s[1]),mt([t.lng,t.lat],e[this.currentCoordinate-1]))return;const n=function(a=[[[0,0],[0,1],[1,1],[1,0],[0,0]]]){return{type:"Feature",geometry:{type:"Polygon",coordinates:a},properties:{}}}([[...e.slice(0,-1),[t.lng,t.lat],e[0]]]);if(!this.updatePolygonGeometry(n.geometry.coordinates[0],I.Commit))return;this.currentCoordinate++,this.closingPoints.ids.length&&this.closingPoints.update(n.geometry.coordinates[0])}}}onClick(t){this.currentCoordinate>0&&!this.mouseMove&&this.onMouseMove(t),this.mouseMove=!1,t.button==="right"&&this.allowPointerEvent(this.pointerEvents.rightClick,t)||t.isContextMenu&&this.allowPointerEvent(this.pointerEvents.contextMenu,t)?this.onRightClick(t):t.button==="left"&&this.allowPointerEvent(this.pointerEvents.leftClick,t)&&this.onLeftClick(t)}onKeyUp(t){t.key===this.keyEvents.cancel?this.cleanUp():t.key===this.keyEvents.finish&&this.close()}onKeyDown(){}onDragStart(t,e){if(!this.allowPointerEvent(this.pointerEvents.onDragStart,t)||!this.editable)return;let i;if(this.state==="started"){const o=this.lineSnapping.getSnappable(t,n=>this.polygonFilter(n));o.coordinate&&(this.editedSnapType="line",this.editedFeatureCoordinateIndex=o.featureCoordinateIndex,this.editedFeatureId=o.featureId,i=o.coordinate);const s=this.coordinateSnapping.getSnappable(t,n=>this.polygonFilter(n));s.coordinate&&(this.editedSnapType="coordinate",this.editedFeatureCoordinateIndex=s.featureCoordinateIndex,this.editedFeatureId=s.featureId,i=s.coordinate)}if(this.editedFeatureId&&i){if(!this.editedPointId){const[o]=this.store.create([{geometry:{type:"Point",coordinates:i},properties:{mode:this.mode,[R]:!0}}]);this.editedPointId=o}this.setCursor(this.cursors.dragStart),e(!1)}}onDrag(t,e){if(!this.allowPointerEvent(this.pointerEvents.onDrag,t)||this.editedFeatureId===void 0||this.editedFeatureCoordinateIndex===void 0)return;const i=this.store.getGeometryCopy(this.editedFeatureId),o=i.coordinates[0];this.editedSnapType==="coordinate"||this.editedSnapType==="line"&&this.editedInsertIndex!==void 0?this.editedFeatureCoordinateIndex===0||this.editedFeatureCoordinateIndex===i.coordinates[0].length-1?(o[0]=[t.lng,t.lat],o[o.length-1]=[t.lng,t.lat]):o[this.editedFeatureCoordinateIndex]=[t.lng,t.lat]:this.editedSnapType==="line"&&this.editedInsertIndex===void 0&&(this.editedInsertIndex=this.editedFeatureCoordinateIndex+1,i.coordinates[0].splice(this.editedInsertIndex,0,[t.lng,t.lat]),this.editedFeatureCoordinateIndex++);const s={type:"Polygon",coordinates:i.coordinates};this.validate&&!this.validate({type:"Feature",geometry:s,properties:this.store.getPropertiesCopy(this.editedFeatureId)},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:I.Provisional}).valid||(this.snapping&&this.snappedPointId&&(this.store.delete([this.snappedPointId]),this.snappedPointId=void 0),this.store.updateGeometry([{id:this.editedFeatureId,geometry:s}]),this.showCoordinatePoints&&this.coordinatePoints.createOrUpdate(this.editedFeatureId),this.editedPointId&&this.store.updateGeometry([{id:this.editedPointId,geometry:{type:"Point",coordinates:[t.lng,t.lat]}}]),this.store.updateProperty([{id:this.editedFeatureId,property:R,value:!0}]))}onDragEnd(t,e){this.allowPointerEvent(this.pointerEvents.onDragEnd,t)&&this.editedFeatureId!==void 0&&(this.setCursor(this.cursors.dragEnd),this.editedPointId&&(this.store.delete([this.editedPointId]),this.editedPointId=void 0),this.store.updateProperty([{id:this.editedFeatureId,property:R,value:!1}]),this.onFinish(this.editedFeatureId,{mode:this.mode,action:"edit"}),this.editedFeatureId=void 0,this.editedFeatureCoordinateIndex=void 0,this.editedInsertIndex=void 0,this.editedSnapType=void 0,e(!0))}cleanUp(){const t=this.currentId,e=this.snappedPointId,i=this.editedPointId;this.currentId=void 0,this.snappedPointId=void 0,this.editedPointId=void 0,this.editedFeatureId=void 0,this.editedFeatureCoordinateIndex=void 0,this.editedInsertIndex=void 0,this.editedSnapType=void 0,this.currentCoordinate=0,this.state==="drawing"&&this.setStarted();try{t&&this.coordinatePoints.deletePointsByFeatureIds([t]),t!==void 0&&this.store.delete([t]),i!==void 0&&this.store.delete([i]),e!==void 0&&this.store.delete([e]),this.closingPoints.ids.length&&this.closingPoints.delete()}catch{}}styleFeature(t){const e=M({},{polygonFillColor:"#3f97e0",polygonOutlineColor:"#3f97e0",polygonOutlineWidth:4,polygonFillOpacity:.3,pointColor:"#3f97e0",pointOutlineColor:"#ffffff",pointOutlineWidth:0,pointWidth:6,lineStringColor:"#3f97e0",lineStringWidth:4,zIndex:0});if(t.properties.mode===this.mode){if(t.geometry.type==="Polygon")return e.polygonFillColor=this.getHexColorStylingValue(this.styles.fillColor,e.polygonFillColor,t),e.polygonOutlineColor=this.getHexColorStylingValue(this.styles.outlineColor,e.polygonOutlineColor,t),e.polygonOutlineWidth=this.getNumericStylingValue(this.styles.outlineWidth,e.polygonOutlineWidth,t),e.polygonFillOpacity=this.getNumericStylingValue(this.styles.fillOpacity,e.polygonFillOpacity,t),e.zIndex=U,e;if(t.geometry.type==="Point"){const i=t.properties[R],o=t.properties[Ot],s=i?"editedPoint":t.properties[et]?"closingPoint":t.properties[Ft]?"snappingPoint":o?"coordinatePoint":void 0;if(!s)return e;const n={editedPoint:{width:this.styles.editedPointOutlineWidth,color:this.styles.editedPointColor,outlineColor:this.styles.editedPointOutlineColor,outlineWidth:this.styles.editedPointOutlineWidth},closingPoint:{width:this.styles.closingPointWidth,color:this.styles.closingPointColor,outlineColor:this.styles.closingPointOutlineColor,outlineWidth:this.styles.closingPointOutlineWidth},snappingPoint:{width:this.styles.snappingPointWidth,color:this.styles.snappingPointColor,outlineColor:this.styles.snappingPointOutlineColor,outlineWidth:this.styles.snappingPointOutlineWidth},coordinatePoint:{width:this.styles.coordinatePointWidth,color:this.styles.coordinatePointColor,outlineColor:this.styles.coordinatePointOutlineColor,outlineWidth:this.styles.coordinatePointOutlineWidth}};return e.pointWidth=this.getNumericStylingValue(n[s].width,e.pointWidth,t),e.pointColor=this.getHexColorStylingValue(n[s].color,e.pointColor,t),e.pointOutlineColor=this.getHexColorStylingValue(n[s].outlineColor,e.pointOutlineColor,t),e.pointOutlineWidth=this.getNumericStylingValue(n[s].outlineWidth,2,t),e.zIndex=i?40:o?20:30,e}}return e}afterFeatureAdded(t){this.showCoordinatePoints&&this.coordinatePoints.createOrUpdate(t.id)}validateFeature(t){return this.validateModeFeature(t,e=>jt(e,this.coordinatePrecision))}}const ni={cancel:"Escape",finish:"Enter"},ri={start:"crosshair"};class ai extends z{constructor(t){super(t,!0),this.mode="rectangle",this.center=void 0,this.clickCount=0,this.currentRectangleId=void 0,this.keyEvents=ni,this.cursors=ri,this.updateOptions(t)}updateOptions(t){super.updateOptions(t),t!=null&&t.cursors&&(this.cursors=M({},this.cursors,t.cursors)),(t==null?void 0:t.keyEvents)===null?this.keyEvents={cancel:null,finish:null}:t!=null&&t.keyEvents&&(this.keyEvents=M({},this.keyEvents,t.keyEvents))}updateRectangle(t,e){if(this.clickCount===1&&this.center&&this.currentRectangleId){const i=this.store.getGeometryCopy(this.currentRectangleId).coordinates[0][0],o={type:"Polygon",coordinates:[[i,[t.lng,i[1]],[t.lng,t.lat],[i[0],t.lat],i]]};if(this.validate&&!this.validate({id:this.currentRectangleId,geometry:o},{project:this.project,unproject:this.unproject,coordinatePrecision:this.coordinatePrecision,updateType:e}).valid)return;this.store.updateGeometry([{id:this.currentRectangleId,geometry:o}])}}close(){const t=this.currentRectangleId;if(t){const e=Lt(this.store.getGeometryCopy(t));e&&this.store.updateGeometry([{id:t,geometry:e}]),this.store.updateProperty([{id:t,property:N,value:void 0}])}this.center=void 0,this.currentRectangleId=void 0,this.clickCount=0,this.state==="drawing"&&this.setStarted(),t!==void 0&&this.onFinish(t,{mode:this.mode,action:"draw"})}start(){this.setStarted(),this.setCursor(this.cursors.start)}stop(){this.cleanUp(),this.setStopped(),this.setCursor("unset")}onClick(t){if(t.button==="right"&&this.allowPointerEvent(this.pointerEvents.rightClick,t)||t.button==="left"&&this.allowPointerEvent(this.pointerEvents.leftClick,t)||t.isContextMenu&&this.allowPointerEvent(this.pointerEvents.contextMenu,t))if(this.clickCount===0){this.center=[t.lng,t.lat];const[e]=this.store.create([{geometry:{type:"Polygon",coordinates:[[[t.lng,t.lat],[t.lng,t.lat],[t.lng,t.lat],[t.lng,t.lat]]]},properties:{mode:this.mode,[N]:!0}}]);this.currentRectangleId=e,this.clickCount++,this.setDrawing()}else this.updateRectangle(t,I.Finish),this.close()}onMouseMove(t){this.updateRectangle(t,I.Provisional)}onKeyDown(){}onKeyUp(t){t.key===this.keyEvents.cancel?this.cleanUp():t.key===this.keyEvents.finish&&this.close()}onDragStart(){}onDrag(){}onDragEnd(){}cleanUp(){const t=this.currentRectangleId;this.center=void 0,this.currentRectangleId=void 0,this.clickCount=0,this.state==="drawing"&&this.setStarted(),t!==void 0&&this.store.delete([t])}styleFeature(t){const e=M({},{polygonFillColor:"#3f97e0",polygonOutlineColor:"#3f97e0",polygonOutlineWidth:4,polygonFillOpacity:.3,pointColor:"#3f97e0",pointOutlineColor:"#ffffff",pointOutlineWidth:0,pointWidth:6,lineStringColor:"#3f97e0",lineStringWidth:4,zIndex:0});return t.type==="Feature"&&t.geometry.type==="Polygon"&&t.properties.mode===this.mode&&(e.polygonFillColor=this.getHexColorStylingValue(this.styles.fillColor,e.polygonFillColor,t),e.polygonOutlineColor=this.getHexColorStylingValue(this.styles.outlineColor,e.polygonOutlineColor,t),e.polygonOutlineWidth=this.getNumericStylingValue(this.styles.outlineWidth,e.polygonOutlineWidth,t),e.polygonFillOpacity=this.getNumericStylingValue(this.styles.fillOpacity,e.polygonFillOpacity,t),e.zIndex=U),e}validateFeature(t){return this.validateModeFeature(t,e=>ge(e,this.coordinatePrecision))}}function _t(r,t){const e=r,i=t,o=_(e[1]),s=_(i[1]);let n=_(i[0]-e[0]);n>Math.PI&&(n-=2*Math.PI),n<-Math.PI&&(n+=2*Math.PI);const a=Math.log(Math.tan(s/2+Math.PI/4)/Math.tan(o/2+Math.PI/4)),d=(V(Math.atan2(n,a))+360)%360;return d>180?-(360-d):d}function me(r,t,e){let i=t;t<0&&(i=-Math.abs(i));const o=i/ce,s=r[0]*Math.PI/180,n=_(r[1]),a=_(e),d=o*Math.cos(a);let l=n+d;Math.abs(l)>Math.PI/2&&(l=l>0?Math.PI-l:-Math.PI-l);const h=Math.log(Math.tan(l/2+Math.PI/4)/Math.tan(n/2+Math.PI/4)),c=Math.abs(h)>1e-11?d/h:Math.cos(n),u=[(180*(s+o*Math.sin(a)/c)/Math.PI+540)%360-180,180*l/Math.PI];return u[0]+=u[0]-r[0]>180?-360:r[0]-u[0]>180?360:0,u}function di(r,t,e,i,o){const s=i(r[0],r[1]),n=i(t[0],t[1]),{lng:a,lat:d}=o((s.x+n.x)/2,(s.y+n.y)/2);return[b(a,e),b(d,e)]}function li(r,t,e){const i=me(r,1e3*T(r,t)/2,_t(r,t));return[b(i[0],e),b(i[1],e)]}function ie({featureCoords:r,precision:t,unproject:e,project:i,projection:o}){const s=[];for(let n=0;n({geometry:{type:"Point",coordinates:h},properties:s(c)}))}(t,o=>({mode:this.mode,[L.MID_POINT]:!0,midPointSegment:o,midPointFeatureId:e}),i,this.config.project,this.config.unproject,this.projection))}delete(){this._midPoints.length&&(this.store.delete(this._midPoints),this._midPoints=[])}getUpdated(t){if(this._midPoints.length!==0)return ie({featureCoords:t,precision:this.coordinatePrecision,project:this.config.project,unproject:this.config.unproject,projection:this.config.projection}).map((e,i)=>({id:this._midPoints[i],geometry:{type:"Point",coordinates:e}}))}}class ci extends k{constructor(t){super(t),this._selectionPoints=[]}get ids(){return this._selectionPoints.concat()}set ids(t){}create(t,e,i){this._selectionPoints=this.store.create(function(o,s,n){const a=[],d=s==="Polygon"?o.length-1:o.length;for(let l=0;l({mode:this.mode,index:o,[L.SELECTION_POINT]:!0,[L.SELECTION_POINT_FEATURE_ID]:i})))}delete(){this.ids.length&&(this.store.delete(this.ids),this._selectionPoints=[])}getUpdated(t){if(this._selectionPoints.length!==0)return this._selectionPoints.map((e,i)=>({id:e,geometry:{type:"Point",coordinates:t[i]}}))}getOneUpdated(t,e){if(this._selectionPoints[t]!==void 0)return{id:this._selectionPoints[t],geometry:{type:"Point",coordinates:e}}}}function ve(r,t){let e=!1;for(let n=0,a=t.length;n(i=r)[1]!=(s=d[c])[1]>i[1]&&i[0]<(s[0]-o[0])*(i[1]-o[1])/(s[1]-o[1])+o[0]&&(e=!e)}var i,o,s;return e}const Dt=(r,t,e)=>{const i=s=>s*s,o=(s,n)=>i(s.x-n.x)+i(s.y-n.y);return Math.sqrt(((s,n,a)=>{const d=o(n,a);if(d===0)return o(s,n);let l=((s.x-n.x)*(a.x-n.x)+(s.y-n.y)*(a.y-n.y))/d;return l=Math.max(0,Math.min(1,l)),o(s,{x:n.x+l*(a.x-n.x),y:n.y+l*(a.y-n.y)})})(r,t,e))};class ui extends k{constructor(t,e,i){super(t),this.config=void 0,this.createClickBoundingBox=void 0,this.pixelDistance=void 0,this.config=t,this.createClickBoundingBox=e,this.pixelDistance=i}find(t,e){let i,o,s,n,a=1/0,d=1/0,l=1/0;const h=this.createClickBoundingBox.create(t),c=this.store.search(h);for(let u=0;u180||u<-180||p>90||p<-90)return!1;s[h]=[u,p]}i.type==="Polygon"&&(s[s.length-1]=[s[0][0],s[0][1]]);const a=this.selectionPoints.getUpdated(s)||[],d=this.midPoints.getUpdated(s)||[],l=this.coordinatePoints.getUpdated(this.draggedFeatureId,s)||[];if(e&&!e({type:"Feature",id:this.draggedFeatureId,geometry:i,properties:{}},{project:this.config.project,unproject:this.config.unproject,coordinatePrecision:this.config.coordinatePrecision,updateType:I.Provisional}).valid)return!1;this.store.updateGeometry([{id:this.draggedFeatureId,geometry:i},...a,...d,...l]),this.dragPosition=[t.lng,t.lat]}else i.type==="Point"&&(this.store.updateGeometry([{id:this.draggedFeatureId,geometry:{type:"Point",coordinates:o}}]),this.dragPosition=[t.lng,t.lat])}}class gi extends k{constructor(t,e,i,o,s,n,a){super(t),this.config=void 0,this.pixelDistance=void 0,this.selectionPoints=void 0,this.midPoints=void 0,this.coordinatePoints=void 0,this.coordinateSnapping=void 0,this.lineSnapping=void 0,this.draggedCoordinate={id:null,index:-1},this.config=t,this.pixelDistance=e,this.selectionPoints=i,this.midPoints=o,this.coordinatePoints=s,this.coordinateSnapping=n,this.lineSnapping=a}getClosestCoordinate(t,e){const i={dist:1/0,index:-1,isFirstOrLastPolygonCoord:!1};let o;if(e.type==="LineString")o=e.coordinates;else{if(e.type!=="Polygon")return i;o=e.coordinates[0]}for(let s=0;s!!(n.properties&&n.properties.mode===i.properties.mode&&n.id!==this.draggedCoordinate.id);if(e!=null&&e.toLine){let n;n=this.lineSnapping.getSnappable(t,s).coordinate,n&&(o=n)}if(e.toCoordinate){let n;n=this.coordinateSnapping.getSnappable(t,s).coordinate,n&&(o=n)}if(e!=null&&e.toCustom){let n;n=e.toCustom(t,{currentCoordinate:this.draggedCoordinate.index,currentId:i.id,getCurrentGeometrySnapshot:i.id?()=>this.store.getGeometryCopy(i.id):()=>null,project:this.project,unproject:this.unproject}),n&&(o=n)}return o}drag(t,e,i,o){const s=this.draggedCoordinate.id;if(s===null)return!1;const n=this.draggedCoordinate.index,a=this.store.getGeometryCopy(s),d=this.store.getPropertiesCopy(s),l=a.type==="LineString"?a.coordinates:a.coordinates[0],h=a.type==="Polygon"&&(n===l.length-1||n===0),c={type:"Feature",id:s,geometry:a,properties:d},u=this.snapCoordinate(t,o,c);if(t.lng>180||t.lng<-180||t.lat>90||t.lat<-90)return!1;if(h){const m=l.length-1;l[0]=u,l[m]=u}else l[n]=u;const p=this.selectionPoints.getOneUpdated(n,u),g=p?[p]:[],y=this.midPoints.getUpdated(l)||[],v=this.coordinatePoints.getUpdated(s,l)||[];return!(a.type!=="Point"&&!e&&pe({geometry:a})||i&&!i(c,{project:this.config.project,unproject:this.config.unproject,coordinatePrecision:this.config.coordinatePrecision,updateType:I.Provisional}).valid||(this.store.updateGeometry([{id:s,geometry:a},...g,...y,...v]),0))}isDragging(){return this.draggedCoordinate.id!==null}startDragging(t,e){this.draggedCoordinate={id:t,index:e}}stopDragging(){this.draggedCoordinate={id:null,index:-1}}}function oe(r){let t=0,e=0,i=0;return(r.geometry.type==="Polygon"?r.geometry.coordinates[0].slice(0,-1):r.geometry.coordinates).forEach(o=>{t+=o[0],e+=o[1],i++},!0),[t/i,e/i]}function kt(r){const t=(r.geometry.type==="Polygon"?r.geometry.coordinates[0]:r.geometry.coordinates).map(e=>{const{x:i,y:o}=S(e[0],e[1]);return[i,o]});return r.geometry.type==="Polygon"?function(e){let i=0,o=0,s=0;const n=e.length;for(let a=0;a{if(g===0||g===360||g===-360)return p;const y=.017453292519943295*g,v=(p.geometry.type==="Polygon"?p.geometry.coordinates[0]:p.geometry.coordinates).map(([f,P])=>S(f,P)),m=v.reduce((f,P)=>({x:f.x+P.x,y:f.y+P.y}),{x:0,y:0});m.x/=v.length,m.y/=v.length;const C=v.map(f=>({x:m.x+(f.x-m.x)*Math.cos(y)-(f.y-m.y)*Math.sin(y),y:m.y+(f.x-m.x)*Math.sin(y)+(f.y-m.y)*Math.cos(y)})).map(({x:f,y:P})=>[ot(f,P).lng,ot(f,P).lat]);p.geometry.type==="Polygon"?p.geometry.coordinates[0]=C:p.geometry.coordinates=C})(a,-(this.lastBearing-n))}else{if(this.config.projection!=="globe")throw new Error("Unsupported projection");if(this.selectedGeometryCentroid||(this.selectedGeometryCentroid=oe({geometry:o})),n=_t(this.selectedGeometryCentroid,s),!this.lastBearing)return void(this.lastBearing=n+180);(function(u,p){if(p===0||p===360||p===-360)return u;const g=oe(u);(u.geometry.type==="Polygon"?u.geometry.coordinates[0]:u.geometry.coordinates).forEach(y=>{const v=_t(g,y)+p,m=function(f,P){f[0]+=f[0]-P[0]>180?-360:P[0]-f[0]>180?360:0;const x=ce,w=P[1]*Math.PI/180,F=f[1]*Math.PI/180,O=F-w;let B=Math.abs(f[0]-P[0])*Math.PI/180;B>Math.PI&&(B-=2*Math.PI);const D=Math.log(Math.tan(F/2+Math.PI/4)/Math.tan(w/2+Math.PI/4)),W=Math.abs(D)>1e-11?O/D:Math.cos(w);return Math.sqrt(O*O+W*W*B*B)*x}(g,y),C=me(g,m,v);y[0]=C[0],y[1]=C[1]})})(a,-(this.lastBearing-(n+180)))}const d=o.type==="Polygon"?o.coordinates[0]:o.coordinates;d.forEach(u=>{u[0]=b(u[0],this.coordinatePrecision),u[1]=b(u[1],this.coordinatePrecision)});const l=this.midPoints.getUpdated(d)||[],h=this.selectionPoints.getUpdated(d)||[],c=this.coordinatePoints.getUpdated(e,d)||[];if(i&&!i({id:e,type:"Feature",geometry:o,properties:{}},{project:this.config.project,unproject:this.config.unproject,coordinatePrecision:this.config.coordinatePrecision,updateType:I.Provisional}))return!1;this.store.updateGeometry([{id:e,geometry:o},...h,...l,...c]),this.projection==="web-mercator"?this.lastBearing=n:this.projection==="globe"&&(this.lastBearing=n+180)}}class fi extends k{constructor(t,e){super(t),this.config=void 0,this.dragCoordinateResizeBehavior=void 0,this.config=t,this.dragCoordinateResizeBehavior=e}scale(t,e,i){if(!this.dragCoordinateResizeBehavior.isDragging()){const o=this.dragCoordinateResizeBehavior.getDraggableIndex(t,e);this.dragCoordinateResizeBehavior.startDragging(e,o)}this.dragCoordinateResizeBehavior.drag(t,"center-fixed",i)}reset(){this.dragCoordinateResizeBehavior.stopDragging()}}class mi extends k{constructor(t,e,i,o,s){super(t),this.config=void 0,this.pixelDistance=void 0,this.selectionPoints=void 0,this.midPoints=void 0,this.coordinatePoints=void 0,this.minimumScale=1e-4,this.draggedCoordinate={id:null,index:-1},this.boundingBoxMaps={opposite:{0:4,1:5,2:6,3:7,4:0,5:1,6:2,7:3}},this.config=t,this.pixelDistance=e,this.selectionPoints=i,this.midPoints=o,this.coordinatePoints=s}getClosestCoordinate(t,e){const i={dist:1/0,index:-1,isFirstOrLastPolygonCoord:!1};let o;if(e.type==="LineString")o=e.coordinates;else{if(e.type!=="Polygon")return i;o=e.coordinates[0]}for(let s=0;s=0)return!1;break;case 1:if(i>=0)return!1;break;case 2:if(e>=0||i>=0)return!1;break;case 3:if(e>=0)return!1;break;case 4:if(e>=0||i<=0)return!1;break;case 5:if(i<=0)return!1;break;case 6:if(e<=0||i<=0)return!1;break;case 7:if(e<=0)return!1}return!0}getSelectedFeatureDataWebMercator(){if(!this.draggedCoordinate.id||this.draggedCoordinate.index===-1)return null;const t=this.getFeature(this.draggedCoordinate.id);if(!t)return null;const e=this.getNormalisedCoordinates(t.geometry);return{boundingBox:this.getBBoxWebMercator(e),feature:t,updatedCoords:e,selectedCoordinate:e[this.draggedCoordinate.index]}}centerWebMercatorDrag(t){const e=this.getSelectedFeatureDataWebMercator();if(!e)return null;const{feature:i,boundingBox:o,updatedCoords:s,selectedCoordinate:n}=e,a=kt(i);if(!a)return null;const d=S(n[0],n[1]),{closestBBoxIndex:l}=this.getIndexesWebMercator(o,d),h=S(t.lng,t.lat);return this.scaleWebMercator({closestBBoxIndex:l,updatedCoords:s,webMercatorCursor:h,webMercatorSelected:d,webMercatorOrigin:a}),s}centerFixedWebMercatorDrag(t){const e=this.getSelectedFeatureDataWebMercator();if(!e)return null;const{feature:i,boundingBox:o,updatedCoords:s,selectedCoordinate:n}=e,a=kt(i);if(!a)return null;const d=S(n[0],n[1]),{closestBBoxIndex:l}=this.getIndexesWebMercator(o,d),h=S(t.lng,t.lat);return this.scaleFixedWebMercator({closestBBoxIndex:l,updatedCoords:s,webMercatorCursor:h,webMercatorSelected:d,webMercatorOrigin:a}),s}scaleFixedWebMercator({closestBBoxIndex:t,webMercatorOrigin:e,webMercatorSelected:i,webMercatorCursor:o,updatedCoords:s}){if(!this.isValidDragWebMercator(t,e.x-o.x,e.y-o.y))return null;let n=G(e,o)/G(e,i);return n<0&&(n=this.minimumScale),this.performWebMercatorScale(s,e.x,e.y,n,n),s}oppositeFixedWebMercatorDrag(t){const e=this.getSelectedFeatureDataWebMercator();if(!e)return null;const{boundingBox:i,updatedCoords:o,selectedCoordinate:s}=e,n=S(s[0],s[1]),{oppositeBboxIndex:a,closestBBoxIndex:d}=this.getIndexesWebMercator(i,n),l={x:i[a][0],y:i[a][1]},h=S(t.lng,t.lat);return this.scaleFixedWebMercator({closestBBoxIndex:d,updatedCoords:o,webMercatorCursor:h,webMercatorSelected:n,webMercatorOrigin:l}),o}oppositeWebMercatorDrag(t){const e=this.getSelectedFeatureDataWebMercator();if(!e)return null;const{boundingBox:i,updatedCoords:o,selectedCoordinate:s}=e,n=S(s[0],s[1]),{oppositeBboxIndex:a,closestBBoxIndex:d}=this.getIndexesWebMercator(i,n),l={x:i[a][0],y:i[a][1]},h=S(t.lng,t.lat);return this.scaleWebMercator({closestBBoxIndex:d,updatedCoords:o,webMercatorCursor:h,webMercatorSelected:n,webMercatorOrigin:l}),o}scaleWebMercator({closestBBoxIndex:t,webMercatorOrigin:e,webMercatorSelected:i,webMercatorCursor:o,updatedCoords:s}){const n=e.x-o.x,a=e.y-o.y;if(!this.isValidDragWebMercator(t,n,a))return null;let d=1;n!==0&&t!==1&&t!==5&&(d=1-(e.x-i.x-n)/n);let l=1;return a!==0&&t!==3&&t!==7&&(l=1-(e.y-i.y-a)/a),this.validateScale(d,l)?(d<0&&(d=this.minimumScale),l<0&&(l=this.minimumScale),this.performWebMercatorScale(s,e.x,e.y,d,l),s):null}getFeature(t){if(this.draggedCoordinate.id===null)return null;const e=this.store.getGeometryCopy(t);return e.type!=="Polygon"&&e.type!=="LineString"?null:{id:t,type:"Feature",geometry:e,properties:{}}}getNormalisedCoordinates(t){return t.type==="Polygon"?t.coordinates[0]:t.coordinates}validateScale(t,e){const i=!isNaN(t)&&e{const{x:a,y:d}=S(n[0],n[1]),l=e+(a-e)*o,h=i+(d-i)*s,{lng:c,lat:u}=ot(l,h);n[0]=c,n[1]=u})}getBBoxWebMercator(t){const e=[1/0,1/0,-1/0,-1/0];(t=t.map(a=>{const{x:d,y:l}=S(a[0],a[1]);return[d,l]})).forEach(([a,d])=>{ae[2]&&(e[2]=a),d>e[3]&&(e[3]=d)});const[i,o,s,n]=e;return[[i,n],[(i+s)/2,n],[s,n],[s,n+(o-n)/2],[s,o],[(i+s)/2,o],[i,o],[i,n+(o-n)/2]]}getIndexesWebMercator(t,e){let i,o=1/0;for(let s=0;sthis.store.has(e)).map(e=>({id:e,property:L.SELECTED,value:!1}));this.store.updateProperty(t),this.onDeselect(this.selected[0]),this.selected=[],this.selectionPoints.delete(),this.midPoints.delete()}deleteSelected(){this.store.delete(this.selected),this.selected=[]}onRightClick(t){if(!this.selectionPoints.ids.length)return;let e,i=1/0;if(this.selectionPoints.ids.forEach(u=>{const p=this.store.getGeometryCopy(u),g=this.pixelDistance.measure(t,p.coordinates);g0);if(this.selected.length&&i)this.midPoints.insert(this.selected[0],i.id,this.coordinatePrecision);else if(e&&e.id)this.select(e.id,!0);else if(this.selected.length&&this.allowManualDeselection)return void this.deselect()}start(){this.setStarted(),this.setSelecting()}stop(){this.cleanUp(),this.setStarted(),this.setStopped()}onClick(t){t.button==="right"&&this.allowPointerEvent(this.pointerEvents.rightClick,t)||t.isContextMenu&&this.allowPointerEvent(this.pointerEvents.contextMenu,t)?this.onRightClick(t):t.button==="left"&&this.allowPointerEvent(this.pointerEvents.leftClick,t)&&this.onLeftClick(t)}canScale(t){return this.keyEvents.scale&&this.keyEvents.scale.every(e=>t.heldKeys.includes(e))}canRotate(t){return this.keyEvents.rotate&&this.keyEvents.rotate.every(e=>t.heldKeys.includes(e))}preventDefaultKeyEvent(t){const e=this.canRotate(t),i=this.canScale(t);(e||i)&&t.preventDefault()}onKeyDown(t){this.preventDefaultKeyEvent(t)}onKeyUp(t){if(this.preventDefaultKeyEvent(t),this.keyEvents.delete&&t.key===this.keyEvents.delete){if(!this.selected.length)return;const e=this.selected[0];this.onDeselect(this.selected[0]),this.coordinatePoints.deletePointsByFeatureIds([e]),this.deleteSelected(),this.selectionPoints.delete(),this.midPoints.delete()}else this.keyEvents.deselect&&t.key===this.keyEvents.deselect&&this.cleanUp()}cleanUp(){this.selected.length&&this.deselect()}onDragStart(t,e){if(!this.allowPointerEvent(this.pointerEvents.onDragStart,t)||!this.selected.length)return;const i=this.store.getPropertiesCopy(this.selected[0]),o=this.flags[i.mode];if(!(o&&o.feature&&(o.feature.draggable||o.feature.coordinates&&o.feature.coordinates.draggable||o.feature.coordinates&&o.feature.coordinates.resizable||o.feature.coordinates&&typeof o.feature.coordinates.midpoints=="object"&&o.feature.coordinates.midpoints.draggable)))return;this.dragEventCount=0;const s=this.selected[0],n=this.dragCoordinate.getDraggableIndex(t,s);if(o&&o.feature&&o.feature.coordinates&&(o.feature.coordinates.draggable||o.feature.coordinates.resizable)&&n!==-1)return this.setCursor(this.cursors.dragStart),o.feature.coordinates.resizable?this.dragCoordinateResizeFeature.startDragging(s,n):this.dragCoordinate.startDragging(s,n),void e(!1);if(o&&o.feature&&o.feature.coordinates&&typeof o.feature.coordinates.midpoints=="object"&&o.feature.coordinates.midpoints.draggable){const{clickedMidPoint:a}=this.featuresAtMouseEvent.find(t,this.selected.length>0);if(this.selected.length&&a){this.midPoints.insert(s,a.id,this.coordinatePrecision);const d=this.dragCoordinate.getDraggableIndex(t,s);return this.dragCoordinate.startDragging(s,d),void e(!1)}}return o&&o.feature&&o.feature.draggable&&this.dragFeature.canDrag(t,s)?(this.setCursor(this.cursors.dragStart),this.dragFeature.startDragging(t,s),void e(!1)):void 0}onDrag(t,e){if(!this.allowPointerEvent(this.pointerEvents.onDrag,t))return;const i=this.selected[0];if(!i)return;const o=this.store.getPropertiesCopy(i),s=this.flags[o.mode],n=(s&&s.feature&&s.feature.selfIntersectable)===!0;if(this.dragEventCount++,this.dragEventCount%this.dragEventThrottle==0)return;const a=this.validations[o.mode];if(s&&s.feature&&s.feature.rotateable&&this.canRotate(t))return e(!1),void this.rotateFeature.rotate(t,i,a);if(s&&s.feature&&s.feature.scaleable&&this.canScale(t))return e(!1),void this.scaleFeature.scale(t,i,a);if(this.dragCoordinateResizeFeature.isDragging()&&s.feature&&s.feature.coordinates&&s.feature.coordinates.resizable){if(this.projection==="globe")throw new Error("Globe is currently unsupported projection for resizable");return e(!1),void this.dragCoordinateResizeFeature.drag(t,s.feature.coordinates.resizable,a)}if(this.dragCoordinate.isDragging()){var d;const l=(d=s.feature)==null||(d=d.coordinates)==null?void 0:d.snappable;let h={toCoordinate:!1};return l===!0?h={toCoordinate:!0}:typeof l=="object"&&(h=l),void this.dragCoordinate.drag(t,n,a,h)}this.dragFeature.isDragging()?this.dragFeature.drag(t,a):e(!0)}onDragEnd(t,e){this.allowPointerEvent(this.pointerEvents.onDragEnd,t)&&(this.setCursor(this.cursors.dragEnd),this.dragCoordinate.isDragging()?this.onFinish(this.selected[0],{mode:this.mode,action:"dragCoordinate"}):this.dragFeature.isDragging()?this.onFinish(this.selected[0],{mode:this.mode,action:"dragFeature"}):this.dragCoordinateResizeFeature.isDragging()&&this.onFinish(this.selected[0],{mode:this.mode,action:"dragCoordinateResize"}),this.dragCoordinate.stopDragging(),this.dragFeature.stopDragging(),this.dragCoordinateResizeFeature.stopDragging(),this.rotateFeature.reset(),this.scaleFeature.reset(),e(!0))}onMouseMove(t){if(!this.selected.length)return void this.setCursor("unset");if(this.dragFeature.isDragging())return;let e=!1;this.midPoints.ids.forEach(s=>{if(e)return;const n=this.store.getGeometryCopy(s);this.pixelDistance.measure(t,n.coordinates){const n=this.store.getGeometryCopy(s);this.pixelDistance.measure(t,n.coordinates)0&&(o&&o.id===this.selected[0]||i)?this.cursors.pointerOver:"unset")}styleFeature(t){const e=M({},{polygonFillColor:"#3f97e0",polygonOutlineColor:"#3f97e0",polygonOutlineWidth:4,polygonFillOpacity:.3,pointColor:"#3f97e0",pointOutlineColor:"#ffffff",pointOutlineWidth:0,pointWidth:6,lineStringColor:"#3f97e0",lineStringWidth:4,zIndex:0});if(t.properties.mode===this.mode&&t.geometry.type==="Point"){if(t.properties.selectionPoint)return e.pointColor=this.getHexColorStylingValue(this.styles.selectionPointColor,e.pointColor,t),e.pointOutlineColor=this.getHexColorStylingValue(this.styles.selectionPointOutlineColor,e.pointOutlineColor,t),e.pointWidth=this.getNumericStylingValue(this.styles.selectionPointWidth,e.pointWidth,t),e.pointOutlineWidth=this.getNumericStylingValue(this.styles.selectionPointOutlineWidth,2,t),e.zIndex=30,e;if(t.properties.midPoint)return e.pointColor=this.getHexColorStylingValue(this.styles.midPointColor,e.pointColor,t),e.pointOutlineColor=this.getHexColorStylingValue(this.styles.midPointOutlineColor,e.pointOutlineColor,t),e.pointWidth=this.getNumericStylingValue(this.styles.midPointWidth,4,t),e.pointOutlineWidth=this.getNumericStylingValue(this.styles.midPointOutlineWidth,2,t),e.zIndex=50,e}else if(t.properties[L.SELECTED]){if(t.geometry.type==="Polygon")return e.polygonFillColor=this.getHexColorStylingValue(this.styles.selectedPolygonColor,e.polygonFillColor,t),e.polygonOutlineWidth=this.getNumericStylingValue(this.styles.selectedPolygonOutlineWidth,e.polygonOutlineWidth,t),e.polygonOutlineColor=this.getHexColorStylingValue(this.styles.selectedPolygonOutlineColor,e.polygonOutlineColor,t),e.polygonFillOpacity=this.getNumericStylingValue(this.styles.selectedPolygonFillOpacity,e.polygonFillOpacity,t),e.zIndex=U,e;if(t.geometry.type==="LineString")return e.lineStringColor=this.getHexColorStylingValue(this.styles.selectedLineStringColor,e.lineStringColor,t),e.lineStringWidth=this.getNumericStylingValue(this.styles.selectedLineStringWidth,e.lineStringWidth,t),e.zIndex=U,e;if(t.geometry.type==="Point")return e.pointWidth=this.getNumericStylingValue(this.styles.selectedPointWidth,e.pointWidth,t),e.pointColor=this.getHexColorStylingValue(this.styles.selectedPointColor,e.pointColor,t),e.pointOutlineColor=this.getHexColorStylingValue(this.styles.selectedPointOutlineColor,e.pointOutlineColor,t),e.pointOutlineWidth=this.getNumericStylingValue(this.styles.selectedPointOutlineWidth,e.pointOutlineWidth,t),e.zIndex=U,e}return e}}class Pi extends z{constructor(...t){super(...t),this.type=it.Static,this.mode="static"}start(){}stop(){}onKeyUp(){}onKeyDown(){}onClick(){}onDragStart(){}onDrag(){}onDragEnd(){}onMouseMove(){}cleanUp(){}styleFeature(){return M({},{polygonFillColor:"#3f97e0",polygonOutlineColor:"#3f97e0",polygonOutlineWidth:4,polygonFillOpacity:.3,pointColor:"#3f97e0",pointOutlineColor:"#ffffff",pointOutlineWidth:0,pointWidth:6,lineStringColor:"#3f97e0",lineStringWidth:4,zIndex:0})}}function Ce(r,t,e,i,o){for(;i>e;){if(i-e>600){const d=i-e+1,l=t-e+1,h=Math.log(d),c=.5*Math.exp(2*h/3),u=.5*Math.sqrt(h*c*(d-c)/d)*(l-d/2<0?-1:1);Ce(r,t,Math.max(e,Math.floor(t-l*c/d+u)),Math.min(i,Math.floor(t+(d-l)*c/d+u)),o)}const s=r[t];let n=e,a=i;for(st(r,e,t),o(r[i],s)>0&&st(r,e,i);n0;)a--}o(r[e],s)===0?st(r,e,a):(a++,st(r,a,i)),a<=t&&(e=a+1),t<=a&&(i=a-1)}}function st(r,t,e){const i=r[t];r[t]=r[e],r[e]=i}function q(r,t){rt(r,0,r.children.length,t,r)}function rt(r,t,e,i,o){o||(o=Q([])),o.minX=1/0,o.minY=1/0,o.maxX=-1/0,o.maxY=-1/0;for(let s=t;s=r.minX&&t.maxY>=r.minY}function Q(r){return{children:r,height:1,leaf:!0,minX:1/0,minY:1/0,maxX:-1/0,maxY:-1/0}}function ne(r,t,e,i,o){const s=[t,e];for(;s.length;){if((e=s.pop())-(t=s.pop())<=i)continue;const n=t+Math.ceil((e-t)/i/2)*i;Ce(r,n,t,e,o),s.push(t,n,n,e)}}class Mi{constructor(t){this._maxEntries=void 0,this._minEntries=void 0,this.data=void 0,this._maxEntries=Math.max(4,t),this._minEntries=Math.max(2,Math.ceil(.4*this._maxEntries)),this.clear()}search(t){let e=this.data;const i=[];if(!gt(t,e))return i;const o=this.toBBox,s=[];for(;e;){for(let n=0;n=0&&s[e].children.length>this._maxEntries;)this._split(s,e),e--;this._adjustParentBBoxes(o,s,e)}_split(t,e){const i=t[e],o=i.children.length,s=this._minEntries;this._chooseSplitAxis(i,s,o);const n=this._chooseSplitIndex(i,s,o),a=Q(i.children.splice(n,i.children.length-n));a.height=i.height,a.leaf=i.leaf,q(i,this.toBBox),q(a,this.toBBox),e?t[e-1].children.push(a):this._splitRoot(i,a)}_splitRoot(t,e){this.data=Q([t,e]),this.data.height=t.height+1,this.data.leaf=!1,q(this.data,this.toBBox)}_chooseSplitIndex(t,e,i){let o,s=1/0,n=1/0;for(let a=e;a<=i-e;a++){const d=rt(t,0,a,this.toBBox),l=rt(t,a,i,this.toBBox),h=Ei(d,l),c=Mt(d)+Mt(l);h=e;l--){const h=t.children[l];at(a,t.leaf?s(h):h),d+=pt(a)}return d}_adjustParentBBoxes(t,e,i){for(let o=i;o>=0;o--)at(e[o],t)}_condense(t){for(let e,i=t.length-1;i>=0;i--)t[i].children.length===0?i>0?(e=t[i-1].children,e.splice(e.indexOf(t[i]),1)):this.clear():q(t[i],this.toBBox)}}class Si{constructor(t){this.tree=void 0,this.idToNode=void 0,this.nodeToId=void 0,this.tree=new Mi(t&&t.maxEntries?t.maxEntries:9),this.idToNode=new Map,this.nodeToId=new Map}setMaps(t,e){this.idToNode.set(t.id,e),this.nodeToId.set(e,t.id)}toBBox(t){const e=[],i=[];let o;if(t.geometry.type==="Polygon")o=t.geometry.coordinates[0];else if(t.geometry.type==="LineString")o=t.geometry.coordinates;else{if(t.geometry.type!=="Point")throw new Error("Not a valid feature to turn into a bounding box");o=[t.geometry.coordinates]}for(let a=0;a{const s=this.toBBox(o);if(this.setMaps(o,s),i.has(String(o.id)))throw new Error(`Duplicate feature ID found ${o.id}`);i.add(String(o.id)),e.push(s)}),this.tree.load(e)}update(t){this.remove(t.id);const e=this.toBBox(t);this.setMaps(t,e),this.tree.insert(e)}remove(t){const e=this.idToNode.get(t);if(!e)throw new Error(`${t} not inserted into the spatial index`);this.tree.remove(e)}clear(){this.tree.clear()}search(t){return this.tree.search(this.toBBox(t)).map(e=>this.nodeToId.get(e))}collides(t){return this.tree.collides(this.toBBox(t))}}const bi={getId:()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(r){const t=16*Math.random()|0;return(r=="x"?t:3&t|8).toString(16)}),isValidId:r=>typeof r=="string"&&r.length===36};class wi{constructor(t){this.idStrategy=void 0,this.tracked=void 0,this.spatialIndex=void 0,this.store=void 0,this._onChange=()=>{},this.store={},this.spatialIndex=new Si,this.tracked=!t||t.tracked!==!1,this.idStrategy=t&&t.idStrategy?t.idStrategy:bi}clone(t){return JSON.parse(JSON.stringify(t))}getId(){return this.idStrategy.getId()}has(t){return!!this.store[t]}load(t,e,i,o){if(t.length===0)return[];let s=this.clone(t);const n=[],a=[];return s=s.filter(d=>{d.id==null&&(d.id=this.idStrategy.getId());const l=d.id;if(e){const h=e(d);if(!h.valid)return a.push({id:l,valid:!1,reason:h.reason}),!1}if(this.tracked){if(d.properties.createdAt){if(!Yt(d.properties.createdAt))return a.push({id:d.id,valid:!1,reason:"createdAt is not a valid numeric timestamp"}),!1}else d.properties.createdAt=+new Date;if(d.properties.updatedAt){if(!Yt(d.properties.updatedAt))return a.push({id:d.id,valid:!1,reason:"updatedAt is not a valid numeric timestamp"}),!1}else d.properties.updatedAt=+new Date}return this.has(l)?(a.push({id:l,valid:!1,reason:`Feature already exists with this id: ${l}`}),!1):(this.store[l]=d,n.push(l),i&&i(d),a.push({id:l,valid:!0}),!0)}),this.spatialIndex.load(s),n.length>0&&this._onChange(n,"create",o),a}search(t,e){const i=this.spatialIndex.search(t).map(o=>this.store[o]);return this.clone(e?i.filter(e):i)}registerOnChange(t){this._onChange=(e,i,o)=>{t(e,i,o)}}getGeometryCopy(t){const e=this.store[t];if(!e)throw new Error(`No feature with this id (${t}), can not get geometry copy`);return this.clone(e.geometry)}getPropertiesCopy(t){const e=this.store[t];if(!e)throw new Error(`No feature with this id (${t}), can not get properties copy`);return this.clone(e.properties)}updateProperty(t,e){const i=[];t.forEach(({id:o,property:s,value:n})=>{const a=this.store[o];if(!a)throw new Error(`No feature with this (${o}), can not update geometry`);i.push(o),n===void 0?delete a.properties[s]:a.properties[s]=n,this.tracked&&(a.properties.updatedAt=+new Date)}),this._onChange&&this._onChange(i,"update",e)}updateGeometry(t,e){const i=[];t.forEach(({id:o,geometry:s})=>{i.push(o);const n=this.store[o];if(!n)throw new Error(`No feature with this (${o}), can not update geometry`);n.geometry=this.clone(s),this.spatialIndex.update(n),this.tracked&&(n.properties.updatedAt=+new Date)}),this._onChange&&this._onChange(i,"update",e)}create(t,e){const i=[];return t.forEach(({geometry:o,properties:s})=>{let n,a=M({},s);this.tracked&&(n=+new Date,s?(a.createdAt=typeof s.createdAt=="number"?s.createdAt:n,a.updatedAt=typeof s.updatedAt=="number"?s.updatedAt:n):a={createdAt:n,updatedAt:n});const d=this.getId(),l={id:d,type:"Feature",geometry:o,properties:a};this.store[d]=l,this.spatialIndex.insert(l),i.push(d)}),this._onChange&&this._onChange([...i],"create",e),i}delete(t,e){t.forEach(i=>{if(!this.store[i])throw new Error(`No feature with id ${i}, can not delete`);delete this.store[i],this.spatialIndex.remove(i)}),this._onChange&&this._onChange([...t],"delete",e)}copy(t){return this.clone(this.store[t])}copyAll(){return this.clone(Object.keys(this.store).map(t=>this.store[t]))}copyAllWhere(t){return this.clone(Object.keys(this.store).map(e=>this.store[e]).filter(e=>e.properties&&t(e.properties)))}clear(){this.store={},this.spatialIndex.clear()}size(){return Object.keys(this.store).length}}class Fi{constructor(t){this._modes=void 0,this._mode=void 0,this._adapter=void 0,this._enabled=!1,this._store=void 0,this._eventListeners=void 0,this._instanceSelectMode=void 0,this._adapter=t.adapter,this._mode=new Pi;const e=new Set,i=t.modes.reduce((h,c)=>{if(e.has(c.mode))throw new Error(`There is already a ${c.mode} mode provided`);return e.add(c.mode),h[c.mode]=c,h},{}),o=Object.keys(i);if(o.length===0)throw new Error("No modes provided");o.forEach(h=>{if(i[h].type===it.Select){if(this._instanceSelectMode)throw new Error("only one type of select mode can be provided");this._instanceSelectMode=h}}),this._modes=M({},i,{static:this._mode}),this._eventListeners={change:[],select:[],deselect:[],finish:[],ready:[]},this._store=new wi({tracked:!!t.tracked,idStrategy:t.idStrategy?t.idStrategy:void 0});const s=h=>{const c=[],u=this._store.copyAll().filter(p=>!h.includes(p.id)||(c.push(p),!1));return{changed:c,unchanged:u}},n=(h,c)=>{this._enabled&&this._eventListeners.finish.forEach(u=>{u(h,c)})},a=(h,c,u)=>{if(!this._enabled)return;this._eventListeners.change.forEach(y=>{y(h,c,u)});const{changed:p,unchanged:g}=s(h);c==="create"?this._adapter.render({created:p,deletedIds:[],unchanged:g,updated:[]},this.getModeStyles()):c==="update"?this._adapter.render({created:[],deletedIds:[],unchanged:g,updated:p},this.getModeStyles()):c==="delete"?this._adapter.render({created:[],deletedIds:h,unchanged:g,updated:[]},this.getModeStyles()):c==="styling"&&this._adapter.render({created:[],deletedIds:[],unchanged:g,updated:[]},this.getModeStyles())},d=h=>{if(!this._enabled)return;this._eventListeners.select.forEach(p=>{p(h)});const{changed:c,unchanged:u}=s([h]);this._adapter.render({created:[],deletedIds:[],unchanged:u,updated:c},this.getModeStyles())},l=h=>{if(!this._enabled)return;this._eventListeners.deselect.forEach(p=>{p()});const{changed:c,unchanged:u}=s([h]);c&&this._adapter.render({created:[],deletedIds:[],unchanged:u,updated:c},this.getModeStyles())};Object.keys(this._modes).forEach(h=>{this._modes[h].register({mode:h,store:this._store,setCursor:this._adapter.setCursor.bind(this._adapter),project:this._adapter.project.bind(this._adapter),unproject:this._adapter.unproject.bind(this._adapter),setDoubleClickToZoom:this._adapter.setDoubleClickToZoom.bind(this._adapter),onChange:a,onSelect:d,onDeselect:l,onFinish:n,coordinatePrecision:this._adapter.getCoordinatePrecision()})})}checkEnabled(){if(!this._enabled)throw new Error("Terra Draw is not enabled")}getModeStyles(){const t={};return Object.keys(this._modes).forEach(e=>{t[e]=i=>this._instanceSelectMode&&i.properties[L.SELECTED]?this._modes[this._instanceSelectMode].styleFeature.bind(this._modes[this._instanceSelectMode])(i):this._modes[e].styleFeature.bind(this._modes[e])(i)}),t}featuresAtLocation({lng:t,lat:e},i){const o=i&&i.pointerDistance!==void 0?i.pointerDistance:30,s=!i||i.ignoreSelectFeatures===void 0||i.ignoreSelectFeatures,n=!(!i||i.ignoreCoordinatePoints===void 0)&&i.ignoreCoordinatePoints,a=!(!i||i.ignoreCurrentlyDrawing===void 0)&&i.ignoreCurrentlyDrawing,d=!(!i||i.ignoreClosingPoints===void 0)&&i.ignoreClosingPoints,l=this._adapter.unproject.bind(this._adapter),h=this._adapter.project.bind(this._adapter),c=h(t,e),u=ye({unproject:l,point:c,pointerDistance:o});return this._store.search(u).filter(p=>{if(s&&(p.properties[L.MID_POINT]||p.properties[L.SELECTION_POINT])||n&&p.properties[Ot]||d&&p.properties[et]||a&&p.properties[N])return!1;if(p.geometry.type==="Point"){const g=p.geometry.coordinates,y=h(g[0],g[1]);return G(c,y){if(!this._store.has(i))throw new Error(`No feature with id ${i}, can not delete`);const o=this._store.copy(i);o.properties[L.SELECTED]&&this.deselectFeature(i),o.properties[dt]&&e.push(...o.properties[dt])}),this._store.delete([...t,...e],{origin:"api"})}selectFeature(t){this.getSelectMode().selectFeature(t)}deselectFeature(t){this.getSelectMode().deselectFeature(t)}getFeatureId(){return this._store.getId()}hasFeature(t){return this._store.has(t)}addFeatures(t){return this.checkEnabled(),t.length===0?[]:this._store.load(t,e=>{if(Xt(e)){const i=e.properties.mode,o=this._modes[i];if(!o)return{id:e.id,valid:!1,reason:`${i} mode is not in the list of instantiated modes`};const s=o.validateFeature.bind(o)(e);return{id:e.id,valid:s.valid,reason:s.reason?s.reason:s.valid?void 0:"Feature is invalid"}}return{id:e.id,valid:!1,reason:"Mode property does not exist"}},e=>{if(Xt(e)){const i=this._modes[e.properties.mode];i&&i.afterFeatureAdded&&i.afterFeatureAdded(e)}},{origin:"api"})}start(){this._enabled||(this._enabled=!0,this._adapter.register({onReady:()=>{this._eventListeners.ready.forEach(t=>{t()})},getState:()=>this._mode.state,onClick:t=>{this._mode.onClick(t)},onMouseMove:t=>{this._mode.onMouseMove(t)},onKeyDown:t=>{this._mode.onKeyDown(t)},onKeyUp:t=>{this._mode.onKeyUp(t)},onDragStart:(t,e)=>{this._mode.onDragStart(t,e)},onDrag:(t,e)=>{this._mode.onDrag(t,e)},onDragEnd:(t,e)=>{this._mode.onDragEnd(t,e)},onClear:()=>{this._mode.cleanUp(),this._store.clear()}}))}getFeaturesAtLngLat(t,e){const{lng:i,lat:o}=t;return this.featuresAtLocation({lng:i,lat:o},e)}getFeaturesAtPointerEvent(t,e){const i=this._adapter.getLngLatFromEvent.bind(this._adapter)(t);return i===null?[]:this.featuresAtLocation(i,e)}stop(){this._enabled&&(this._enabled=!1,this._adapter.unregister())}on(t,e){const i=this._eventListeners[t];i.includes(e)||i.push(e)}off(t,e){const i=this._eventListeners[t];i.includes(e)&&i.splice(i.indexOf(e),1)}}var re,bt,ae;function de(r,t=9){const e=Math.pow(10,t);return Math.round(r*e)/e}(bt=re||(re={})).Commit="commit",bt.Provisional="provisional",bt.Finish="finish",function(r){r.Drawing="drawing",r.Select="select",r.Static="static",r.Render="render"}(ae||(ae={}));class J{constructor({name:t,callback:e,unregister:i,register:o}){this.name=void 0,this.callback=void 0,this.registered=!1,this.register=void 0,this.unregister=void 0,this.name=t,this.register=()=>{this.registered||(this.registered=!0,o(e))},this.unregister=()=>{this.register&&(this.registered=!1,i(e))},this.callback=e}}var _i=class{constructor(r){this._minPixelDragDistance=void 0,this._minPixelDragDistanceDrawing=void 0,this._minPixelDragDistanceSelecting=void 0,this._lastDrawEvent=void 0,this._coordinatePrecision=void 0,this._heldKeys=new Set,this._listeners=[],this._dragState="not-dragging",this._currentModeCallbacks=void 0,this._minPixelDragDistance=typeof r.minPixelDragDistance=="number"?r.minPixelDragDistance:1,this._minPixelDragDistanceSelecting=typeof r.minPixelDragDistanceSelecting=="number"?r.minPixelDragDistanceSelecting:1,this._minPixelDragDistanceDrawing=typeof r.minPixelDragDistanceDrawing=="number"?r.minPixelDragDistanceDrawing:8,this._coordinatePrecision=typeof r.coordinatePrecision=="number"?r.coordinatePrecision:9}getButton(r){return r.button===-1?"neither":r.button===0?"left":r.button===1?"middle":r.button===2?"right":"neither"}getMapElementXYPosition(r){const t=this.getMapEventElement(),{left:e,top:i}=t.getBoundingClientRect();return{containerX:r.clientX-e,containerY:r.clientY-i}}getDrawEventFromEvent(r){const t=this.getLngLatFromEvent(r);if(!t)return null;const{lng:e,lat:i}=t,{containerX:o,containerY:s}=this.getMapElementXYPosition(r),n=this.getButton(r),a=Array.from(this._heldKeys);return{lng:de(e,this._coordinatePrecision),lat:de(i,this._coordinatePrecision),containerX:o,containerY:s,button:n,heldKeys:a}}register(r){this._currentModeCallbacks=r,this._listeners=this.getAdapterListeners(),this._listeners.forEach(t=>{t.register()})}getCoordinatePrecision(){return this._coordinatePrecision}getAdapterListeners(){return[new J({name:"pointerdown",callback:r=>{if(!this._currentModeCallbacks||!r.isPrimary)return;const t=this.getDrawEventFromEvent(r);t&&(this._dragState="pre-dragging",this._lastDrawEvent=t)},register:r=>{this.getMapEventElement().addEventListener("pointerdown",r)},unregister:r=>{this.getMapEventElement().removeEventListener("pointerdown",r)}}),new J({name:"pointermove",callback:r=>{if(!this._currentModeCallbacks||!r.isPrimary)return;r.preventDefault();const t=this.getDrawEventFromEvent(r);if(t)if(this._dragState==="not-dragging")this._currentModeCallbacks.onMouseMove(t),this._lastDrawEvent=t;else if(this._dragState==="pre-dragging"){if(!this._lastDrawEvent)return;const e={x:this._lastDrawEvent.containerX,y:this._lastDrawEvent.containerY},i={x:t.containerX,y:t.containerY},o=this._currentModeCallbacks.getState(),s=((a,d)=>{const{x:l,y:h}=a,{x:c,y:u}=d,p=c-l,g=u-h;return Math.sqrt(g*g+p*p)})(e,i);let n=!1;if(n=o==="drawing"?s{this.setDraggability.bind(this)(a)})}else this._dragState==="dragging"&&this._currentModeCallbacks.onDrag(t,e=>{this.setDraggability.bind(this)(e)})},register:r=>{this.getMapEventElement().addEventListener("pointermove",r)},unregister:r=>{this.getMapEventElement().removeEventListener("pointermove",r)}}),new J({name:"contextmenu",callback:r=>{this._currentModeCallbacks&&r.preventDefault()},register:r=>{this.getMapEventElement().addEventListener("contextmenu",r)},unregister:r=>{this.getMapEventElement().removeEventListener("contextmenu",r)}}),new J({name:"pointerup",callback:r=>{if(!this._currentModeCallbacks||r.target!==this.getMapEventElement()||!r.isPrimary)return;const t=this.getDrawEventFromEvent(r);t&&(this._dragState==="dragging"?this._currentModeCallbacks.onDragEnd(t,e=>{this.setDraggability.bind(this)(e)}):this._dragState!=="not-dragging"&&this._dragState!=="pre-dragging"||this._currentModeCallbacks.onClick(t),this._dragState="not-dragging",this.setDraggability(!0))},register:r=>{this.getMapEventElement().addEventListener("pointerup",r)},unregister:r=>{this.getMapEventElement().removeEventListener("pointerup",r)}}),new J({name:"keyup",callback:r=>{this._currentModeCallbacks&&(this._heldKeys.delete(r.key),this._currentModeCallbacks.onKeyUp({key:r.key,heldKeys:Array.from(this._heldKeys),preventDefault:()=>r.preventDefault()}))},register:r=>{this.getMapEventElement().addEventListener("keyup",r)},unregister:r=>{this.getMapEventElement().removeEventListener("keyup",r)}}),new J({name:"keydown",callback:r=>{this._currentModeCallbacks&&(this._heldKeys.add(r.key),this._currentModeCallbacks.onKeyDown({key:r.key,heldKeys:Array.from(this._heldKeys),preventDefault:()=>r.preventDefault()}))},register:r=>{this.getMapEventElement().addEventListener("keydown",r)},unregister:r=>{this.getMapEventElement().removeEventListener("keydown",r)}})]}unregister(){this._listeners.forEach(r=>{r.unregister()}),this.clear()}};class Di extends _i{constructor(t){if(super(t),this._cursor=void 0,this._cursorStyleSheet=void 0,this._lib=void 0,this._map=void 0,this._overlay=void 0,this._clickEventListener=void 0,this._mouseMoveEventListener=void 0,this.renderedFeatureIds=new Set,this._lib=t.lib,this._map=t.map,!this._map.getDiv().id)throw new Error("Google Map container div requires and id to be set");this._coordinatePrecision=typeof t.coordinatePrecision=="number"?t.coordinatePrecision:9}get _layers(){var t;return((t=this.renderedFeatureIds)==null?void 0:t.size)>0}circlePath(t,e,i){const o=2*i;return`M ${t} ${e} m -${i}, 0 a ${i},${i} 0 1,0 ${o},0 a ${i},${i} 0 1,0 -${o},0`}register(t){super.register(t),this._overlay=new this._lib.OverlayView,this._overlay.draw=function(){},this._overlay.onAdd=()=>{var e;(e=this._currentModeCallbacks)!=null&&e.onReady&&this._currentModeCallbacks.onReady()},this._overlay.setMap(this._map),this._clickEventListener=this._map.data.addListener("click",e=>{const i=this._listeners.find(({name:o})=>o==="click");i&&i.callback(e)}),this._mouseMoveEventListener=this._map.data.addListener("mousemove",e=>{const i=this._listeners.find(({name:o})=>o==="mousemove");i&&i.callback(e)})}unregister(){var t,e,i;super.unregister(),(t=this._clickEventListener)==null||t.remove(),(e=this._mouseMoveEventListener)==null||e.remove(),(i=this._overlay)==null||i.setMap(null),this._overlay=void 0}getLngLatFromEvent(t){if(!this._overlay)throw new Error("cannot get overlay");const e=this._map.getBounds();if(!e)return null;const i=e.getNorthEast(),o=e.getSouthWest(),s=new this._lib.LatLngBounds(o,i),n=this._map.getDiv(),a=t.clientX-n.getBoundingClientRect().left,d=t.clientY-n.getBoundingClientRect().top,l=new this._lib.Point(a,d),h=this._overlay.getProjection();if(!h)return null;const c=h.fromContainerPixelToLatLng(l);return c&&s.contains(c)?{lng:c.lng(),lat:c.lat()}:null}getMapEventElement(){return this._map.getDiv().querySelector('div[style*="z-index: 3;"]')}project(t,e){if(!this._overlay)throw new Error("cannot get overlay");if(this._map.getBounds()===void 0)throw new Error("cannot get bounds");const i=this._overlay.getProjection();if(i===void 0)throw new Error("cannot get projection");const o=i.fromLatLngToContainerPixel(new this._lib.LatLng(e,t));if(o===null)throw new Error("cannot project coordinates");return{x:o.x,y:o.y}}unproject(t,e){if(!this._overlay)throw new Error("cannot get overlay");const i=this._overlay.getProjection();if(i===void 0)throw new Error("cannot get projection");const o=i.fromContainerPixelToLatLng(new this._lib.Point(t,e));if(o===null)throw new Error("cannot unproject coordinates");return{lng:o.lng(),lat:o.lat()}}setCursor(t){if(t!==this._cursor){if(this._cursorStyleSheet&&(this._cursorStyleSheet.remove(),this._cursorStyleSheet=void 0),t!=="unset"){const e=this._map.getDiv(),i=document.querySelector(`#${e.id} .gm-style > div`);if(i){i.classList.add("terra-draw-google-maps");const o=document.createElement("style");o.innerHTML=`.terra-draw-google-maps { cursor: ${t} !important; }`,document.getElementsByTagName("head")[0].appendChild(o),this._cursorStyleSheet=o}}this._cursor=t}}setDoubleClickToZoom(t){this._map.setOptions(t?{disableDoubleClickZoom:!1}:{disableDoubleClickZoom:!0})}setDraggability(t){this._map.setOptions({draggable:t})}render(t,e){this._layers&&(t.deletedIds.forEach(o=>{const s=this._map.data.getFeatureById(o);s&&(this._map.data.remove(s),this.renderedFeatureIds.delete(o))}),t.updated.forEach(o=>{if(!o||!o.id)throw new Error("Feature is not valid");const s=this._map.data.getFeatureById(o.id);if(!s)throw new Error("Feature could not be found by Google Maps API");switch(s.forEachProperty((n,a)=>{s.setProperty(a,void 0)}),Object.keys(o.properties).forEach(n=>{s.setProperty(n,o.properties[n])}),o.geometry.type){case"Point":{const n=o.geometry.coordinates;s.setGeometry(new this._lib.Data.Point(new this._lib.LatLng(n[1],n[0])))}break;case"LineString":{const n=o.geometry.coordinates,a=[];for(let d=0;d{this.renderedFeatureIds.add(o.id),this._map.data.addGeoJson(o)})),t.created.forEach(o=>{this.renderedFeatureIds.add(o.id)});const i={type:"FeatureCollection",features:[...t.created]};this._map.data.addGeoJson(i),this._map.data.setStyle(o=>{const s=o.getProperty("mode"),n=o.getGeometry();if(!n)throw new Error("Google Maps geometry not found");const a=n.getType(),d={};o.forEachProperty((h,c)=>{d[c]=h});const l=e[s]({type:"Feature",geometry:{type:a,coordinates:[]},properties:d});switch(a){case"Point":return{clickable:!1,icon:{path:this.circlePath(0,0,l.pointWidth),fillColor:l.pointColor,fillOpacity:1,strokeColor:l.pointOutlineColor,strokeWeight:l.pointOutlineWidth,rotation:0,scale:1}};case"LineString":return{strokeColor:l.lineStringColor,strokeWeight:l.lineStringWidth};case"Polygon":return{strokeColor:l.polygonOutlineColor,strokeWeight:l.polygonOutlineWidth,fillOpacity:l.polygonFillOpacity,fillColor:l.polygonFillColor}}throw Error("Unknown feature type")})}clearLayers(){this._layers&&(this._map.data.forEach(t=>{const e=t.getId();this.renderedFeatureIds.has(e)&&this._map.data.remove(t)}),this.renderedFeatureIds=new Set)}clear(){this._currentModeCallbacks&&(this._currentModeCallbacks.onClear(),this.clearLayers())}getCoordinatePrecision(){return super.getCoordinatePrecision()}}/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */const le=["#E74C3C","#FF0066","#9B59B6","#673AB7","#3F51B5","#3498DB","#03A9F4","#00BCD4","#009688","#27AE60","#8BC34A","#CDDC39","#F1C40F","#FFC107","#F39C12","#FF5722","#795548"],Z=()=>le[Math.floor(Math.random()*le.length)];function he(r){return r.map(t=>{const e=JSON.parse(JSON.stringify(t));return e.properties.mode==="rectangle"?(e.geometry.type="Polygon",e.properties.mode="polygon"):e.properties.mode==="circle"&&(e.geometry.type="Polygon",e.properties.mode="circle"),e})}function ki(){if(!document.getElementById("mode-ui"))return;const t={"select-mode":"select","point-mode":"point","linestring-mode":"linestring","polygon-mode":"polygon","rectangle-mode":"rectangle","circle-mode":"circle","freehand-mode":"freehand","clear-mode":"static"};for(const e in t){const i=document.getElementById(e);i&&(i.onclick=()=>{Nt(e);const o=t[e];E&&(o==="static"?(E.clear(),E.setMode("static")):o&&E.setMode(o))})}}function Nt(r){const t=document.querySelectorAll(".mode-button"),e=document.getElementById("resize-button"),i=e==null?void 0:e.classList.contains("active");t.forEach(s=>{s.id!=="resize-button"&&s.classList.remove("active")});const o=document.getElementById(r);o&&o.classList.add("active"),i&&(e==null||e.classList.add("active"))}function Oi(){Nt("point-mode")}let yt,E,Bi="static",$=[],ft=[],A=null,nt=!1,j=!1,wt;const ji=new H({apiKey:"AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8",version:"weekly",libraries:["maps","drawing","marker"]});ji.load().then(async()=>{try{const{Map:r}=await google.maps.importLibrary("maps"),{LatLngBounds:t}=await google.maps.importLibrary("core"),{Data:e}=await google.maps.importLibrary("maps"),i={center:{lat:48.862,lng:2.342},zoom:12,mapId:"c306b3c6dd3ed8d9",mapTypeId:"roadmap",zoomControl:!1,tilt:45,mapTypeControl:!0,clickableIcons:!1,streetViewControl:!1,fullscreenControl:!1},o=document.getElementById("map");yt=new r(o,i),yt.addListener("click",()=>{E&&console.log("Current draw mode on map click:",E.getMode())}),yt.addListener("projection_changed",()=>{E=new Fi({adapter:new Di({map:yt,lib:google.maps,coordinatePrecision:9}),modes:[new Ci({flags:{polygon:{feature:{draggable:!0,rotateable:!0,coordinates:{midpoints:!0,draggable:!0,deletable:!0}}},linestring:{feature:{draggable:!0,rotateable:!0,coordinates:{midpoints:!0,draggable:!0,deletable:!0}}},point:{feature:{draggable:!0,rotateable:!0}},rectangle:{feature:{draggable:!0,rotateable:!0,coordinates:{midpoints:!0,draggable:!0,deletable:!0}}},circle:{feature:{draggable:!0,rotateable:!0,coordinates:{midpoints:!0,draggable:!0,deletable:!0}}},freehand:{feature:{draggable:!0,rotateable:!0,coordinates:{midpoints:!0,draggable:!0,deletable:!0}}}}}),new ti({editable:!0,styles:{pointColor:Z()}}),new $e({editable:!0,styles:{lineStringColor:Z()}}),new si({editable:!0,styles:(()=>{const a=Z();return{fillColor:a,outlineColor:a}})()}),new ai({styles:(()=>{const a=Z();return{fillColor:a,outlineColor:a}})()}),new We({styles:(()=>{const a=Z();return{fillColor:a,outlineColor:a}})()}),new Te({styles:(()=>{const a=Z();return{fillColor:a,outlineColor:a}})()})]}),E.start(),E.on("ready",()=>{console.log("TerraDraw is ready!"),Oi(),ki(),E.setMode("point"),Bi="point",Nt("point-mode"),E.on("select",g=>{A&&A!==g&&E.deselectFeature(A),A=g}),E.on("deselect",()=>{A=null}),$.push(he(E.getSnapshot())),E.on("change",(g,y)=>{nt||(wt&&clearTimeout(wt),wt=window.setTimeout(()=>{const v=E.getSnapshot(),C=he(v).filter(f=>!f.properties.midPoint&&!f.properties.selectionPoint);$.push(C),ft=[]},200))});const a=document.getElementById("export-button");a&&(a.onclick=()=>{const y={type:"FeatureCollection",features:E.getSnapshot()},v=JSON.stringify(y,null,2),m=new Blob([v],{type:"text/plain"}),C=URL.createObjectURL(m),f=document.createElement("a");f.href=C,f.download="drawing.geojson",f.click(),URL.revokeObjectURL(C)});const d=document.getElementById("upload-button"),l=document.getElementById("upload-input");d&&l&&(d.onclick=()=>{l.click()},l.onchange=g=>{var v;const y=(v=g.target.files)==null?void 0:v[0];if(y){const m=new FileReader;m.onload=C=>{var f;try{const P=JSON.parse((f=C.target)==null?void 0:f.result);P.type==="FeatureCollection"?E.addFeatures(P.features):alert("Invalid GeoJSON file: must be a FeatureCollection.")}catch{alert("Error parsing GeoJSON file.")}},m.readAsText(y)}});const h=document.getElementById("resize-button");h&&(h.onclick=()=>{j=!j,h.classList.toggle("active",j);const g={polygon:{feature:{draggable:!0,coordinates:{resizable:j?"center":void 0,draggable:!j}}},linestring:{feature:{draggable:!0,coordinates:{resizable:j?"center":void 0,draggable:!j}}},rectangle:{feature:{draggable:!0,coordinates:{resizable:j?"center":void 0,draggable:!j}}},circle:{feature:{draggable:!0,coordinates:{resizable:j?"center":void 0,draggable:!j}}},freehand:{feature:{draggable:!0,coordinates:{resizable:j?"center":void 0,draggable:!j}}}};console.log("Updating flags:",g),E.updateModeOptions("select",{flags:g})});const c=document.getElementById("delete-selected-button");c&&(c.onclick=()=>{if(A)E.removeFeatures([A]);else{const g=E.getSnapshot();if(g.length>0){const y=g[g.length-1];y.id&&E.removeFeatures([y.id])}}});const u=document.getElementById("undo-button");u&&(u.onclick=()=>{if($.length>1){ft.push($.pop());const g=$[$.length-1];console.log("Restoring snapshot (undo):",g),nt=!0,E.clear(),E.addFeatures(g),setTimeout(()=>{nt=!1},0)}});const p=document.getElementById("redo-button");p&&(p.onclick=()=>{if(ft.length>0){const g=ft.pop();console.log("Restoring snapshot (redo):",g),$.push(g),nt=!0,E.clear(),E.addFeatures(g),setTimeout(()=>{nt=!1},0)}})});function s(a,d){const l=JSON.parse(JSON.stringify(a)),h=l.geometry.coordinates,c=n(h),u=h.map(p=>p.map(g=>{const y=g[0]-c[0],v=g[1]-c[1],m=y*Math.cos(d*Math.PI/180)-v*Math.sin(d*Math.PI/180),C=y*Math.sin(d*Math.PI/180)+v*Math.cos(d*Math.PI/180);return[m+c[0],C+c[1]]}));return l.geometry.coordinates=u,l}function n(a){let d=0,l=0,h=0;return a.forEach(c=>{c.forEach(u=>{d+=u[0],l+=u[1],h++})}),[d/h,l/h]}document.addEventListener("keydown",a=>{if(a.key==="r"&&A){const l=E.getSnapshot().find(h=>h.id===A);if(l){const h=s(l,15);E.addFeatures([h])}}})})}catch(r){console.error("Error loading Google Maps API:",r)}}).catch(r=>{console.error("Error loading Google Maps API:",r)}); diff --git a/dist/samples/map-drawing-terradraw/dist/assets/index-P6yNqPBM.css b/dist/samples/map-drawing-terradraw/dist/assets/index-P6yNqPBM.css new file mode 100644 index 00000000..ebd0c783 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/dist/assets/index-P6yNqPBM.css @@ -0,0 +1,5 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */html,body{height:100%;margin:0;padding:0;font-family:Arial,sans-serif}#map{height:100%;width:100%}#mode-ui{position:absolute;top:10px;right:10px;background:#fff;padding:10px;border-radius:5px;box-shadow:0 0 10px #0003;z-index:1000;display:flex;flex-direction:column}#mode-ui button{margin:5px 0;cursor:pointer}.mode-button{width:30px;height:30px;border:1px solid #ccc;background-color:#fff;padding:2px;box-sizing:border-box}.mode-button img{width:100%;height:100%;display:block;-webkit-user-select:none;user-select:none}.mode-button.active{background-color:#e0e0e0}#select-mode,#clear-mode,#delete-selected-button,#undo-button,#redo-button,#export-button,#upload-button,#resize-button{background-color:#000}#select-mode img,#clear-mode img,#delete-selected-button img,#undo-button img,#redo-button img,#export-button img,#upload-button img,#resize-button img{filter:brightness(0) invert(1)}#select-mode.active,#clear-mode:active,#delete-selected-button:active,#undo-button:active,#redo-button:active,#export-button:active,#upload-button:active,#resize-button.active{background-color:#a9a9a9} diff --git a/dist/samples/map-drawing-terradraw/dist/index.html b/dist/samples/map-drawing-terradraw/dist/index.html new file mode 100644 index 00000000..c0cb0acf --- /dev/null +++ b/dist/samples/map-drawing-terradraw/dist/index.html @@ -0,0 +1,41 @@ + + + + + + Terra Draw with Google Maps API Sample + + + + + + +
+ + +
+ + + + + + + + + + + + + + + +
+ + + + + diff --git a/dist/samples/map-drawing-terradraw/docs/index.html b/dist/samples/map-drawing-terradraw/docs/index.html new file mode 100644 index 00000000..48c18039 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/docs/index.html @@ -0,0 +1,41 @@ + + + + + + Terra Draw with Google Maps API Sample + + + + + +
+ + +
+ + + + + + + + + + + + + + + +
+ + + + + + diff --git a/dist/samples/map-drawing-terradraw/docs/index.js b/dist/samples/map-drawing-terradraw/docs/index.js new file mode 100644 index 00000000..6cd1cf33 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/docs/index.js @@ -0,0 +1,451 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +// [START maps_map_drawing_terradraw] +// [START maps_map_drawing_terradraw_libraries] +import { Loader } from '@googlemaps/js-api-loader'; +import { TerraDraw, TerraDrawSelectMode, TerraDrawPointMode, TerraDrawLineStringMode, TerraDrawPolygonMode, TerraDrawRectangleMode, TerraDrawCircleMode, TerraDrawFreehandMode } from 'terra-draw'; +import { TerraDrawGoogleMapsAdapter } from 'terra-draw-google-maps-adapter'; +// [END maps_map_drawing_terradraw_libraries] +const colorPalette = [ + "#E74C3C", + "#FF0066", + "#9B59B6", + "#673AB7", + "#3F51B5", + "#3498DB", + "#03A9F4", + "#00BCD4", + "#009688", + "#27AE60", + "#8BC34A", + "#CDDC39", + "#F1C40F", + "#FFC107", + "#F39C12", + "#FF5722", + "#795548" +]; +const getRandomColor = () => colorPalette[Math.floor(Math.random() * colorPalette.length)]; +function processSnapshotForUndo(snapshot) { + // console.log("Processing snapshot for undo:", snapshot); + return snapshot.map(feature => { + const newFeature = JSON.parse(JSON.stringify(feature)); + if (newFeature.properties.mode === 'rectangle') { + // console.log("Processing rectangle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + newFeature.properties.mode = 'polygon'; + } + else if (newFeature.properties.mode === 'circle') { + // console.log("Processing circle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + // The radius is already in properties, so we just need to ensure the mode is correct for re-creation + newFeature.properties.mode = 'circle'; + } + return newFeature; + }); +} +function setupModeButtons() { + const modeUI = document.getElementById('mode-ui'); + if (!modeUI) { + return; + } + const modeButtons = { + 'select-mode': 'select', + 'point-mode': 'point', + 'linestring-mode': 'linestring', + 'polygon-mode': 'polygon', + 'rectangle-mode': 'rectangle', + 'circle-mode': 'circle', + 'freehand-mode': 'freehand', + 'clear-mode': 'static' + }; + for (const buttonId in modeButtons) { + const button = document.getElementById(buttonId); + if (button) { + button.onclick = () => { + setActiveButton(buttonId); + const modeName = modeButtons[buttonId]; + if (!draw) { + return; + } + if (modeName === 'static') { + draw.clear(); + draw.setMode('static'); + } + else if (modeName) { + draw.setMode(modeName); + } + }; + } + } +} +function setActiveButton(buttonId) { + const buttons = document.querySelectorAll('.mode-button'); + const resizeButton = document.getElementById('resize-button'); + const isResizeActive = resizeButton?.classList.contains('active'); + buttons.forEach(button => { + if (button.id !== 'resize-button') { + button.classList.remove('active'); + } + }); + const activeButton = document.getElementById(buttonId); + if (activeButton) { + activeButton.classList.add('active'); + } + if (isResizeActive) { + resizeButton?.classList.add('active'); + } +} +function initUI() { + setActiveButton('point-mode'); +} +let map; +let draw; +let currentMode = 'static'; +let history = []; +let redoHistory = []; +let selectedFeatureId = null; +let isRestoring = false; +let resizingEnabled = false; +let debounceTimeout; +const loader = new Loader({ + apiKey: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", + version: "weekly", + libraries: ["maps", "drawing", "marker"] +}); +loader.load().then(async () => { + try { + const { Map } = await google.maps.importLibrary("maps"); + const { LatLngBounds } = await google.maps.importLibrary("core"); + const { Data } = await google.maps.importLibrary("maps"); + const mapOptions = { + center: { lat: 48.862, lng: 2.342 }, + zoom: 12, + mapId: 'c306b3c6dd3ed8d9', // raster '6a17c323f461e521', + mapTypeId: 'roadmap', + zoomControl: false, + tilt: 45, + mapTypeControl: true, + clickableIcons: false, + streetViewControl: false, + fullscreenControl: false, + }; + const mapDiv = document.getElementById("map"); + map = new Map(mapDiv, mapOptions); + map.addListener("click", () => { + if (draw) { + console.log("Current draw mode on map click:", draw.getMode()); + } + }); + map.addListener("projection_changed", () => { + // [START maps_drawing_terradraw_modes] + draw = new TerraDraw({ + adapter: new TerraDrawGoogleMapsAdapter({ map, lib: google.maps, coordinatePrecision: 9 }), + modes: [ + new TerraDrawSelectMode({ + flags: { + polygon: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + linestring: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + point: { + feature: { + draggable: true, + rotateable: true, + }, + }, + rectangle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + circle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + freehand: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + }, + }), + new TerraDrawPointMode({ + editable: true, + styles: { pointColor: getRandomColor() }, + }), + new TerraDrawLineStringMode({ + editable: true, + styles: { lineStringColor: getRandomColor() }, + }), + new TerraDrawPolygonMode({ + editable: true, + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawRectangleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawCircleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawFreehandMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + ], + }); + draw.start(); + draw.on('ready', () => { + console.log("TerraDraw is ready!"); + initUI(); + setupModeButtons(); + draw.setMode('point'); + currentMode = 'point'; + setActiveButton('point-mode'); + draw.on("select", (id) => { + // console.log(`Feature selected: ${id}`); + if (selectedFeatureId && selectedFeatureId !== id) { + draw.deselectFeature(selectedFeatureId); + } + selectedFeatureId = id; + }); + draw.on("deselect", () => { + // console.log("Feature deselected"); + selectedFeatureId = null; + }); + history.push(processSnapshotForUndo(draw.getSnapshot())); // Push initial empty state + draw.on("change", (ids, type) => { + if (isRestoring) { + return; + } + if (debounceTimeout) { + clearTimeout(debounceTimeout); + } + debounceTimeout = window.setTimeout(() => { + const snapshot = draw.getSnapshot(); + const processedSnapshot = processSnapshotForUndo(snapshot); + const filteredSnapshot = processedSnapshot.filter((f) => !f.properties.midPoint && !f.properties.selectionPoint); + history.push(filteredSnapshot); + redoHistory = []; + }, 200); + }); + // [END maps_drawing_terradraw_modes] + const exportButton = document.getElementById('export-button'); + if (exportButton) { + exportButton.onclick = () => { + const features = draw.getSnapshot(); + const geojson = { + type: "FeatureCollection", + features: features, + }; + const data = JSON.stringify(geojson, null, 2); + const blob = new Blob([data], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "drawing.geojson"; + link.click(); + URL.revokeObjectURL(url); + }; + } + const uploadButton = document.getElementById('upload-button'); + const uploadInput = document.getElementById('upload-input'); + if (uploadButton && uploadInput) { + uploadButton.onclick = () => { + uploadInput.click(); + }; + uploadInput.onchange = (event) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const geojson = JSON.parse(e.target?.result); + if (geojson.type === "FeatureCollection") { + draw.addFeatures(geojson.features); + } + else { + alert("Invalid GeoJSON file: must be a FeatureCollection."); + } + } + catch (error) { + alert("Error parsing GeoJSON file."); + } + }; + reader.readAsText(file); + } + }; + } + const resizeButton = document.getElementById('resize-button'); + if (resizeButton) { + resizeButton.onclick = () => { + resizingEnabled = !resizingEnabled; + resizeButton.classList.toggle('active', resizingEnabled); + const flags = { + polygon: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + linestring: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + rectangle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + circle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + freehand: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + }; + console.log("Updating flags:", flags); + draw.updateModeOptions('select', { flags }); + }; + } + const deleteSelectedButton = document.getElementById('delete-selected-button'); + if (deleteSelectedButton) { + deleteSelectedButton.onclick = () => { + if (selectedFeatureId) { + draw.removeFeatures([selectedFeatureId]); + } + else { + const features = draw.getSnapshot(); + if (features.length > 0) { + const lastFeature = features[features.length - 1]; + if (lastFeature.id) { + draw.removeFeatures([lastFeature.id]); + } + } + } + }; + } + const undoButton = document.getElementById('undo-button'); + if (undoButton) { + undoButton.onclick = () => { + if (history.length > 1) { + redoHistory.push(history.pop()); + const snapshotToRestore = history[history.length - 1]; + console.log("Restoring snapshot (undo):", snapshotToRestore); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshotToRestore); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + const redoButton = document.getElementById('redo-button'); + if (redoButton) { + redoButton.onclick = () => { + if (redoHistory.length > 0) { + const snapshot = redoHistory.pop(); + console.log("Restoring snapshot (redo):", snapshot); + history.push(snapshot); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshot); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + }); + function rotateFeature(feature, angle) { + const newFeature = JSON.parse(JSON.stringify(feature)); + const coordinates = newFeature.geometry.coordinates; + const center = getCenter(coordinates); + const rotatedCoordinates = coordinates.map(ring => { + return ring.map(point => { + const x = point[0] - center[0]; + const y = point[1] - center[1]; + const newX = x * Math.cos(angle * Math.PI / 180) - y * Math.sin(angle * Math.PI / 180); + const newY = x * Math.sin(angle * Math.PI / 180) + y * Math.cos(angle * Math.PI / 180); + return [newX + center[0], newY + center[1]]; + }); + }); + newFeature.geometry.coordinates = rotatedCoordinates; + return newFeature; + } + function getCenter(coordinates) { + let x = 0; + let y = 0; + let count = 0; + coordinates.forEach(ring => { + ring.forEach(point => { + x += point[0]; + y += point[1]; + count++; + }); + }); + return [x / count, y / count]; + } + document.addEventListener('keydown', (event) => { + if (event.key === 'r' && selectedFeatureId) { + const features = draw.getSnapshot(); + const selectedFeature = features.find(f => f.id === selectedFeatureId); + if (selectedFeature) { + const newFeature = rotateFeature(selectedFeature, 15); + draw.addFeatures([newFeature]); + } + } + }); + }); + } + catch (e) { + console.error("Error loading Google Maps API:", e); + } +}).catch(e => { + console.error("Error loading Google Maps API:", e); +}); +// [END maps_map_drawing_terradraw] diff --git a/dist/samples/map-drawing-terradraw/docs/index.ts b/dist/samples/map-drawing-terradraw/docs/index.ts new file mode 100644 index 00000000..51c00895 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/docs/index.ts @@ -0,0 +1,509 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START maps_map_drawing_terradraw] +// [START maps_map_drawing_terradraw_libraries] +import { Loader } from '@googlemaps/js-api-loader'; + +import { + TerraDraw, + TerraDrawSelectMode, + TerraDrawPointMode, + TerraDrawLineStringMode, + TerraDrawPolygonMode, + TerraDrawRectangleMode, + TerraDrawCircleMode, + TerraDrawFreehandMode +} from 'terra-draw'; +import { TerraDrawGoogleMapsAdapter } from 'terra-draw-google-maps-adapter'; + +// [END maps_map_drawing_terradraw_libraries] + +const colorPalette = [ + "#E74C3C", + "#FF0066", + "#9B59B6", + "#673AB7", + "#3F51B5", + "#3498DB", + "#03A9F4", + "#00BCD4", + "#009688", + "#27AE60", + "#8BC34A", + "#CDDC39", + "#F1C40F", + "#FFC107", + "#F39C12", + "#FF5722", + "#795548" +]; + +const getRandomColor = () => colorPalette[Math.floor(Math.random() * colorPalette.length)] as `#${string}`; + +function processSnapshotForUndo(snapshot: any[]): any[] { + // console.log("Processing snapshot for undo:", snapshot); + return snapshot.map(feature => { + const newFeature = JSON.parse(JSON.stringify(feature)); + + if (newFeature.properties.mode === 'rectangle') { + // console.log("Processing rectangle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + newFeature.properties.mode = 'polygon'; + } else if (newFeature.properties.mode === 'circle') { + // console.log("Processing circle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + // The radius is already in properties, so we just need to ensure the mode is correct for re-creation + newFeature.properties.mode = 'circle'; + } + return newFeature; + }); +} + +function setupModeButtons(): void { + const modeUI = document.getElementById('mode-ui'); + if (!modeUI) { + return; + } + + const modeButtons: { [key: string]: string } = { + 'select-mode': 'select', + 'point-mode': 'point', + 'linestring-mode': 'linestring', + 'polygon-mode': 'polygon', + 'rectangle-mode': 'rectangle', + 'circle-mode': 'circle', + 'freehand-mode': 'freehand', + 'clear-mode': 'static' + }; + + for (const buttonId in modeButtons) { + const button = document.getElementById(buttonId); + if (button) { + button.onclick = () => { + setActiveButton(buttonId); + const modeName = modeButtons[buttonId]; + + if (!draw) { + return; + } + if (modeName === 'static') { + draw.clear(); + draw.setMode('static'); + } else if (modeName) { + draw.setMode(modeName); + } + }; + } + } +} + +function setActiveButton(buttonId: string): void { + const buttons = document.querySelectorAll('.mode-button'); + const resizeButton = document.getElementById('resize-button'); + const isResizeActive = resizeButton?.classList.contains('active'); + + buttons.forEach(button => { + if (button.id !== 'resize-button') { + button.classList.remove('active'); + } + }); + + const activeButton = document.getElementById(buttonId); + if (activeButton) { + activeButton.classList.add('active'); + } + + if (isResizeActive) { + resizeButton?.classList.add('active'); + } +} + +function initUI(): void { + setActiveButton('point-mode'); +} + +let map: google.maps.Map; +let draw: TerraDraw; +let currentMode: string = 'static'; +let history: any[] = []; +let redoHistory: any[] = []; +let selectedFeatureId: string | null = null; +let isRestoring = false; +let resizingEnabled = false; +let debounceTimeout: number | undefined; + +const loader = new Loader({ + apiKey: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", + version: "weekly", + libraries: ["maps", "drawing", "marker"] +}); + +loader.load().then(async () => { + try { + const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary; + const { LatLngBounds } = await google.maps.importLibrary("core") as google.maps.CoreLibrary; + const { Data } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary; + + const mapOptions: google.maps.MapOptions = { + center: { lat: 48.862, lng: 2.342 }, + zoom: 12, + mapId:'c306b3c6dd3ed8d9', // raster '6a17c323f461e521', + mapTypeId: 'roadmap', + zoomControl:false, + tilt: 45, + mapTypeControl: true, + clickableIcons:false, + streetViewControl:false, + fullscreenControl:false, + }; + + const mapDiv = document.getElementById("map") as HTMLElement; + map = new Map(mapDiv, mapOptions); + + map.addListener("click", () => { + if (draw) { + console.log("Current draw mode on map click:", draw.getMode()); + } + }); + + map.addListener("projection_changed", () => { + + // [START maps_drawing_terradraw_modes] + + draw = new TerraDraw({ + adapter: new TerraDrawGoogleMapsAdapter({ map, lib: google.maps, coordinatePrecision: 9 }), + modes: [ + new TerraDrawSelectMode({ + flags: { + polygon: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + linestring: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + point: { + feature: { + draggable: true, + rotateable: true, + }, + }, + rectangle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + circle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + freehand: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + }, + }), + + new TerraDrawPointMode({ + editable: true, + styles: { pointColor: getRandomColor() }, + }), + new TerraDrawLineStringMode({ + editable: true, + styles: { lineStringColor: getRandomColor() }, + }), + new TerraDrawPolygonMode({ + editable: true, + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawRectangleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawCircleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawFreehandMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + ], + }); + + draw.start(); + + + draw.on('ready', () => { + console.log("TerraDraw is ready!"); + initUI(); + setupModeButtons(); + draw.setMode('point'); + currentMode = 'point'; + setActiveButton('point-mode'); + + draw.on("select", (id) => { + // console.log(`Feature selected: ${id}`); + if (selectedFeatureId && selectedFeatureId !== id) { + draw.deselectFeature(selectedFeatureId); + } + selectedFeatureId = id as string; + }); + + draw.on("deselect", () => { + // console.log("Feature deselected"); + selectedFeatureId = null; + }); + + history.push(processSnapshotForUndo(draw.getSnapshot())); // Push initial empty state + + draw.on("change", (ids, type) => { + if (isRestoring) { + return; + } + + if (debounceTimeout) { + clearTimeout(debounceTimeout); + } + + debounceTimeout = window.setTimeout(() => { + const snapshot = draw.getSnapshot(); + const processedSnapshot = processSnapshotForUndo(snapshot); + const filteredSnapshot = processedSnapshot.filter( + (f) => !f.properties.midPoint && !f.properties.selectionPoint + ); + history.push(filteredSnapshot); + redoHistory = []; + }, 200); + }); + + // [END maps_drawing_terradraw_modes] + + const exportButton = document.getElementById('export-button'); + if (exportButton) { + exportButton.onclick = () => { + const features = draw.getSnapshot(); + const geojson = { + type: "FeatureCollection", + features: features, + }; + const data = JSON.stringify(geojson, null, 2); + const blob = new Blob([data], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "drawing.geojson"; + link.click(); + URL.revokeObjectURL(url); + }; + } + + const uploadButton = document.getElementById('upload-button'); + const uploadInput = document.getElementById('upload-input') as HTMLInputElement; + + if (uploadButton && uploadInput) { + uploadButton.onclick = () => { + uploadInput.click(); + }; + + uploadInput.onchange = (event) => { + const file = (event.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const geojson = JSON.parse(e.target?.result as string); + if (geojson.type === "FeatureCollection") { + draw.addFeatures(geojson.features); + } else { + alert("Invalid GeoJSON file: must be a FeatureCollection."); + } + } catch (error) { + alert("Error parsing GeoJSON file."); + } + }; + reader.readAsText(file); + } + }; + } + + const resizeButton = document.getElementById('resize-button'); + if (resizeButton) { + resizeButton.onclick = () => { + resizingEnabled = !resizingEnabled; + resizeButton.classList.toggle('active', resizingEnabled); + + const flags = { + polygon: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + linestring: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + rectangle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + circle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + freehand: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + }; + + console.log("Updating flags:", flags); + draw.updateModeOptions('select', { flags }); + }; + } + + const deleteSelectedButton = document.getElementById('delete-selected-button'); + if (deleteSelectedButton) { + deleteSelectedButton.onclick = () => { + if (selectedFeatureId) { + draw.removeFeatures([selectedFeatureId]); + } else { + const features = draw.getSnapshot(); + if (features.length > 0) { + const lastFeature = features[features.length - 1]; + if (lastFeature.id) { + draw.removeFeatures([lastFeature.id]); + } + } + } + }; + } + + const undoButton = document.getElementById('undo-button'); + if (undoButton) { + undoButton.onclick = () => { + if (history.length > 1) { + redoHistory.push(history.pop()); + const snapshotToRestore = history[history.length - 1]; + console.log("Restoring snapshot (undo):", snapshotToRestore); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshotToRestore); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + + const redoButton = document.getElementById('redo-button'); + if (redoButton) { + redoButton.onclick = () => { + if (redoHistory.length > 0) { + const snapshot = redoHistory.pop(); + console.log("Restoring snapshot (redo):", snapshot); + history.push(snapshot); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshot); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + }); + + function rotateFeature(feature, angle) { + const newFeature = JSON.parse(JSON.stringify(feature)); + const coordinates = newFeature.geometry.coordinates; + const center = getCenter(coordinates); + + const rotatedCoordinates = coordinates.map(ring => { + return ring.map(point => { + const x = point[0] - center[0]; + const y = point[1] - center[1]; + const newX = x * Math.cos(angle * Math.PI / 180) - y * Math.sin(angle * Math.PI / 180); + const newY = x * Math.sin(angle * Math.PI / 180) + y * Math.cos(angle * Math.PI / 180); + return [newX + center[0], newY + center[1]]; + }); + }); + + newFeature.geometry.coordinates = rotatedCoordinates; + return newFeature; + } + + function getCenter(coordinates) { + let x = 0; + let y = 0; + let count = 0; + coordinates.forEach(ring => { + ring.forEach(point => { + x += point[0]; + y += point[1]; + count++; + }); + }); + return [x / count, y / count]; + } + + document.addEventListener('keydown', (event) => { + if (event.key === 'r' && selectedFeatureId) { + const features = draw.getSnapshot(); + const selectedFeature = features.find(f => f.id === selectedFeatureId); + + if (selectedFeature) { + const newFeature = rotateFeature(selectedFeature, 15); + draw.addFeatures([newFeature]); + } + } + }); + }); + + } catch (e) { + console.error("Error loading Google Maps API:", e); + } +}).catch(e => { + console.error("Error loading Google Maps API:", e); +}); +// [END maps_map_drawing_terradraw] diff --git a/dist/samples/map-drawing-terradraw/docs/style.css b/dist/samples/map-drawing-terradraw/docs/style.css new file mode 100644 index 00000000..2584b672 --- /dev/null +++ b/dist/samples/map-drawing-terradraw/docs/style.css @@ -0,0 +1,94 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_map_drawing_terradraw] */ +html, +body { + height: 100%; + margin: 0; + padding: 0; + font-family: Arial, sans-serif; +} +#map { + height: 100%; + width: 100%; +} +#mode-ui { + position: absolute; + top: 10px; + right: 10px; + background: white; + padding: 10px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + z-index: 1000; + display: flex; + flex-direction: column; +} +#mode-ui button { + margin: 5px 0; + cursor: pointer; +} + +.mode-button { + width: 30px; + height: 30px; + border: 1px solid #ccc; + background-color: white; + padding: 2px; + box-sizing: border-box; +} + +.mode-button img { + width: 100%; + height: 100%; + display: block; + user-select: none; +} + +/* Active state for shape modes */ +.mode-button.active { + background-color: #e0e0e0; /* light grey */ +} + +/* Special buttons default state */ +#select-mode, +#clear-mode, +#delete-selected-button, +#undo-button, +#redo-button, +#export-button, +#upload-button, +#resize-button { + background-color: #000000; +} + +/* Special buttons icon default state */ +#select-mode img, +#clear-mode img, +#delete-selected-button img, +#undo-button img, +#redo-button img, +#export-button img, +#upload-button img, +#resize-button img { + filter: brightness(0) invert(1); +} + +/* Special buttons active/click states */ +#select-mode.active { + background-color: #A9A9A9; /* dark grey */ +} + +#clear-mode:active, +#delete-selected-button:active, +#undo-button:active, +#redo-button:active, +#export-button:active, +#upload-button:active, +#resize-button.active { + background-color: #A9A9A9; /* dark grey */ +} +/* [END maps_map_drawing_terradraw] */ diff --git a/dist/samples/map-drawing-terradraw/jsfiddle/demo.css b/dist/samples/map-drawing-terradraw/jsfiddle/demo.css new file mode 100644 index 00000000..8542350b --- /dev/null +++ b/dist/samples/map-drawing-terradraw/jsfiddle/demo.css @@ -0,0 +1,94 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +html, +body { + height: 100%; + margin: 0; + padding: 0; + font-family: Arial, sans-serif; +} +#map { + height: 100%; + width: 100%; +} +#mode-ui { + position: absolute; + top: 10px; + right: 10px; + background: white; + padding: 10px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + z-index: 1000; + display: flex; + flex-direction: column; +} +#mode-ui button { + margin: 5px 0; + cursor: pointer; +} + +.mode-button { + width: 30px; + height: 30px; + border: 1px solid #ccc; + background-color: white; + padding: 2px; + box-sizing: border-box; +} + +.mode-button img { + width: 100%; + height: 100%; + display: block; + user-select: none; +} + +/* Active state for shape modes */ +.mode-button.active { + background-color: #e0e0e0; /* light grey */ +} + +/* Special buttons default state */ +#select-mode, +#clear-mode, +#delete-selected-button, +#undo-button, +#redo-button, +#export-button, +#upload-button, +#resize-button { + background-color: #000000; +} + +/* Special buttons icon default state */ +#select-mode img, +#clear-mode img, +#delete-selected-button img, +#undo-button img, +#redo-button img, +#export-button img, +#upload-button img, +#resize-button img { + filter: brightness(0) invert(1); +} + +/* Special buttons active/click states */ +#select-mode.active { + background-color: #A9A9A9; /* dark grey */ +} + +#clear-mode:active, +#delete-selected-button:active, +#undo-button:active, +#redo-button:active, +#export-button:active, +#upload-button:active, +#resize-button.active { + background-color: #A9A9A9; /* dark grey */ +} + diff --git a/dist/samples/map-drawing-terradraw/jsfiddle/demo.details b/dist/samples/map-drawing-terradraw/jsfiddle/demo.details new file mode 100644 index 00000000..6cbc1dca --- /dev/null +++ b/dist/samples/map-drawing-terradraw/jsfiddle/demo.details @@ -0,0 +1,7 @@ +name: map-drawing-terradraw +authors: + - Geo Developer IX Documentation Team +tags: + - google maps +load_type: h +description: Sample code supporting Google Maps Platform JavaScript API documentation. diff --git a/dist/samples/map-drawing-terradraw/jsfiddle/demo.html b/dist/samples/map-drawing-terradraw/jsfiddle/demo.html new file mode 100644 index 00000000..181332dc --- /dev/null +++ b/dist/samples/map-drawing-terradraw/jsfiddle/demo.html @@ -0,0 +1,41 @@ + + + + + + Terra Draw with Google Maps API Sample + + + + + +
+ + +
+ + + + + + + + + + + + + + + +
+ + + + + + diff --git a/dist/samples/map-drawing-terradraw/jsfiddle/demo.js b/dist/samples/map-drawing-terradraw/jsfiddle/demo.js new file mode 100644 index 00000000..38988c3b --- /dev/null +++ b/dist/samples/map-drawing-terradraw/jsfiddle/demo.js @@ -0,0 +1,451 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + + +import { Loader } from '@googlemaps/js-api-loader'; +import { TerraDraw, TerraDrawSelectMode, TerraDrawPointMode, TerraDrawLineStringMode, TerraDrawPolygonMode, TerraDrawRectangleMode, TerraDrawCircleMode, TerraDrawFreehandMode } from 'terra-draw'; +import { TerraDrawGoogleMapsAdapter } from 'terra-draw-google-maps-adapter'; + +const colorPalette = [ + "#E74C3C", + "#FF0066", + "#9B59B6", + "#673AB7", + "#3F51B5", + "#3498DB", + "#03A9F4", + "#00BCD4", + "#009688", + "#27AE60", + "#8BC34A", + "#CDDC39", + "#F1C40F", + "#FFC107", + "#F39C12", + "#FF5722", + "#795548" +]; +const getRandomColor = () => colorPalette[Math.floor(Math.random() * colorPalette.length)]; +function processSnapshotForUndo(snapshot) { + // console.log("Processing snapshot for undo:", snapshot); + return snapshot.map(feature => { + const newFeature = JSON.parse(JSON.stringify(feature)); + if (newFeature.properties.mode === 'rectangle') { + // console.log("Processing rectangle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + newFeature.properties.mode = 'polygon'; + } + else if (newFeature.properties.mode === 'circle') { + // console.log("Processing circle for undo:", newFeature); + newFeature.geometry.type = 'Polygon'; + // The radius is already in properties, so we just need to ensure the mode is correct for re-creation + newFeature.properties.mode = 'circle'; + } + return newFeature; + }); +} +function setupModeButtons() { + const modeUI = document.getElementById('mode-ui'); + if (!modeUI) { + return; + } + const modeButtons = { + 'select-mode': 'select', + 'point-mode': 'point', + 'linestring-mode': 'linestring', + 'polygon-mode': 'polygon', + 'rectangle-mode': 'rectangle', + 'circle-mode': 'circle', + 'freehand-mode': 'freehand', + 'clear-mode': 'static' + }; + for (const buttonId in modeButtons) { + const button = document.getElementById(buttonId); + if (button) { + button.onclick = () => { + setActiveButton(buttonId); + const modeName = modeButtons[buttonId]; + if (!draw) { + return; + } + if (modeName === 'static') { + draw.clear(); + draw.setMode('static'); + } + else if (modeName) { + draw.setMode(modeName); + } + }; + } + } +} +function setActiveButton(buttonId) { + const buttons = document.querySelectorAll('.mode-button'); + const resizeButton = document.getElementById('resize-button'); + const isResizeActive = resizeButton?.classList.contains('active'); + buttons.forEach(button => { + if (button.id !== 'resize-button') { + button.classList.remove('active'); + } + }); + const activeButton = document.getElementById(buttonId); + if (activeButton) { + activeButton.classList.add('active'); + } + if (isResizeActive) { + resizeButton?.classList.add('active'); + } +} +function initUI() { + setActiveButton('point-mode'); +} +let map; +let draw; +let currentMode = 'static'; +let history = []; +let redoHistory = []; +let selectedFeatureId = null; +let isRestoring = false; +let resizingEnabled = false; +let debounceTimeout; +const loader = new Loader({ + apiKey: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", + version: "weekly", + libraries: ["maps", "drawing", "marker"] +}); +loader.load().then(async () => { + try { + const { Map } = await google.maps.importLibrary("maps"); + const { LatLngBounds } = await google.maps.importLibrary("core"); + const { Data } = await google.maps.importLibrary("maps"); + const mapOptions = { + center: { lat: 48.862, lng: 2.342 }, + zoom: 12, + mapId: 'c306b3c6dd3ed8d9', // raster '6a17c323f461e521', + mapTypeId: 'roadmap', + zoomControl: false, + tilt: 45, + mapTypeControl: true, + clickableIcons: false, + streetViewControl: false, + fullscreenControl: false, + }; + const mapDiv = document.getElementById("map"); + map = new Map(mapDiv, mapOptions); + map.addListener("click", () => { + if (draw) { + console.log("Current draw mode on map click:", draw.getMode()); + } + }); + map.addListener("projection_changed", () => { + + draw = new TerraDraw({ + adapter: new TerraDrawGoogleMapsAdapter({ map, lib: google.maps, coordinatePrecision: 9 }), + modes: [ + new TerraDrawSelectMode({ + flags: { + polygon: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + linestring: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + point: { + feature: { + draggable: true, + rotateable: true, + }, + }, + rectangle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + circle: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + freehand: { + feature: { + draggable: true, + rotateable: true, + coordinates: { + midpoints: true, + draggable: true, + deletable: true, + }, + }, + }, + }, + }), + new TerraDrawPointMode({ + editable: true, + styles: { pointColor: getRandomColor() }, + }), + new TerraDrawLineStringMode({ + editable: true, + styles: { lineStringColor: getRandomColor() }, + }), + new TerraDrawPolygonMode({ + editable: true, + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawRectangleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawCircleMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + new TerraDrawFreehandMode({ + styles: (() => { + const color = getRandomColor(); + return { + fillColor: color, + outlineColor: color, + }; + })(), + }), + ], + }); + draw.start(); + draw.on('ready', () => { + console.log("TerraDraw is ready!"); + initUI(); + setupModeButtons(); + draw.setMode('point'); + currentMode = 'point'; + setActiveButton('point-mode'); + draw.on("select", (id) => { + // console.log(`Feature selected: ${id}`); + if (selectedFeatureId && selectedFeatureId !== id) { + draw.deselectFeature(selectedFeatureId); + } + selectedFeatureId = id; + }); + draw.on("deselect", () => { + // console.log("Feature deselected"); + selectedFeatureId = null; + }); + history.push(processSnapshotForUndo(draw.getSnapshot())); // Push initial empty state + draw.on("change", (ids, type) => { + if (isRestoring) { + return; + } + if (debounceTimeout) { + clearTimeout(debounceTimeout); + } + debounceTimeout = window.setTimeout(() => { + const snapshot = draw.getSnapshot(); + const processedSnapshot = processSnapshotForUndo(snapshot); + const filteredSnapshot = processedSnapshot.filter((f) => !f.properties.midPoint && !f.properties.selectionPoint); + history.push(filteredSnapshot); + redoHistory = []; + }, 200); + }); + + const exportButton = document.getElementById('export-button'); + if (exportButton) { + exportButton.onclick = () => { + const features = draw.getSnapshot(); + const geojson = { + type: "FeatureCollection", + features: features, + }; + const data = JSON.stringify(geojson, null, 2); + const blob = new Blob([data], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "drawing.geojson"; + link.click(); + URL.revokeObjectURL(url); + }; + } + const uploadButton = document.getElementById('upload-button'); + const uploadInput = document.getElementById('upload-input'); + if (uploadButton && uploadInput) { + uploadButton.onclick = () => { + uploadInput.click(); + }; + uploadInput.onchange = (event) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const geojson = JSON.parse(e.target?.result); + if (geojson.type === "FeatureCollection") { + draw.addFeatures(geojson.features); + } + else { + alert("Invalid GeoJSON file: must be a FeatureCollection."); + } + } + catch (error) { + alert("Error parsing GeoJSON file."); + } + }; + reader.readAsText(file); + } + }; + } + const resizeButton = document.getElementById('resize-button'); + if (resizeButton) { + resizeButton.onclick = () => { + resizingEnabled = !resizingEnabled; + resizeButton.classList.toggle('active', resizingEnabled); + const flags = { + polygon: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + linestring: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + rectangle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + circle: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + freehand: { feature: { draggable: true, coordinates: { resizable: resizingEnabled ? 'center' : undefined, draggable: !resizingEnabled } } }, + }; + console.log("Updating flags:", flags); + draw.updateModeOptions('select', { flags }); + }; + } + const deleteSelectedButton = document.getElementById('delete-selected-button'); + if (deleteSelectedButton) { + deleteSelectedButton.onclick = () => { + if (selectedFeatureId) { + draw.removeFeatures([selectedFeatureId]); + } + else { + const features = draw.getSnapshot(); + if (features.length > 0) { + const lastFeature = features[features.length - 1]; + if (lastFeature.id) { + draw.removeFeatures([lastFeature.id]); + } + } + } + }; + } + const undoButton = document.getElementById('undo-button'); + if (undoButton) { + undoButton.onclick = () => { + if (history.length > 1) { + redoHistory.push(history.pop()); + const snapshotToRestore = history[history.length - 1]; + console.log("Restoring snapshot (undo):", snapshotToRestore); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshotToRestore); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + const redoButton = document.getElementById('redo-button'); + if (redoButton) { + redoButton.onclick = () => { + if (redoHistory.length > 0) { + const snapshot = redoHistory.pop(); + console.log("Restoring snapshot (redo):", snapshot); + history.push(snapshot); + isRestoring = true; + draw.clear(); + draw.addFeatures(snapshot); + setTimeout(() => { isRestoring = false; }, 0); + } + }; + } + }); + function rotateFeature(feature, angle) { + const newFeature = JSON.parse(JSON.stringify(feature)); + const coordinates = newFeature.geometry.coordinates; + const center = getCenter(coordinates); + const rotatedCoordinates = coordinates.map(ring => { + return ring.map(point => { + const x = point[0] - center[0]; + const y = point[1] - center[1]; + const newX = x * Math.cos(angle * Math.PI / 180) - y * Math.sin(angle * Math.PI / 180); + const newY = x * Math.sin(angle * Math.PI / 180) + y * Math.cos(angle * Math.PI / 180); + return [newX + center[0], newY + center[1]]; + }); + }); + newFeature.geometry.coordinates = rotatedCoordinates; + return newFeature; + } + function getCenter(coordinates) { + let x = 0; + let y = 0; + let count = 0; + coordinates.forEach(ring => { + ring.forEach(point => { + x += point[0]; + y += point[1]; + count++; + }); + }); + return [x / count, y / count]; + } + document.addEventListener('keydown', (event) => { + if (event.key === 'r' && selectedFeatureId) { + const features = draw.getSnapshot(); + const selectedFeature = features.find(f => f.id === selectedFeatureId); + if (selectedFeature) { + const newFeature = rotateFeature(selectedFeature, 15); + draw.addFeatures([newFeature]); + } + } + }); + }); + } + catch (e) { + console.error("Error loading Google Maps API:", e); + } +}).catch(e => { + console.error("Error loading Google Maps API:", e); +}); + diff --git a/package-lock.json b/package-lock.json index c8a20fb7..1a6b2825 100644 --- a/package-lock.json +++ b/package-lock.json @@ -631,6 +631,10 @@ "resolved": "samples/deckgl-polygon", "link": true }, + "node_modules/@js-api-samples/map-drawing-terradraw": { + "resolved": "samples/map-drawing-terradraw", + "link": true + }, "node_modules/@js-api-samples/map-simple": { "resolved": "samples/map-simple", "link": true @@ -1625,10 +1629,6 @@ "node": "20 || >=22" } }, - "node_modules/map-drawing-terradraw-basic": { - "resolved": "samples/map-drawing-terradraw", - "link": true - }, "node_modules/minimatch": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", @@ -2506,7 +2506,7 @@ "version": "1.0.0" }, "samples/map-drawing-terradraw": { - "name": "map-drawing-terradraw-basic", + "name": "@js-api-samples/map-drawing-terradraw", "version": "1.0.0", "dependencies": { "@googlemaps/js-api-loader": "^1.16.8",