{"version":3,"file":"terra-draw.cjs","sources":["../src/geometry/limit-decimal-precision.ts","../src/geometry/measure/pixel-distance.ts","../src/adapters/common/adapter-listener.ts","../src/adapters/common/base.adapter.ts","../src/adapters/google-maps.adapter.ts","../src/adapters/leaflet.adapter.ts","../src/adapters/mapbox-gl.adapter.ts","../src/adapters/maplibre-gl.adapter.ts","../node_modules/ol/util.js","../node_modules/ol/size.js","../node_modules/ol/style/Image.js","../node_modules/color-space/rgb.js","../node_modules/color-space/xyz.js","../node_modules/color-space/luv.js","../node_modules/color-space/lchuv.js","../node_modules/color-parse/node_modules/color-name/index.js","../node_modules/color-parse/index.js","../node_modules/color-space/hsl.js","../node_modules/ol/math.js","../node_modules/ol/color.js","../node_modules/color-rgba/index.js","../node_modules/ol/has.js","../node_modules/ol/dom.js","../node_modules/ol/Disposable.js","../node_modules/ol/events/Event.js","../node_modules/ol/functions.js","../node_modules/ol/obj.js","../node_modules/ol/events/Target.js","../node_modules/ol/events/EventType.js","../node_modules/ol/events.js","../node_modules/ol/style/IconImageCache.js","../node_modules/ol/ImageState.js","../node_modules/ol/style/IconImage.js","../node_modules/ol/Image.js","../node_modules/ol/colorlike.js","../node_modules/ol/ObjectEventType.js","../node_modules/ol/Observable.js","../node_modules/ol/Object.js","../node_modules/ol/render/canvas.js","../node_modules/ol/style/RegularShape.js","../node_modules/ol/style/Circle.js","../node_modules/ol/style/Fill.js","../node_modules/ol/style/Stroke.js","../node_modules/ol/style/Style.js","../node_modules/ol/proj/Units.js","../node_modules/ol/proj/Projection.js","../node_modules/ol/proj/epsg3857.js","../node_modules/ol/proj/epsg4326.js","../node_modules/ol/proj/projections.js","../node_modules/ol/proj/transforms.js","../node_modules/ol/proj.js","../src/adapters/openlayers.adapter.ts","../src/common.ts","../src/adapters/arcgis-maps-sdk.adapter.ts","../src/modes/base.mode.ts","../src/store/store-feature-validation.ts","../src/geometry/measure/haversine-distance.ts","../src/geometry/helpers.ts","../src/geometry/project/web-mercator.ts","../src/geometry/shape/create-circle.ts","../src/geometry/boolean/self-intersects.ts","../src/geometry/boolean/is-valid-coordinate.ts","../src/validations/polygon.validation.ts","../src/modes/circle/circle.mode.ts","../src/util/styling.ts","../src/geometry/shape/web-mercator-distortion.ts","../src/modes/freehand/freehand.mode.ts","../src/modes/base.behavior.ts","../src/geometry/shape/create-bbox.ts","../src/modes/click-bounding-box.behavior.ts","../src/modes/pixel-distance.behavior.ts","../src/modes/snapping.behavior.ts","../src/geometry/measure/destination.ts","../src/geometry/measure/bearing.ts","../src/geometry/measure/slice-along.ts","../src/geometry/shape/great-circle-coordinates.ts","../src/modes/insert-coordinates.behavior.ts","../src/geometry/coordinates-identical.ts","../src/modes/linestring/linestring.mode.ts","../src/validations/point.validation.ts","../src/modes/point/point.mode.ts","../src/modes/polygon/behaviors/closing-points.behavior.ts","../src/modes/polygon/polygon.mode.ts","../src/util/geoms.ts","../src/modes/rectangle/rectangle.mode.ts","../src/modes/render/render.mode.ts","../src/validations/linestring.validation.ts","../src/geometry/measure/rhumb-bearing.ts","../src/geometry/measure/rhumb-destination.ts","../src/geometry/midpoint-coordinate.ts","../src/geometry/get-midpoints.ts","../src/modes/select/behaviors/midpoint.behavior.ts","../src/modes/select/behaviors/selection-point.behavior.ts","../src/geometry/get-coordinates-as-points.ts","../src/geometry/boolean/point-in-polygon.ts","../src/geometry/measure/pixel-distance-to-line.ts","../src/modes/select/behaviors/feature-at-pointer-event.behavior.ts","../src/modes/select/behaviors/drag-feature.behavior.ts","../src/modes/select/behaviors/drag-coordinate.behavior.ts","../src/geometry/centroid.ts","../src/geometry/measure/rhumb-distance.ts","../src/geometry/web-mercator-centroid.ts","../src/modes/select/behaviors/rotate-feature.behavior.ts","../src/geometry/transform/rotate.ts","../src/modes/select/behaviors/scale-feature.behavior.ts","../src/geometry/transform/scale.ts","../src/modes/select/behaviors/drag-coordinate-resize.behavior.ts","../src/modes/select/select.mode.ts","../src/modes/static/static.mode.ts","../src/store/spatial-index/quickselect.ts","../src/store/spatial-index/rbush.ts","../src/store/spatial-index/spatial-index.ts","../src/store/store.ts","../src/util/id.ts","../src/geometry/measure/area.ts","../src/geometry/calculate-relative-angle.ts","../src/modes/angled-rectangle/angled-rectangle.mode.ts","../src/geometry/determine-halfplane.ts","../src/terra-draw.ts","../src/validations/max-size.validation.ts","../src/validations/min-size.validation.ts","../src/validations/not-self-intersecting.validation.ts"],"sourcesContent":["export function limitPrecision(num: number, decimalLimit = 9) {\n\tconst decimals = Math.pow(10, decimalLimit);\n\treturn Math.round(num * decimals) / decimals;\n}\n","export const cartesianDistance = (\n\tpointOne: { x: number; y: number },\n\tpointTwo: { x: number; y: number },\n) => {\n\tconst { x: x1, y: y1 } = pointOne;\n\tconst { x: x2, y: y2 } = pointTwo;\n\tconst y = x2 - x1;\n\tconst x = y2 - y1;\n\treturn Math.sqrt(x * x + y * y);\n};\n","export class AdapterListener any> {\n\tpublic name: string;\n\tpublic callback: (...args: any[]) => any;\n\tpublic registered = false;\n\tpublic register: any;\n\tpublic unregister: any;\n\n\t/**\n\t * Creates a new AdapterListener instance with the provided configuration.\n\t *\n\t * @param {Object} config - The configuration object for the listener.\n\t * @param {string} config.name - The name of the event listener.\n\t * @param {Function} config.callback - The callback function to be called when the event is triggered.\n\t * @param {Function} config.unregister - The function to unregister the event listeners.\n\t * @param {Function} config.register - The function to register the event listeners.\n\t */\n\tconstructor({\n\t\tname,\n\t\tcallback,\n\t\tunregister,\n\t\tregister,\n\t}: {\n\t\tname: string;\n\t\tcallback: Callback;\n\t\tunregister: (callbacks: Callback) => void;\n\t\tregister: (callback: Callback) => void;\n\t}) {\n\t\tthis.name = name;\n\n\t\t// Function to register the event listeners\n\t\tthis.register = () => {\n\t\t\tif (!this.registered) {\n\t\t\t\tthis.registered = true;\n\t\t\t\tregister(callback);\n\t\t\t}\n\t\t};\n\n\t\t// Function to unregister the event listeners\n\t\tthis.unregister = () => {\n\t\t\tif (this.register) {\n\t\t\t\tthis.registered = false;\n\t\t\t\tunregister(callback);\n\t\t\t}\n\t\t};\n\n\t\tthis.callback = callback;\n\t}\n}\n","import {\n\tProject,\n\tUnproject,\n\tTerraDrawCallbacks,\n\tTerraDrawChanges,\n\tTerraDrawMouseEvent,\n\tSetCursor,\n\tTerraDrawStylingFunction,\n\tGetLngLatFromEvent,\n\tTerraDrawAdapter,\n} from \"../../common\";\nimport { limitPrecision } from \"../../geometry/limit-decimal-precision\";\nimport { cartesianDistance } from \"../../geometry/measure/pixel-distance\";\nimport { AdapterListener } from \"./adapter-listener\";\n\ntype BasePointerListener = (event: PointerEvent) => void;\ntype BaseKeyboardListener = (event: KeyboardEvent) => void;\ntype BaseMouseListener = (event: MouseEvent) => void;\n\nexport type BaseAdapterConfig = {\n\tcoordinatePrecision?: number;\n\tminPixelDragDistanceDrawing?: number;\n\tminPixelDragDistance?: number;\n\tminPixelDragDistanceSelecting?: number;\n};\n\nexport abstract class TerraDrawBaseAdapter implements TerraDrawAdapter {\n\tconstructor(config: BaseAdapterConfig) {\n\t\tthis._minPixelDragDistance =\n\t\t\ttypeof config.minPixelDragDistance === \"number\"\n\t\t\t\t? config.minPixelDragDistance\n\t\t\t\t: 1;\n\n\t\tthis._minPixelDragDistanceSelecting =\n\t\t\ttypeof config.minPixelDragDistanceSelecting === \"number\"\n\t\t\t\t? config.minPixelDragDistanceSelecting\n\t\t\t\t: 1;\n\n\t\tthis._minPixelDragDistanceDrawing =\n\t\t\ttypeof config.minPixelDragDistanceDrawing === \"number\"\n\t\t\t\t? config.minPixelDragDistanceDrawing\n\t\t\t\t: 8;\n\n\t\tthis._coordinatePrecision =\n\t\t\ttypeof config.coordinatePrecision === \"number\"\n\t\t\t\t? config.coordinatePrecision\n\t\t\t\t: 9;\n\t}\n\n\tprotected _minPixelDragDistance: number;\n\tprotected _minPixelDragDistanceDrawing: number;\n\tprotected _minPixelDragDistanceSelecting: number;\n\tprotected _lastDrawEvent: TerraDrawMouseEvent | undefined;\n\tprotected _coordinatePrecision: number;\n\tprotected _heldKeys: Set = new Set();\n\tprotected _listeners: AdapterListener<\n\t\tBasePointerListener | BaseKeyboardListener | BaseMouseListener\n\t>[] = [];\n\tprotected _dragState: \"not-dragging\" | \"pre-dragging\" | \"dragging\" =\n\t\t\"not-dragging\";\n\tprotected _currentModeCallbacks: TerraDrawCallbacks | undefined;\n\n\tpublic abstract getMapEventElement(): HTMLElement;\n\n\tprotected getButton(event: PointerEvent | MouseEvent) {\n\t\tif (event.button === -1) {\n\t\t\treturn \"neither\";\n\t\t} else if (event.button === 0) {\n\t\t\treturn \"left\";\n\t\t} else if (event.button === 1) {\n\t\t\treturn \"middle\";\n\t\t} else if (event.button === 2) {\n\t\t\treturn \"right\";\n\t\t}\n\n\t\t// This shouldn't happen (?)\n\t\treturn \"neither\";\n\t}\n\n\tprotected getMapElementXYPosition(event: PointerEvent | MouseEvent) {\n\t\tconst mapElement = this.getMapEventElement();\n\t\tconst { left, top } = mapElement.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tcontainerX: event.clientX - left,\n\t\t\tcontainerY: event.clientY - top,\n\t\t};\n\t}\n\n\tprotected getDrawEventFromEvent(\n\t\tevent: PointerEvent | MouseEvent,\n\t): TerraDrawMouseEvent | null {\n\t\tconst latLng = this.getLngLatFromEvent(event);\n\n\t\tif (!latLng) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst { lng, lat } = latLng;\n\t\tconst { containerX, containerY } = this.getMapElementXYPosition(event);\n\t\tconst button = this.getButton(event);\n\t\tconst heldKeys = Array.from(this._heldKeys);\n\n\t\treturn {\n\t\t\tlng: limitPrecision(lng, this._coordinatePrecision),\n\t\t\tlat: limitPrecision(lat, this._coordinatePrecision),\n\t\t\tcontainerX,\n\t\t\tcontainerY,\n\t\t\tbutton,\n\t\t\theldKeys,\n\t\t};\n\t}\n\n\t/**\n\t * Registers the provided callbacks for the current drawing mode and attaches\n\t * the necessary event listeners.\n\t * @param {TerraDrawCallbacks} callbacks - An object containing callback functions\n\t * for handling various drawing events in the current mode.\n\t */\n\tpublic register(callbacks: TerraDrawCallbacks) {\n\t\tthis._currentModeCallbacks = callbacks;\n\n\t\tthis._listeners = this.getAdapterListeners();\n\n\t\tthis._listeners.forEach((listener) => {\n\t\t\tlistener.register();\n\t\t});\n\t}\n\n\t/**\n\t * Gets the coordinate precision.\n\t * @returns {number} The coordinate precision.\n\t * @description The coordinate precision is the number of decimal places. Note that the precision will be overriden by the precision of the TerraDraw Adapter.\n\t */\n\tpublic getCoordinatePrecision() {\n\t\treturn this._coordinatePrecision;\n\t}\n\n\tprivate getAdapterListeners() {\n\t\treturn [\n\t\t\tnew AdapterListener({\n\t\t\t\tname: \"pointerdown\",\n\t\t\t\tcallback: (event) => {\n\t\t\t\t\tif (!this._currentModeCallbacks) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// We don't support multitouch as this point in time\n\t\t\t\t\tif (!event.isPrimary) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst drawEvent = this.getDrawEventFromEvent(event);\n\t\t\t\t\tif (!drawEvent) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis._dragState = \"pre-dragging\";\n\n\t\t\t\t\t// On pointer devices pointer mouse move events won't be\n\t\t\t\t\t// triggered so this._lastDrawEvent will not get set in\n\t\t\t\t\t// pointermove listener, so we must set it here.\n\t\t\t\t\tthis._lastDrawEvent = drawEvent;\n\t\t\t\t},\n\t\t\t\tregister: (callback) => {\n\t\t\t\t\tthis.getMapEventElement().addEventListener(\"pointerdown\", callback);\n\t\t\t\t},\n\t\t\t\tunregister: (callback) => {\n\t\t\t\t\tthis.getMapEventElement().removeEventListener(\n\t\t\t\t\t\t\"pointerdown\",\n\t\t\t\t\t\tcallback,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t}),\n\t\t\tnew AdapterListener({\n\t\t\t\tname: \"pointermove\",\n\t\t\t\tcallback: (event) => {\n\t\t\t\t\tif (!this._currentModeCallbacks) return;\n\n\t\t\t\t\t// We don't support multitouch as this point in time\n\t\t\t\t\tif (!event.isPrimary) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tevent.preventDefault();\n\n\t\t\t\t\tconst drawEvent = this.getDrawEventFromEvent(event);\n\t\t\t\t\tif (!drawEvent) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this._dragState === \"not-dragging\") {\n\t\t\t\t\t\t// If we're not dragging we can trigger the onMouseMove event\n\t\t\t\t\t\tthis._currentModeCallbacks.onMouseMove(drawEvent);\n\t\t\t\t\t\tthis._lastDrawEvent = drawEvent;\n\t\t\t\t\t} else if (this._dragState === \"pre-dragging\") {\n\t\t\t\t\t\t// This should always be set because of pointerdown event\n\t\t\t\t\t\tif (!this._lastDrawEvent) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst lastEventXY = {\n\t\t\t\t\t\t\tx: this._lastDrawEvent.containerX,\n\t\t\t\t\t\t\ty: this._lastDrawEvent.containerY,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst currentEventXY = {\n\t\t\t\t\t\t\tx: drawEvent.containerX,\n\t\t\t\t\t\t\ty: drawEvent.containerY,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// We only want to prevent micro drags when we are\n\t\t\t\t\t\t// drawing as doing in on selection can cause janky\n\t\t\t\t\t\t// behaviours\n\t\t\t\t\t\tconst modeState = this._currentModeCallbacks.getState();\n\n\t\t\t\t\t\tconst pixelDistanceToCheck = cartesianDistance(\n\t\t\t\t\t\t\tlastEventXY,\n\t\t\t\t\t\t\tcurrentEventXY,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// We start off assuming it is not a microdrag\n\t\t\t\t\t\tlet isMicroDrag = false;\n\n\t\t\t\t\t\tif (modeState === \"drawing\") {\n\t\t\t\t\t\t\t// We want to ignore very small pointer movements when holding\n\t\t\t\t\t\t\t// the map down as these are normally done by accident when\n\t\t\t\t\t\t\t// drawing and is not an intended drag\n\t\t\t\t\t\t\tisMicroDrag =\n\t\t\t\t\t\t\t\tpixelDistanceToCheck < this._minPixelDragDistanceDrawing;\n\t\t\t\t\t\t} else if (modeState === \"selecting\") {\n\t\t\t\t\t\t\t// Simiarly when selecting, we want to ignore very small pointer\n\t\t\t\t\t\t\t// movements when holding the map down as these are normally done\n\t\t\t\t\t\t\t// by accident when drawing and is not an intended drag\n\t\t\t\t\t\t\tisMicroDrag =\n\t\t\t\t\t\t\t\tpixelDistanceToCheck < this._minPixelDragDistanceSelecting;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Same as above, but when not drawing we generally want a much lower tolerance\n\t\t\t\t\t\t\tisMicroDrag = pixelDistanceToCheck < this._minPixelDragDistance;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If it is a microdrag we do not register it by returning early\n\t\t\t\t\t\tif (isMicroDrag) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis._dragState = \"dragging\";\n\t\t\t\t\t\tthis._currentModeCallbacks.onDragStart(\n\t\t\t\t\t\t\tdrawEvent,\n\t\t\t\t\t\t\t(enabled: boolean) => {\n\t\t\t\t\t\t\t\tthis.setDraggability.bind(this)(enabled);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (this._dragState === \"dragging\") {\n\t\t\t\t\t\tthis._currentModeCallbacks.onDrag(drawEvent, (enabled: boolean) => {\n\t\t\t\t\t\t\tthis.setDraggability.bind(this)(enabled);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.addEventListener(\"pointermove\", callback);\n\t\t\t\t},\n\t\t\t\tunregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.removeEventListener(\"pointermove\", callback);\n\t\t\t\t},\n\t\t\t}),\n\t\t\tnew AdapterListener({\n\t\t\t\tname: \"contextmenu\",\n\t\t\t\tcallback: (event) => {\n\t\t\t\t\tif (!this._currentModeCallbacks) return;\n\n\t\t\t\t\t// We do not want the context menu to open\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t},\n\t\t\t\tregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.addEventListener(\"contextmenu\", callback);\n\t\t\t\t},\n\t\t\t\tunregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.removeEventListener(\"contextmenu\", callback);\n\t\t\t\t},\n\t\t\t}),\n\t\t\tnew AdapterListener({\n\t\t\t\tname: \"pointerup\",\n\t\t\t\tcallback: (event) => {\n\t\t\t\t\tif (!this._currentModeCallbacks) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (event.target !== this.getMapEventElement()) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// We don't support multitouch as this point in time\n\t\t\t\t\tif (!event.isPrimary) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst drawEvent = this.getDrawEventFromEvent(event);\n\n\t\t\t\t\tif (!drawEvent) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this._dragState === \"dragging\") {\n\t\t\t\t\t\tthis._currentModeCallbacks.onDragEnd(drawEvent, (enabled) => {\n\t\t\t\t\t\t\tthis.setDraggability.bind(this)(enabled);\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (\n\t\t\t\t\t\tthis._dragState === \"not-dragging\" ||\n\t\t\t\t\t\tthis._dragState === \"pre-dragging\"\n\t\t\t\t\t) {\n\t\t\t\t\t\t// If we're not dragging or about to drag we\n\t\t\t\t\t\t// can trigger the onClick event\n\t\t\t\t\t\tthis._currentModeCallbacks.onClick(drawEvent);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Ensure we go back to the regular behaviour\n\t\t\t\t\t// not dragging and re-enable draggin on the actual map\n\t\t\t\t\tthis._dragState = \"not-dragging\";\n\t\t\t\t\tthis.setDraggability(true);\n\t\t\t\t},\n\t\t\t\tregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.addEventListener(\"pointerup\", callback);\n\t\t\t\t},\n\t\t\t\tunregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.removeEventListener(\"pointerup\", callback);\n\t\t\t\t},\n\t\t\t}),\n\t\t\tnew AdapterListener({\n\t\t\t\tname: \"keyup\",\n\t\t\t\tcallback: (event: KeyboardEvent) => {\n\t\t\t\t\t// map has no keypress event, so we add one to the canvas itself\n\n\t\t\t\t\tif (!this._currentModeCallbacks) return;\n\n\t\t\t\t\tthis._heldKeys.delete(event.key);\n\n\t\t\t\t\tthis._currentModeCallbacks.onKeyUp({\n\t\t\t\t\t\tkey: event.key,\n\t\t\t\t\t\theldKeys: Array.from(this._heldKeys),\n\t\t\t\t\t\tpreventDefault: () => event.preventDefault(),\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.addEventListener(\"keyup\", callback);\n\t\t\t\t},\n\t\t\t\tunregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.removeEventListener(\"keyup\", callback);\n\t\t\t\t},\n\t\t\t}),\n\t\t\tnew AdapterListener({\n\t\t\t\tname: \"keydown\",\n\t\t\t\tcallback: (event: KeyboardEvent) => {\n\t\t\t\t\tif (!this._currentModeCallbacks) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis._heldKeys.add(event.key);\n\n\t\t\t\t\tthis._currentModeCallbacks.onKeyDown({\n\t\t\t\t\t\tkey: event.key,\n\t\t\t\t\t\theldKeys: Array.from(this._heldKeys),\n\t\t\t\t\t\tpreventDefault: () => event.preventDefault(),\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.addEventListener(\"keydown\", callback);\n\t\t\t\t},\n\t\t\t\tunregister: (callback) => {\n\t\t\t\t\tconst mapElement = this.getMapEventElement();\n\t\t\t\t\tmapElement.removeEventListener(\"keydown\", callback);\n\t\t\t\t},\n\t\t\t}),\n\t\t];\n\t}\n\n\t/**\n\t * Unregisters the event listeners for the current drawing mode.\n\t * This is typically called when switching between drawing modes or\n\t * stopping the drawing process.\n\t */\n\tpublic unregister() {\n\t\tthis._listeners.forEach((listener) => {\n\t\t\tlistener.unregister();\n\t\t});\n\t\tthis.clear();\n\t}\n\n\tpublic abstract clear(): void;\n\n\tpublic abstract project(...args: Parameters): ReturnType;\n\n\tpublic abstract unproject(\n\t\t...args: Parameters\n\t): ReturnType;\n\n\tpublic abstract setCursor(\n\t\t...args: Parameters\n\t): ReturnType;\n\n\tpublic abstract getLngLatFromEvent(\n\t\t...event: Parameters\n\t): ReturnType;\n\n\tpublic abstract setDraggability(enabled: boolean): void;\n\n\tpublic abstract setDoubleClickToZoom(enabled: boolean): void;\n\n\tpublic abstract render(\n\t\tchanges: TerraDrawChanges,\n\t\tstyling: TerraDrawStylingFunction,\n\t): void;\n}\n","import {\n\tTerraDrawChanges,\n\tSetCursor,\n\tTerraDrawStylingFunction,\n\tTerraDrawCallbacks,\n} from \"../common\";\nimport { GeoJsonObject } from \"geojson\";\nimport { BaseAdapterConfig, TerraDrawBaseAdapter } from \"./common/base.adapter\";\nimport { FeatureId } from \"../store/store\";\n\nexport class TerraDrawGoogleMapsAdapter extends TerraDrawBaseAdapter {\n\tconstructor(\n\t\tconfig: {\n\t\t\tlib: typeof google.maps;\n\t\t\tmap: google.maps.Map;\n\t\t} & BaseAdapterConfig,\n\t) {\n\t\tsuper(config);\n\t\tthis._lib = config.lib;\n\t\tthis._map = config.map;\n\n\t\t// In order for the internals of the adapter to work we require an ID to\n\t\t// allow query selectors to work\n\t\tif (!this._map.getDiv().id) {\n\t\t\tthrow new Error(\"Google Map container div requires and id to be set\");\n\t\t}\n\n\t\tthis._coordinatePrecision =\n\t\t\ttypeof config.coordinatePrecision === \"number\"\n\t\t\t\t? config.coordinatePrecision\n\t\t\t\t: 9;\n\t}\n\n\tprivate _cursor: string | undefined;\n\tprivate _cursorStyleSheet: HTMLStyleElement | undefined;\n\tprivate _lib: typeof google.maps;\n\tprivate _map: google.maps.Map;\n\tprivate _overlay: google.maps.OverlayView | undefined;\n\tprivate _clickEventListener: google.maps.MapsEventListener | undefined;\n\tprivate _mouseMoveEventListener: google.maps.MapsEventListener | undefined;\n\n\tprivate get _layers(): boolean {\n\t\treturn Boolean(this.renderedFeatureIds?.size > 0);\n\t}\n\n\t/**\n\t * Generates an SVG path string for a circle with the given center coordinates and radius.\n\t * Based off this StackOverflow answer: https://stackoverflow.com/a/27905268/1363484\n\t * @param cx The x-coordinate of the circle's center.\n\t * @param cy The y-coordinate of the circle's center.\n\t * @param r The radius of the circle.\n\t * @returns The SVG path string representing the circle.\n\t */\n\tprivate circlePath(cx: number, cy: number, r: number) {\n\t\tconst d = r * 2;\n\t\treturn `M ${cx} ${cy} m -${r}, 0 a ${r},${r} 0 1,0 ${d},0 a ${r},${r} 0 1,0 -${d},0`;\n\t}\n\n\tpublic register(callbacks: TerraDrawCallbacks) {\n\t\tsuper.register(callbacks);\n\n\t\t// The overlay is responsible for allow us to\n\t\t// get the projection, which in turn allows us to\n\t\t// go through lng/lat to pixel space and vice versa\n\t\tthis._overlay = new this._lib.OverlayView();\n\t\tthis._overlay.draw = function () {};\n\n\t\t// Unforunately it is only ready after the onAdd\n\t\t// method is called, which is why we need to use the 'ready'\n\t\t// listener with the Google Maps adapter\n\t\tthis._overlay.onAdd = () => {\n\t\t\tthis._currentModeCallbacks &&\n\t\t\t\tthis._currentModeCallbacks.onReady &&\n\t\t\t\tthis._currentModeCallbacks.onReady();\n\t\t};\n\t\tthis._overlay.setMap(this._map);\n\n\t\t// Clicking on data geometries triggers\n\t\t// swallows the map onclick event,\n\t\t// so we need to forward it to the click callback handler\n\t\tthis._clickEventListener = this._map.data.addListener(\n\t\t\t\"click\",\n\t\t\t(\n\t\t\t\tevent: google.maps.MapMouseEvent & {\n\t\t\t\t\tdomEvent: MouseEvent;\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\tconst clickListener = this._listeners.find(\n\t\t\t\t\t({ name }) => name === \"click\",\n\t\t\t\t);\n\t\t\t\tif (clickListener) {\n\t\t\t\t\tclickListener.callback(event);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tthis._mouseMoveEventListener = this._map.data.addListener(\n\t\t\t\"mousemove\",\n\t\t\t(\n\t\t\t\tevent: google.maps.MapMouseEvent & {\n\t\t\t\t\tdomEvent: MouseEvent;\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\tconst mouseMoveListener = this._listeners.find(\n\t\t\t\t\t({ name }) => name === \"mousemove\",\n\t\t\t\t);\n\t\t\t\tif (mouseMoveListener) {\n\t\t\t\t\tmouseMoveListener.callback(event);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic unregister(): void {\n\t\tsuper.unregister();\n\t\tthis._clickEventListener?.remove();\n\t\tthis._mouseMoveEventListener?.remove();\n\t\tthis._overlay?.setMap(null);\n\t\tthis._overlay = undefined;\n\t}\n\n\t/**\n\t * Returns the longitude and latitude coordinates from a given PointerEvent on the map.\n\t * @param event The PointerEvent or MouseEvent containing the screen coordinates of the pointer.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude, or null if the conversion is not possible.\n\t */\n\tgetLngLatFromEvent(event: PointerEvent | MouseEvent) {\n\t\tif (!this._overlay) {\n\t\t\tthrow new Error(\"cannot get overlay\");\n\t\t}\n\n\t\tconst bounds = this._map.getBounds();\n\n\t\tif (!bounds) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst ne = bounds.getNorthEast();\n\t\tconst sw = bounds.getSouthWest();\n\t\tconst latLngBounds = new this._lib.LatLngBounds(sw, ne);\n\n\t\tconst mapCanvas = this._map.getDiv();\n\t\tconst offsetX = event.clientX - mapCanvas.getBoundingClientRect().left;\n\t\tconst offsetY = event.clientY - mapCanvas.getBoundingClientRect().top;\n\t\tconst screenCoord = new this._lib.Point(offsetX, offsetY);\n\n\t\tconst projection = this._overlay.getProjection();\n\t\tif (!projection) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst latLng = projection.fromContainerPixelToLatLng(screenCoord);\n\n\t\tif (latLng && latLngBounds.contains(latLng)) {\n\t\t\treturn { lng: latLng.lng(), lat: latLng.lat() };\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves the HTML element of the Google Map element that handles interaction events\n\t * @returns The HTMLElement representing the map container.\n\t */\n\tpublic getMapEventElement() {\n\t\t// TODO: This is a bit hacky, maybe there is a better solution here\n\t\tconst selector = 'div[style*=\"z-index: 3;\"]';\n\t\treturn this._map.getDiv().querySelector(selector) as HTMLDivElement;\n\t}\n\n\t/**\n\t * Converts longitude and latitude coordinates to pixel coordinates in the map container.\n\t * @param lng The longitude coordinate to project.\n\t * @param lat The latitude coordinate to project.\n\t * @returns An object with 'x' and 'y' properties representing the pixel coordinates within the map container.\n\t */\n\tproject(lng: number, lat: number) {\n\t\tif (!this._overlay) {\n\t\t\tthrow new Error(\"cannot get overlay\");\n\t\t}\n\n\t\tconst bounds = this._map.getBounds();\n\n\t\tif (bounds === undefined) {\n\t\t\tthrow new Error(\"cannot get bounds\");\n\t\t}\n\n\t\tconst projection = this._overlay.getProjection();\n\t\tif (projection === undefined) {\n\t\t\tthrow new Error(\"cannot get projection\");\n\t\t}\n\n\t\tconst point = projection.fromLatLngToContainerPixel(\n\t\t\tnew this._lib.LatLng(lat, lng),\n\t\t);\n\n\t\tif (point === null) {\n\t\t\tthrow new Error(\"cannot project coordinates\");\n\t\t}\n\n\t\treturn { x: point.x, y: point.y };\n\t}\n\n\t/**\n\t * Converts pixel coordinates in the map container to longitude and latitude coordinates.\n\t * @param x The x-coordinate in the map container to unproject.\n\t * @param y The y-coordinate in the map container to unproject.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude coordinates.\n\t */\n\tunproject(x: number, y: number) {\n\t\tif (!this._overlay) {\n\t\t\tthrow new Error(\"cannot get overlay\");\n\t\t}\n\n\t\tconst projection = this._overlay.getProjection();\n\t\tif (projection === undefined) {\n\t\t\tthrow new Error(\"cannot get projection\");\n\t\t}\n\n\t\tconst latLng = projection.fromContainerPixelToLatLng(\n\t\t\tnew this._lib.Point(x, y),\n\t\t);\n\n\t\tif (latLng === null) {\n\t\t\tthrow new Error(\"cannot unproject coordinates\");\n\t\t}\n\n\t\treturn { lng: latLng.lng(), lat: latLng.lat() };\n\t}\n\n\t/**\n\t * Sets the cursor style for the map container.\n\t * @param cursor The CSS cursor style to apply, or 'unset' to remove any previously applied cursor style.\n\t */\n\tsetCursor(cursor: Parameters[0]) {\n\t\tif (cursor === this._cursor) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this._cursorStyleSheet) {\n\t\t\tthis._cursorStyleSheet.remove();\n\t\t\tthis._cursorStyleSheet = undefined;\n\t\t}\n\n\t\tif (cursor !== \"unset\") {\n\t\t\t// TODO: We could cache these individually per cursor\n\n\t\t\tconst div = this._map.getDiv();\n\t\t\tconst styleDivSelector = `#${div.id} .gm-style > div`;\n\t\t\tconst styleDiv = document.querySelector(styleDivSelector);\n\n\t\t\tif (styleDiv) {\n\t\t\t\tstyleDiv.classList.add(\"terra-draw-google-maps\");\n\n\t\t\t\tconst style = document.createElement(\"style\");\n\t\t\t\tstyle.innerHTML = `.terra-draw-google-maps { cursor: ${cursor} !important; }`;\n\t\t\t\tdocument.getElementsByTagName(\"head\")[0].appendChild(style);\n\t\t\t\tthis._cursorStyleSheet = style;\n\t\t\t}\n\t\t}\n\n\t\tthis._cursor = cursor;\n\t}\n\n\t/**\n\t * Enables or disables the double-click to zoom functionality on the map.\n\t * @param enabled Set to true to enable double-click to zoom, or false to disable it.\n\t */\n\tsetDoubleClickToZoom(enabled: boolean) {\n\t\tif (enabled) {\n\t\t\tthis._map.setOptions({ disableDoubleClickZoom: false });\n\t\t} else {\n\t\t\tthis._map.setOptions({ disableDoubleClickZoom: true });\n\t\t}\n\t}\n\n\t/**\n\t * Enables or disables the draggable functionality of the map.\n\t * @param enabled Set to true to enable map dragging, or false to disable it.\n\t */\n\tsetDraggability(enabled: boolean) {\n\t\tthis._map.setOptions({ draggable: enabled });\n\t}\n\n\tprivate renderedFeatureIds: Set = new Set();\n\n\t/**\n\t * Renders GeoJSON features on the map using the provided styling configuration.\n\t * @param changes An object containing arrays of created, updated, and unchanged features to render.\n\t * @param styling An object mapping draw modes to feature styling functions\n\t */\n\trender(changes: TerraDrawChanges, styling: TerraDrawStylingFunction) {\n\t\tif (this._layers) {\n\t\t\tchanges.deletedIds.forEach((deletedId) => {\n\t\t\t\tconst featureToDelete = this._map.data.getFeatureById(deletedId);\n\t\t\t\tif (featureToDelete) {\n\t\t\t\t\tthis._map.data.remove(featureToDelete);\n\t\t\t\t\tthis.renderedFeatureIds.delete(deletedId);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tchanges.updated.forEach((updatedFeature) => {\n\t\t\t\tif (!updatedFeature || !updatedFeature.id) {\n\t\t\t\t\tthrow new Error(\"Feature is not valid\");\n\t\t\t\t}\n\n\t\t\t\tconst featureToUpdate = this._map.data.getFeatureById(\n\t\t\t\t\tupdatedFeature.id,\n\t\t\t\t);\n\n\t\t\t\tif (!featureToUpdate) {\n\t\t\t\t\tthrow new Error(\"Feature could not be found by Google Maps API\");\n\t\t\t\t}\n\n\t\t\t\t// Remove all keys\n\t\t\t\tfeatureToUpdate.forEachProperty((property, name) => {\n\t\t\t\t\tfeatureToUpdate.setProperty(name, undefined);\n\t\t\t\t});\n\n\t\t\t\t// Update all keys\n\t\t\t\tObject.keys(updatedFeature.properties).forEach((property) => {\n\t\t\t\t\tfeatureToUpdate.setProperty(\n\t\t\t\t\t\tproperty,\n\t\t\t\t\t\tupdatedFeature.properties[property],\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tswitch (updatedFeature.geometry.type) {\n\t\t\t\t\tcase \"Point\":\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tconst coordinates = updatedFeature.geometry.coordinates;\n\n\t\t\t\t\t\t\tfeatureToUpdate.setGeometry(\n\t\t\t\t\t\t\t\tnew this._lib.Data.Point(\n\t\t\t\t\t\t\t\t\tnew this._lib.LatLng(coordinates[1], coordinates[0]),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"LineString\":\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tconst coordinates = updatedFeature.geometry.coordinates;\n\n\t\t\t\t\t\t\tconst path = [];\n\t\t\t\t\t\t\tfor (let i = 0; i < coordinates.length; i++) {\n\t\t\t\t\t\t\t\tconst coordinate = coordinates[i];\n\t\t\t\t\t\t\t\tconst latLng = new this._lib.LatLng(\n\t\t\t\t\t\t\t\t\tcoordinate[1],\n\t\t\t\t\t\t\t\t\tcoordinate[0],\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tpath.push(latLng);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfeatureToUpdate.setGeometry(new this._lib.Data.LineString(path));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"Polygon\":\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tconst coordinates = updatedFeature.geometry.coordinates;\n\n\t\t\t\t\t\t\tconst paths = [];\n\t\t\t\t\t\t\tfor (let i = 0; i < coordinates.length; i++) {\n\t\t\t\t\t\t\t\tconst path = [];\n\t\t\t\t\t\t\t\tfor (let j = 0; j < coordinates[i].length; j++) {\n\t\t\t\t\t\t\t\t\tconst latLng = new this._lib.LatLng(\n\t\t\t\t\t\t\t\t\t\tcoordinates[i][j][1],\n\t\t\t\t\t\t\t\t\t\tcoordinates[i][j][0],\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tpath.push(latLng);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpaths.push(path);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfeatureToUpdate.setGeometry(new this._lib.Data.Polygon(paths));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Create new features\n\t\t\tchanges.created.forEach((createdFeature) => {\n\t\t\t\tthis.renderedFeatureIds.add(createdFeature.id as string);\n\t\t\t\tthis._map.data.addGeoJson(createdFeature);\n\t\t\t});\n\t\t}\n\n\t\tchanges.created.forEach((feature) => {\n\t\t\tthis.renderedFeatureIds.add(feature.id as string);\n\t\t});\n\n\t\tconst featureCollection = {\n\t\t\ttype: \"FeatureCollection\",\n\t\t\tfeatures: [...changes.created],\n\t\t} as GeoJsonObject;\n\n\t\tthis._map.data.addGeoJson(featureCollection);\n\n\t\tthis._map.data.setStyle((feature) => {\n\t\t\tconst mode = feature.getProperty(\"mode\");\n\t\t\tconst gmGeometry = feature.getGeometry();\n\t\t\tif (!gmGeometry) {\n\t\t\t\tthrow new Error(\"Google Maps geometry not found\");\n\t\t\t}\n\t\t\tconst type = gmGeometry.getType();\n\t\t\tconst properties: Record = {};\n\n\t\t\tfeature.forEachProperty((value, property) => {\n\t\t\t\tproperties[property] = value;\n\t\t\t});\n\n\t\t\tconst calculatedStyles = styling[mode]({\n\t\t\t\ttype: \"Feature\",\n\t\t\t\tgeometry: {\n\t\t\t\t\ttype: type as \"Point\" | \"LineString\" | \"Polygon\",\n\t\t\t\t\tcoordinates: [],\n\t\t\t\t},\n\t\t\t\tproperties,\n\t\t\t});\n\n\t\t\tswitch (type) {\n\t\t\t\tcase \"Point\":\n\t\t\t\t\tconst path = this.circlePath(0, 0, calculatedStyles.pointWidth);\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tclickable: false,\n\t\t\t\t\t\ticon: {\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tfillColor: calculatedStyles.pointColor,\n\t\t\t\t\t\t\tfillOpacity: 1,\n\t\t\t\t\t\t\tstrokeColor: calculatedStyles.pointOutlineColor,\n\t\t\t\t\t\t\tstrokeWeight: calculatedStyles.pointOutlineWidth,\n\t\t\t\t\t\t\trotation: 0,\n\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\n\t\t\t\tcase \"LineString\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstrokeColor: calculatedStyles.lineStringColor,\n\t\t\t\t\t\tstrokeWeight: calculatedStyles.lineStringWidth,\n\t\t\t\t\t};\n\t\t\t\tcase \"Polygon\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstrokeColor: calculatedStyles.polygonOutlineColor,\n\t\t\t\t\t\tstrokeWeight: calculatedStyles.polygonOutlineWidth,\n\t\t\t\t\t\tfillOpacity: calculatedStyles.polygonFillOpacity,\n\t\t\t\t\t\tfillColor: calculatedStyles.polygonFillColor,\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\tthrow Error(\"Unknown feature type\");\n\t\t});\n\t}\n\n\tprivate clearLayers() {\n\t\tif (this._layers) {\n\t\t\tthis._map.data.forEach((feature) => {\n\t\t\t\tconst id = feature.getId() as string;\n\t\t\t\tconst hasFeature = this.renderedFeatureIds.has(id);\n\t\t\t\tif (hasFeature) {\n\t\t\t\t\tthis._map.data.remove(feature);\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.renderedFeatureIds = new Set();\n\t\t}\n\t}\n\n\t/**\n\t * Clears the map and store of all rendered data layers\n\t * @returns void\n\t * */\n\tpublic clear() {\n\t\tif (this._currentModeCallbacks) {\n\t\t\t// Clean up state first\n\t\t\tthis._currentModeCallbacks.onClear();\n\n\t\t\t// Then clean up rendering\n\t\t\tthis.clearLayers();\n\t\t}\n\t}\n\n\tpublic getCoordinatePrecision(): number {\n\t\t// TODO: It seems this shouldn't be necessary as extends BaseAdapter which as this method\n\t\treturn super.getCoordinatePrecision();\n\t}\n}\n","import {\n\tTerraDrawChanges,\n\tSetCursor,\n\tTerraDrawStylingFunction,\n\tTerraDrawCallbacks,\n} from \"../common\";\nimport L from \"leaflet\";\nimport { GeoJSONStoreFeatures } from \"../store/store\";\nimport { BaseAdapterConfig, TerraDrawBaseAdapter } from \"./common/base.adapter\";\n\nexport class TerraDrawLeafletAdapter extends TerraDrawBaseAdapter {\n\tconstructor(\n\t\tconfig: {\n\t\t\tlib: typeof L;\n\t\t\tmap: L.Map;\n\t\t} & BaseAdapterConfig,\n\t) {\n\t\tsuper(config);\n\n\t\tthis._lib = config.lib;\n\t\tthis._map = config.map;\n\t\tthis._container = this._map.getContainer();\n\t}\n\n\tprivate _lib: typeof L;\n\tprivate _map: L.Map;\n\tprivate _panes: Record = {};\n\tprivate _container: HTMLElement;\n\tprivate _layers: Record> = {};\n\n\t/**\n\t * Creates a pane and its associated style sheet\n\t * @param pane - The pane name\n\t * @param zIndex - The zIndex value for the pane\n\t * @returns The created style element\n\t */\n\tprivate createPaneStyleSheet(pane: string, zIndex: number) {\n\t\tconst baseZIndex = 600;\n\t\tconst style = document.createElement(\"style\");\n\t\tstyle.innerHTML = `.leaflet-${pane}-pane {z-index: ${\n\t\t\tzIndex + baseZIndex\n\t\t};}`;\n\t\tdocument.getElementsByTagName(\"head\")[0].appendChild(style);\n\t\tthis._map.createPane(pane);\n\t\treturn style;\n\t}\n\n\t/**\n\t * Clears the panes created by the adapter\n\t * @returns void\n\t * */\n\tprivate clearPanes() {\n\t\tObject.values(this._panes).forEach((pane) => {\n\t\t\tif (pane) {\n\t\t\t\tpane.remove();\n\t\t\t}\n\t\t});\n\t\tthis._panes = {};\n\t}\n\n\t/**\n\t * Clears the leaflet layers created by the adapter\n\t * @returns void\n\t * */\n\tprivate clearLayers() {\n\t\tObject.values(this._layers).forEach((layer) => {\n\t\t\tthis._map.removeLayer(layer);\n\t\t});\n\t\tthis._layers = {};\n\t}\n\n\t/**\n\t * Styles a GeoJSON layer based on the styling function\n\t * @param styling - The styling function\n\t * */\n\tprivate styleGeoJSONLayer(\n\t\tstyling: TerraDrawStylingFunction,\n\t): L.GeoJSONOptions {\n\t\treturn {\n\t\t\t// Style points - convert markers to circle markers\n\t\t\tpointToLayer: (\n\t\t\t\tfeature: GeoJSONStoreFeatures,\n\t\t\t\tlatlng: L.LatLngExpression,\n\t\t\t) => {\n\t\t\t\tif (!feature.properties) {\n\t\t\t\t\tthrow new Error(\"Feature has no properties\");\n\t\t\t\t}\n\t\t\t\tif (typeof feature.properties.mode !== \"string\") {\n\t\t\t\t\tthrow new Error(\"Feature mode is not a string\");\n\t\t\t\t}\n\n\t\t\t\tconst mode = feature.properties.mode;\n\t\t\t\tconst modeStyle = styling[mode];\n\t\t\t\tconst featureStyles = modeStyle(feature);\n\t\t\t\tconst paneId = String(featureStyles.zIndex);\n\t\t\t\tconst pane = this._panes[paneId];\n\n\t\t\t\tif (!pane) {\n\t\t\t\t\tthis._panes[paneId] = this.createPaneStyleSheet(\n\t\t\t\t\t\tpaneId,\n\t\t\t\t\t\tfeatureStyles.zIndex,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst styles = {\n\t\t\t\t\tradius: featureStyles.pointWidth,\n\t\t\t\t\tstroke: featureStyles.pointOutlineWidth || false,\n\t\t\t\t\tcolor: featureStyles.pointOutlineColor,\n\t\t\t\t\tweight: featureStyles.pointOutlineWidth,\n\t\t\t\t\tfillOpacity: 0.8,\n\t\t\t\t\tfillColor: featureStyles.pointColor,\n\t\t\t\t\tpane: paneId,\n\t\t\t\t\tinteractive: false, // Removes mouse hover cursor styles\n\t\t\t\t} as L.CircleMarkerOptions;\n\n\t\t\t\tconst marker = this._lib.circleMarker(latlng, styles);\n\n\t\t\t\treturn marker;\n\t\t\t},\n\n\t\t\t// Style LineStrings and Polygons\n\t\t\tstyle: (_feature) => {\n\t\t\t\tif (!_feature || !_feature.properties) {\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\n\t\t\t\tconst feature = _feature as GeoJSONStoreFeatures;\n\n\t\t\t\tconst mode = feature.properties.mode as string;\n\t\t\t\tconst modeStyle = styling[mode];\n\t\t\t\tconst featureStyles = modeStyle(feature);\n\t\t\t\tconst paneId = String(featureStyles.zIndex);\n\t\t\t\tconst pane = this._panes[paneId];\n\n\t\t\t\tif (!pane) {\n\t\t\t\t\tthis._panes[paneId] = this.createPaneStyleSheet(\n\t\t\t\t\t\tpaneId,\n\t\t\t\t\t\tfeatureStyles.zIndex,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (feature.geometry.type === \"LineString\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tinteractive: false, // Removes mouse hover cursor styles\n\t\t\t\t\t\tcolor: featureStyles.lineStringColor,\n\t\t\t\t\t\tweight: featureStyles.lineStringWidth,\n\t\t\t\t\t\tpane: paneId,\n\t\t\t\t\t};\n\t\t\t\t} else if (feature.geometry.type === \"Polygon\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tinteractive: false, // Removes mouse hover cursor styles\n\t\t\t\t\t\tfillOpacity: featureStyles.polygonFillOpacity,\n\t\t\t\t\t\tfillColor: featureStyles.polygonFillColor,\n\t\t\t\t\t\tweight: featureStyles.polygonOutlineWidth,\n\t\t\t\t\t\tstroke: true,\n\t\t\t\t\t\tcolor: featureStyles.polygonFillColor,\n\t\t\t\t\t\tpane: paneId,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn {};\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Returns the longitude and latitude coordinates from a given PointerEvent on the map.\n\t * @param event The PointerEvent or MouseEvent containing the screen coordinates of the pointer.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude, or null if the conversion is not possible.\n\t */\n\tpublic getLngLatFromEvent(event: PointerEvent | MouseEvent) {\n\t\tconst { containerX: x, containerY: y } =\n\t\t\tthis.getMapElementXYPosition(event);\n\n\t\tconst point = { x, y } as L.Point;\n\n\t\t// If is not valid point we don't want to convert\n\t\tif (\n\t\t\tpoint.x === null ||\n\t\t\tisNaN(point.x) ||\n\t\t\tpoint.y === null ||\n\t\t\tisNaN(point.y)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst latLng = this._map.containerPointToLatLng(point);\n\t\tif (\n\t\t\tlatLng.lng === null ||\n\t\t\tisNaN(latLng.lng) ||\n\t\t\tlatLng.lat === null ||\n\t\t\tisNaN(latLng.lat)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { lng: latLng.lng, lat: latLng.lat };\n\t}\n\n\t/**\n\t * Retrieves the HTML element of the Leaflet element that handles interaction events\n\t * @returns The HTMLElement representing the map container.\n\t */\n\tpublic getMapEventElement() {\n\t\treturn this._container;\n\t}\n\n\t/**\n\t * Enables or disables the draggable functionality of the map.\n\t * @param enabled Set to true to enable map dragging, or false to disable it.\n\t */\n\tpublic setDraggability(enabled: boolean) {\n\t\tif (enabled) {\n\t\t\tthis._map.dragging.enable();\n\t\t} else {\n\t\t\tthis._map.dragging.disable();\n\t\t}\n\t}\n\n\t/**\n\t * Converts longitude and latitude coordinates to pixel coordinates in the map container.\n\t * @param lng The longitude coordinate to project.\n\t * @param lat The latitude coordinate to project.\n\t * @returns An object with 'x' and 'y' properties representing the pixel coordinates within the map container.\n\t */\n\tpublic project(lng: number, lat: number) {\n\t\tconst { x, y } = this._map.latLngToContainerPoint({ lng, lat });\n\t\treturn { x, y };\n\t}\n\n\t/**\n\t * Converts pixel coordinates in the map container to longitude and latitude coordinates.\n\t * @param x The x-coordinate in the map container to unproject.\n\t * @param y The y-coordinate in the map container to unproject.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude coordinates.\n\t */\n\tpublic unproject(x: number, y: number) {\n\t\tconst { lng, lat } = this._map.containerPointToLatLng({\n\t\t\tx,\n\t\t\ty,\n\t\t} as L.PointExpression);\n\t\treturn { lng, lat };\n\t}\n\n\t/**\n\t * Sets the cursor style for the map container.\n\t * @param cursor The CSS cursor style to apply, or 'unset' to remove any previously applied cursor style.\n\t */\n\tpublic setCursor(cursor: Parameters[0]) {\n\t\tif (cursor === \"unset\") {\n\t\t\tthis.getMapEventElement().style.removeProperty(\"cursor\");\n\t\t} else {\n\t\t\tthis.getMapEventElement().style.cursor = cursor;\n\t\t}\n\t}\n\n\t/**\n\t * Enables or disables the double-click to zoom functionality on the map.\n\t * @param enabled Set to true to enable double-click to zoom, or false to disable it.\n\t */\n\tpublic setDoubleClickToZoom(enabled: boolean) {\n\t\tif (enabled) {\n\t\t\tthis._map.doubleClickZoom.enable();\n\t\t} else {\n\t\t\tthis._map.doubleClickZoom.disable();\n\t\t}\n\t}\n\n\t/**\n\t * Renders GeoJSON features on the map using the provided styling configuration.\n\t * @param changes An object containing arrays of created, updated, and unchanged features to render.\n\t * @param styling An object mapping draw modes to feature styling functions\n\t */\n\tpublic render(changes: TerraDrawChanges, styling: TerraDrawStylingFunction) {\n\t\tchanges.created.forEach((created) => {\n\t\t\tthis._layers[created.id as string] = this._lib.geoJSON(\n\t\t\t\tcreated,\n\t\t\t\tthis.styleGeoJSONLayer(styling),\n\t\t\t);\n\t\t\tthis._map.addLayer(this._layers[created.id as string]);\n\t\t});\n\n\t\tchanges.deletedIds.forEach((deleted) => {\n\t\t\tthis._map.removeLayer(this._layers[deleted]);\n\t\t});\n\n\t\tchanges.updated.forEach((updated) => {\n\t\t\tthis._map.removeLayer(this._layers[updated.id as string]);\n\t\t\tthis._layers[updated.id as string] = this._lib.geoJSON(\n\t\t\t\tupdated,\n\t\t\t\tthis.styleGeoJSONLayer(styling),\n\t\t\t);\n\t\t\tthis._map.addLayer(this._layers[updated.id as string]);\n\t\t});\n\t}\n\n\t/**\n\t * Clears the map and store of all rendered data layers\n\t * @returns void\n\t * */\n\tpublic clear() {\n\t\tif (this._currentModeCallbacks) {\n\t\t\t// Clear up state first\n\t\t\tthis._currentModeCallbacks.onClear();\n\n\t\t\t// Then clean up rendering\n\t\t\tthis.clearLayers();\n\t\t\tthis.clearPanes();\n\t\t}\n\t}\n\n\tpublic register(callbacks: TerraDrawCallbacks) {\n\t\tsuper.register(callbacks);\n\n\t\tthis._currentModeCallbacks &&\n\t\t\tthis._currentModeCallbacks.onReady &&\n\t\t\tthis._currentModeCallbacks.onReady();\n\t}\n\n\tpublic getCoordinatePrecision(): number {\n\t\t// TODO: It seems this shouldn't be necessary as extends BaseAdapter which as this method\n\t\treturn super.getCoordinatePrecision();\n\t}\n\n\tpublic unregister(): void {\n\t\t// TODO: It seems this shouldn't be necessary as extends BaseAdapter which as this method\n\t\treturn super.unregister();\n\t}\n}\n","import {\n\tTerraDrawChanges,\n\tSetCursor,\n\tTerraDrawStylingFunction,\n\tTerraDrawCallbacks,\n} from \"../common\";\nimport { Feature, LineString, Point, Polygon } from \"geojson\";\nimport mapboxgl, {\n\tCircleLayer,\n\tFillLayer,\n\tLineLayer,\n\tPointLike,\n} from \"mapbox-gl\";\nimport { GeoJSONStoreFeatures, GeoJSONStoreGeometries } from \"../store/store\";\nimport { BaseAdapterConfig, TerraDrawBaseAdapter } from \"./common/base.adapter\";\n\nexport class TerraDrawMapboxGLAdapter extends TerraDrawBaseAdapter {\n\tconstructor(config: { map: mapboxgl.Map } & BaseAdapterConfig) {\n\t\tsuper(config);\n\n\t\tthis._map = config.map;\n\t\tthis._container = this._map.getContainer();\n\t}\n\n\tprivate _nextRender: any;\n\tprivate _map: mapboxgl.Map;\n\tprivate _container: HTMLElement;\n\tprivate _rendered = false;\n\n\t/**\n\t * Clears the map of rendered layers and sources\n\t * @returns void\n\t * */\n\tprivate clearLayers() {\n\t\tif (this._rendered) {\n\t\t\tconst geometryTypes = [\"point\", \"linestring\", \"polygon\"] as const;\n\t\t\tgeometryTypes.forEach((geometryKey) => {\n\t\t\t\tconst id = `td-${geometryKey.toLowerCase()}`;\n\t\t\t\tthis._map.removeLayer(id);\n\n\t\t\t\t// Special case for polygons as it has another id for the outline\n\t\t\t\t// that we need to make sure we remove\n\t\t\t\tif (geometryKey === \"polygon\") {\n\t\t\t\t\tthis._map.removeLayer(id + \"-outline\");\n\t\t\t\t}\n\t\t\t\tthis._map.removeSource(id);\n\t\t\t});\n\n\t\t\tthis._rendered = false;\n\n\t\t\t// TODO: This is necessary to prevent render artifacts, perhaps there is a nicer solution?\n\t\t\tif (this._nextRender) {\n\t\t\t\tcancelAnimationFrame(this._nextRender);\n\t\t\t\tthis._nextRender = undefined;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _addGeoJSONSource(id: string, features: Feature[]) {\n\t\tthis._map.addSource(id, {\n\t\t\ttype: \"geojson\",\n\t\t\tdata: {\n\t\t\t\ttype: \"FeatureCollection\",\n\t\t\t\tfeatures: features,\n\t\t\t},\n\t\t\ttolerance: 0,\n\t\t});\n\t}\n\n\tprivate _addFillLayer(id: string) {\n\t\treturn this._map.addLayer({\n\t\t\tid,\n\t\t\tsource: id,\n\t\t\ttype: \"fill\",\n\t\t\t// No need for filters as style is driven by properties\n\t\t\tpaint: {\n\t\t\t\t\"fill-color\": [\"get\", \"polygonFillColor\"],\n\t\t\t\t\"fill-opacity\": [\"get\", \"polygonFillOpacity\"],\n\t\t\t},\n\t\t} as FillLayer);\n\t}\n\n\tprivate _addFillOutlineLayer(id: string, beneath?: string) {\n\t\tconst layer = this._map.addLayer({\n\t\t\tid: id + \"-outline\",\n\t\t\tsource: id,\n\t\t\ttype: \"line\",\n\t\t\t// No need for filters as style is driven by properties\n\t\t\tpaint: {\n\t\t\t\t\"line-width\": [\"get\", \"polygonOutlineWidth\"],\n\t\t\t\t\"line-color\": [\"get\", \"polygonOutlineColor\"],\n\t\t\t},\n\t\t} as LineLayer);\n\n\t\tif (beneath) {\n\t\t\tthis._map.moveLayer(id, beneath);\n\t\t}\n\n\t\treturn layer;\n\t}\n\n\tprivate _addLineLayer(id: string, beneath?: string) {\n\t\tconst layer = this._map.addLayer({\n\t\t\tid,\n\t\t\tsource: id,\n\t\t\ttype: \"line\",\n\t\t\t// No need for filters as style is driven by properties\n\t\t\tpaint: {\n\t\t\t\t\"line-width\": [\"get\", \"lineStringWidth\"],\n\t\t\t\t\"line-color\": [\"get\", \"lineStringColor\"],\n\t\t\t},\n\t\t} as LineLayer);\n\n\t\tif (beneath) {\n\t\t\tthis._map.moveLayer(id, beneath);\n\t\t}\n\n\t\treturn layer;\n\t}\n\n\tprivate _addPointLayer(id: string, beneath?: string) {\n\t\tconst layer = this._map.addLayer({\n\t\t\tid,\n\t\t\tsource: id,\n\t\t\ttype: \"circle\",\n\t\t\t// No need for filters as style is driven by properties\n\t\t\tpaint: {\n\t\t\t\t\"circle-stroke-color\": [\"get\", \"pointOutlineColor\"],\n\t\t\t\t\"circle-stroke-width\": [\"get\", \"pointOutlineWidth\"],\n\t\t\t\t\"circle-radius\": [\"get\", \"pointWidth\"],\n\t\t\t\t\"circle-color\": [\"get\", \"pointColor\"],\n\t\t\t},\n\t\t} as CircleLayer);\n\t\tif (beneath) {\n\t\t\tthis._map.moveLayer(id, beneath);\n\t\t}\n\t\treturn layer;\n\t}\n\n\tprivate _addLayer(\n\t\tid: string,\n\t\tfeatureType: \"Point\" | \"LineString\" | \"Polygon\",\n\t\tbeneath?: string,\n\t) {\n\t\tif (featureType === \"Point\") {\n\t\t\tthis._addPointLayer(id, beneath);\n\t\t}\n\t\tif (featureType === \"LineString\") {\n\t\t\tthis._addLineLayer(id, beneath);\n\t\t}\n\t\tif (featureType === \"Polygon\") {\n\t\t\tthis._addFillLayer(id);\n\t\t\tthis._addFillOutlineLayer(id, beneath);\n\t\t}\n\t}\n\n\tprivate _addGeoJSONLayer(\n\t\tfeatureType: Feature[\"geometry\"][\"type\"],\n\t\tfeatures: Feature[],\n\t) {\n\t\tconst id = `td-${featureType.toLowerCase()}`;\n\t\tthis._addGeoJSONSource(id, features);\n\t\tthis._addLayer(id, featureType);\n\n\t\treturn id;\n\t}\n\n\tprivate _setGeoJSONLayerData(\n\t\tfeatureType: Feature[\"geometry\"][\"type\"],\n\t\tfeatures: Feature[],\n\t) {\n\t\tconst id = `td-${featureType.toLowerCase()}`;\n\t\t(this._map.getSource(id) as any).setData({\n\t\t\ttype: \"FeatureCollection\",\n\t\t\tfeatures: features,\n\t\t});\n\t\treturn id;\n\t}\n\n\tprivate getEmptyGeometries(): {\n\t\tpoints: GeoJSONStoreFeatures[];\n\t\tlinestrings: GeoJSONStoreFeatures[];\n\t\tpolygons: GeoJSONStoreFeatures[];\n\t} {\n\t\treturn {\n\t\t\tpoints: [],\n\t\t\tlinestrings: [],\n\t\t\tpolygons: [],\n\t\t};\n\t}\n\n\tprivate changedIds: {\n\t\tdeletion: boolean;\n\t\tpoints: boolean;\n\t\tlinestrings: boolean;\n\t\tpolygons: boolean;\n\t\tstyling: boolean;\n\t} = {\n\t\tdeletion: false,\n\t\tpoints: false,\n\t\tlinestrings: false,\n\t\tpolygons: false,\n\t\tstyling: false,\n\t};\n\n\tprivate updateChangedIds(changes: TerraDrawChanges) {\n\t\t[...changes.updated, ...changes.created].forEach((feature) => {\n\t\t\tif (feature.geometry.type === \"Point\") {\n\t\t\t\tthis.changedIds.points = true;\n\t\t\t} else if (feature.geometry.type === \"LineString\") {\n\t\t\t\tthis.changedIds.linestrings = true;\n\t\t\t} else if (feature.geometry.type === \"Polygon\") {\n\t\t\t\tthis.changedIds.polygons = true;\n\t\t\t}\n\t\t});\n\n\t\tif (changes.deletedIds.length > 0) {\n\t\t\tthis.changedIds.deletion = true;\n\t\t}\n\n\t\tif (\n\t\t\tchanges.created.length === 0 &&\n\t\t\tchanges.updated.length === 0 &&\n\t\t\tchanges.deletedIds.length === 0\n\t\t) {\n\t\t\tthis.changedIds.styling = true;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the longitude and latitude coordinates from a given PointerEvent on the map.\n\t * @param event The PointerEvent or MouseEvent containing the screen coordinates of the pointer.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude, or null if the conversion is not possible.\n\t */\n\tpublic getLngLatFromEvent(event: PointerEvent | MouseEvent) {\n\t\tconst { left, top } = this._container.getBoundingClientRect();\n\t\tconst x = event.clientX - left;\n\t\tconst y = event.clientY - top;\n\n\t\treturn this.unproject(x, y);\n\t}\n\n\t/**\n\t *Retrieves the HTML element of the Mapbox element that handles interaction events\n\t * @returns The HTMLElement representing the map container.\n\t */\n\tpublic getMapEventElement() {\n\t\treturn this._map.getCanvas();\n\t}\n\n\t/**\n\t * Enables or disables the draggable functionality of the map.\n\t * @param enabled Set to true to enable map dragging, or false to disable it.\n\t */\n\tpublic setDraggability(enabled: boolean) {\n\t\tif (enabled) {\n\t\t\t// Mapbox GL has both drag rotation and drag panning interactions\n\t\t\t// hence having to enable/disable both\n\t\t\tthis._map.dragRotate.enable();\n\t\t\tthis._map.dragPan.enable();\n\t\t} else {\n\t\t\tthis._map.dragRotate.disable();\n\t\t\tthis._map.dragPan.disable();\n\t\t}\n\t}\n\n\t/**\n\t * Converts longitude and latitude coordinates to pixel coordinates in the map container.\n\t * @param lng The longitude coordinate to project.\n\t * @param lat The latitude coordinate to project.\n\t * @returns An object with 'x' and 'y' properties representing the pixel coordinates within the map container.\n\t */\n\tpublic project(lng: number, lat: number) {\n\t\tconst { x, y } = this._map.project({ lng, lat });\n\t\treturn { x, y };\n\t}\n\n\t/**\n\t * Converts pixel coordinates in the map container to longitude and latitude coordinates.\n\t * @param x The x-coordinate in the map container to unproject.\n\t * @param y The y-coordinate in the map container to unproject.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude coordinates.\n\t */\n\tpublic unproject(x: number, y: number) {\n\t\tconst { lng, lat } = this._map.unproject({ x, y } as PointLike);\n\t\treturn { lng, lat };\n\t}\n\n\t/**\n\t * Sets the cursor style for the map container.\n\t * @param cursor The CSS cursor style to apply, or 'unset' to remove any previously applied cursor style.\n\t */\n\tpublic setCursor(cursor: Parameters[0]) {\n\t\tconst canvas = this._map.getCanvas();\n\t\tif (cursor === \"unset\") {\n\t\t\tcanvas.style.removeProperty(\"cursor\");\n\t\t} else {\n\t\t\tcanvas.style.cursor = cursor;\n\t\t}\n\t}\n\n\t/**\n\t * Enables or disables the double-click to zoom functionality on the map.\n\t * @param enabled Set to true to enable double-click to zoom, or false to disable it.\n\t */\n\tpublic setDoubleClickToZoom(enabled: boolean) {\n\t\tif (enabled) {\n\t\t\tthis._map.doubleClickZoom.enable();\n\t\t} else {\n\t\t\tthis._map.doubleClickZoom.disable();\n\t\t}\n\t}\n\n\t/**\n\t * Renders GeoJSON features on the map using the provided styling configuration.\n\t * @param changes An object containing arrays of created, updated, and unchanged features to render.\n\t * @param styling An object mapping draw modes to feature styling functions\n\t */\n\tpublic render(changes: TerraDrawChanges, styling: TerraDrawStylingFunction) {\n\t\tthis.updateChangedIds(changes);\n\n\t\tif (this._nextRender) {\n\t\t\tcancelAnimationFrame(this._nextRender);\n\t\t}\n\n\t\t// Because Mapbox GL makes us pass in a full re-render of alll the features\n\t\t// we can do debounce rendering to only render the last render in a given\n\t\t// frame bucket (16ms)\n\n\t\tthis._nextRender = requestAnimationFrame(() => {\n\t\t\t// Get a map of the changed feature IDs by geometry type\n\t\t\t// We use this to determine which MB layers need to be updated\n\n\t\t\tconst features = [\n\t\t\t\t...changes.created,\n\t\t\t\t...changes.updated,\n\t\t\t\t...changes.unchanged,\n\t\t\t];\n\n\t\t\tconst geometryFeatures = this.getEmptyGeometries();\n\n\t\t\tfor (let i = 0; i < features.length; i++) {\n\t\t\t\tconst feature = features[i];\n\n\t\t\t\tObject.keys(styling).forEach((mode) => {\n\t\t\t\t\tconst { properties } = feature;\n\n\t\t\t\t\tif (properties.mode !== mode) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst styles = styling[mode](feature);\n\n\t\t\t\t\tif (feature.geometry.type === \"Point\") {\n\t\t\t\t\t\tproperties.pointColor = styles.pointColor;\n\t\t\t\t\t\tproperties.pointOutlineColor = styles.pointOutlineColor;\n\t\t\t\t\t\tproperties.pointOutlineWidth = styles.pointOutlineWidth;\n\t\t\t\t\t\tproperties.pointWidth = styles.pointWidth;\n\t\t\t\t\t\tgeometryFeatures.points.push(feature);\n\t\t\t\t\t} else if (feature.geometry.type === \"LineString\") {\n\t\t\t\t\t\tproperties.lineStringColor = styles.lineStringColor;\n\t\t\t\t\t\tproperties.lineStringWidth = styles.lineStringWidth;\n\t\t\t\t\t\tgeometryFeatures.linestrings.push(feature);\n\t\t\t\t\t} else if (feature.geometry.type === \"Polygon\") {\n\t\t\t\t\t\tproperties.polygonFillColor = styles.polygonFillColor;\n\t\t\t\t\t\tproperties.polygonFillOpacity = styles.polygonFillOpacity;\n\t\t\t\t\t\tproperties.polygonOutlineColor = styles.polygonOutlineColor;\n\t\t\t\t\t\tproperties.polygonOutlineWidth = styles.polygonOutlineWidth;\n\t\t\t\t\t\tgeometryFeatures.polygons.push(feature);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst { points, linestrings, polygons } = geometryFeatures;\n\n\t\t\tif (!this._rendered) {\n\t\t\t\tconst pointId = this._addGeoJSONLayer(\n\t\t\t\t\t\"Point\",\n\t\t\t\t\tpoints as Feature[],\n\t\t\t\t);\n\t\t\t\tthis._addGeoJSONLayer(\n\t\t\t\t\t\"LineString\",\n\t\t\t\t\tlinestrings as Feature[],\n\t\t\t\t);\n\t\t\t\tthis._addGeoJSONLayer(\n\t\t\t\t\t\"Polygon\",\n\t\t\t\t\tpolygons as Feature[],\n\t\t\t\t);\n\t\t\t\tthis._rendered = true;\n\n\t\t\t\t// Ensure selection/mid points are rendered on top\n\t\t\t\tpointId && this._map.moveLayer(pointId);\n\t\t\t} else {\n\t\t\t\t// If deletion occured we always have to update all layers\n\t\t\t\t// as we don't know the type (TODO: perhaps we could pass that back?)\n\t\t\t\tconst deletionOccured = this.changedIds.deletion;\n\t\t\t\tconst styleUpdatedOccured = this.changedIds.styling;\n\t\t\t\tconst forceUpdate = deletionOccured || styleUpdatedOccured;\n\n\t\t\t\t// Determine if we need to update each layer by geometry type\n\t\t\t\tconst updatePoints = forceUpdate || this.changedIds.points;\n\t\t\t\tconst updateLineStrings = forceUpdate || this.changedIds.linestrings;\n\t\t\t\tconst updatedPolygon = forceUpdate || this.changedIds.polygons;\n\n\t\t\t\tlet pointId;\n\t\t\t\tif (updatePoints) {\n\t\t\t\t\tpointId = this._setGeoJSONLayerData(\n\t\t\t\t\t\t\"Point\",\n\t\t\t\t\t\tpoints as Feature[],\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (updateLineStrings) {\n\t\t\t\t\tthis._setGeoJSONLayerData(\n\t\t\t\t\t\t\"LineString\",\n\t\t\t\t\t\tlinestrings as Feature[],\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (updatedPolygon) {\n\t\t\t\t\tthis._setGeoJSONLayerData(\n\t\t\t\t\t\t\"Polygon\",\n\t\t\t\t\t\tpolygons as Feature[],\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// TODO: This logic could be better - I think this will render the selection points above user\n\t\t\t\t// defined layers outside of TerraDraw which is perhaps unideal\n\n\t\t\t\t// Ensure selection/mid points are rendered on top\n\t\t\t\tpointId && this._map.moveLayer(pointId);\n\t\t\t}\n\n\t\t\t// Reset changed ids\n\t\t\tthis.changedIds = {\n\t\t\t\tpoints: false,\n\t\t\t\tlinestrings: false,\n\t\t\t\tpolygons: false,\n\t\t\t\tdeletion: false,\n\t\t\t\tstyling: false,\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * Clears the map and store of all rendered data layers\n\t * @returns void\n\t * */\n\tpublic clear() {\n\t\tif (this._currentModeCallbacks) {\n\t\t\t// Clear up state first\n\t\t\tthis._currentModeCallbacks.onClear();\n\n\t\t\t// Then clean up rendering\n\t\t\tthis.clearLayers();\n\t\t}\n\t}\n\n\tpublic getCoordinatePrecision(): number {\n\t\treturn super.getCoordinatePrecision();\n\t}\n\n\tpublic unregister(): void {\n\t\t// TODO: It seems this shouldn't be necessary as extends BaseAdapter which as this method\n\t\treturn super.unregister();\n\t}\n\n\tpublic register(callbacks: TerraDrawCallbacks) {\n\t\tsuper.register(callbacks);\n\t\tthis._currentModeCallbacks &&\n\t\t\tthis._currentModeCallbacks.onReady &&\n\t\t\tthis._currentModeCallbacks.onReady();\n\t}\n}\n","import {\n\tTerraDrawChanges,\n\tSetCursor,\n\tTerraDrawStylingFunction,\n\tTerraDrawCallbacks,\n} from \"../common\";\nimport { Map } from \"maplibre-gl\";\nimport { TerraDrawMapboxGLAdapter } from \"./mapbox-gl.adapter\";\nimport { BaseAdapterConfig, TerraDrawBaseAdapter } from \"./common/base.adapter\";\n\nexport class TerraDrawMapLibreGLAdapter extends TerraDrawBaseAdapter {\n\tprivate mapboxglAdapter: TerraDrawMapboxGLAdapter;\n\n\tconstructor(config: { map: Map } & BaseAdapterConfig) {\n\t\tsuper(config);\n\n\t\t// At the moment the APIs of MapboxGL and MapLibre are so compatible that\n\t\t// there is not need to bother completely reimplementing the internals of the adapter.\n\t\t// This may change over time and gives us a shell to allow for rewriting the internals\n\t\t// of the adapter should the MapboxGL and MapbLibre APIs diverge in the instances where\n\t\t// we rely on them.\n\t\tthis.mapboxglAdapter = new TerraDrawMapboxGLAdapter(\n\t\t\tconfig as {\n\t\t\t\tmap: any; //\n\t\t\t\tcoordinatePrecision: number;\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic register(callbacks: TerraDrawCallbacks): void {\n\t\tthis.mapboxglAdapter.register(callbacks);\n\t}\n\n\tpublic unregister(): void {\n\t\tthis.mapboxglAdapter.unregister();\n\t}\n\n\tpublic getCoordinatePrecision(): number {\n\t\treturn this.mapboxglAdapter.getCoordinatePrecision();\n\t}\n\n\t/**\n\t * Returns the longitude and latitude coordinates from a given PointerEvent on the map.\n\t * @param event The PointerEvent or MouseEvent containing the screen coordinates of the pointer.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude, or null if the conversion is not possible.\n\t */\n\tpublic getLngLatFromEvent(event: PointerEvent | MouseEvent) {\n\t\treturn this.mapboxglAdapter.getLngLatFromEvent(event);\n\t}\n\n\t/**\n\t * Retrieves the HTML element of the MapLibre element that handles interaction events\n\t * @returns The HTMLElement representing the map container.\n\t */\n\tpublic getMapEventElement() {\n\t\treturn this.mapboxglAdapter.getMapEventElement();\n\t}\n\n\t/**\n\t * Enables or disables the draggable functionality of the map.\n\t * @param enabled Set to true to enable map dragging, or false to disable it.\n\t */\n\tpublic setDraggability(enabled: boolean) {\n\t\tthis.mapboxglAdapter.setDraggability(enabled);\n\t}\n\n\t/**\n\t * Converts longitude and latitude coordinates to pixel coordinates in the map container.\n\t * @param lng The longitude coordinate to project.\n\t * @param lat The latitude coordinate to project.\n\t * @returns An object with 'x' and 'y' properties representing the pixel coordinates within the map container.\n\t */\n\tpublic project(lng: number, lat: number) {\n\t\treturn this.mapboxglAdapter.project(lng, lat);\n\t}\n\n\t/**\n\t * Converts pixel coordinates in the map container to longitude and latitude coordinates.\n\t * @param x The x-coordinate in the map container to unproject.\n\t * @param y The y-coordinate in the map container to unproject.\n\t * @returns An object with 'lng' and 'lat' properties representing the longitude and latitude coordinates.\n\t */\n\tpublic unproject(x: number, y: number) {\n\t\treturn this.mapboxglAdapter.unproject(x, y);\n\t}\n\n\t/**\n\t * Sets the cursor style for the map container.\n\t * @param cursor The CSS cursor style to apply, or 'unset' to remove any previously applied cursor style.\n\t */\n\tpublic setCursor(style: Parameters[0]) {\n\t\tthis.mapboxglAdapter.setCursor(style);\n\t}\n\n\t/**\n\t * Enables or disables the double-click to zoom functionality on the map.\n\t * @param enabled Set to true to enable double-click to zoom, or false to disable it.\n\t */\n\tpublic setDoubleClickToZoom(enabled: boolean) {\n\t\tthis.mapboxglAdapter.setDoubleClickToZoom(enabled);\n\t}\n\n\t/**\n\t * Renders GeoJSON features on the map using the provided styling configuration.\n\t * @param changes An object containing arrays of created, updated, and unchanged features to render.\n\t * @param styling An object mapping draw modes to feature styling functions\n\t */\n\tpublic render(changes: TerraDrawChanges, styling: TerraDrawStylingFunction) {\n\t\tthis.mapboxglAdapter.render(changes, styling);\n\t}\n\n\t/**\n\t * Clears the map and store of all rendered data layers\n\t * @returns void\n\t * */\n\tpublic clear() {\n\t\tthis.mapboxglAdapter.clear();\n\t}\n}\n","/**\n * @module ol/util\n */\n\n/**\n * @return {never} Any return.\n */\nexport function abstract() {\n throw new Error('Unimplemented abstract method.');\n}\n\n/**\n * Counter for getUid.\n * @type {number}\n * @private\n */\nlet uidCounter_ = 0;\n\n/**\n * Gets a unique ID for an object. This mutates the object so that further calls\n * with the same object as a parameter returns the same value. Unique IDs are generated\n * as a strictly increasing sequence. Adapted from goog.getUid.\n *\n * @param {Object} obj The object to get the unique ID for.\n * @return {string} The unique ID for the object.\n * @api\n */\nexport function getUid(obj) {\n return obj.ol_uid || (obj.ol_uid = String(++uidCounter_));\n}\n\n/**\n * OpenLayers version.\n * @type {string}\n */\nexport const VERSION = '10.0.0';\n","/**\n * @module ol/size\n */\n\n/**\n * An array of numbers representing a size: `[width, height]`.\n * @typedef {Array} Size\n * @api\n */\n\n/**\n * Returns a buffered size.\n * @param {Size} size Size.\n * @param {number} num The amount by which to buffer.\n * @param {Size} [dest] Optional reusable size array.\n * @return {Size} The buffered size.\n */\nexport function buffer(size, num, dest) {\n if (dest === undefined) {\n dest = [0, 0];\n }\n dest[0] = size[0] + 2 * num;\n dest[1] = size[1] + 2 * num;\n return dest;\n}\n\n/**\n * Determines if a size has a positive area.\n * @param {Size} size The size to test.\n * @return {boolean} The size has a positive area.\n */\nexport function hasArea(size) {\n return size[0] > 0 && size[1] > 0;\n}\n\n/**\n * Returns a size scaled by a ratio. The result will be an array of integers.\n * @param {Size} size Size.\n * @param {number} ratio Ratio.\n * @param {Size} [dest] Optional reusable size array.\n * @return {Size} The scaled size.\n */\nexport function scale(size, ratio, dest) {\n if (dest === undefined) {\n dest = [0, 0];\n }\n dest[0] = (size[0] * ratio + 0.5) | 0;\n dest[1] = (size[1] * ratio + 0.5) | 0;\n return dest;\n}\n\n/**\n * Returns an `Size` array for the passed in number (meaning: square) or\n * `Size` array.\n * (meaning: non-square),\n * @param {number|Size} size Width and height.\n * @param {Size} [dest] Optional reusable size array.\n * @return {Size} Size.\n * @api\n */\nexport function toSize(size, dest) {\n if (Array.isArray(size)) {\n return size;\n }\n if (dest === undefined) {\n dest = [size, size];\n } else {\n dest[0] = size;\n dest[1] = size;\n }\n return dest;\n}\n","/**\n * @module ol/style/Image\n */\nimport {abstract} from '../util.js';\nimport {toSize} from '../size.js';\n\n/**\n * @typedef {Object} Options\n * @property {number} opacity Opacity.\n * @property {boolean} rotateWithView If the image should get rotated with the view.\n * @property {number} rotation Rotation.\n * @property {number|import(\"../size.js\").Size} scale Scale.\n * @property {Array} displacement Displacement.\n * @property {import('../style/Style.js').DeclutterMode} declutterMode Declutter mode: `declutter`, `obstacle`, `none`.\n */\n\n/**\n * @classdesc\n * A base class used for creating subclasses and not instantiated in\n * apps. Base class for {@link module:ol/style/Icon~Icon}, {@link module:ol/style/Circle~CircleStyle} and\n * {@link module:ol/style/RegularShape~RegularShape}.\n * @abstract\n * @api\n */\nclass ImageStyle {\n /**\n * @param {Options} options Options.\n */\n constructor(options) {\n /**\n * @private\n * @type {number}\n */\n this.opacity_ = options.opacity;\n\n /**\n * @private\n * @type {boolean}\n */\n this.rotateWithView_ = options.rotateWithView;\n\n /**\n * @private\n * @type {number}\n */\n this.rotation_ = options.rotation;\n\n /**\n * @private\n * @type {number|import(\"../size.js\").Size}\n */\n this.scale_ = options.scale;\n\n /**\n * @private\n * @type {import(\"../size.js\").Size}\n */\n this.scaleArray_ = toSize(options.scale);\n\n /**\n * @private\n * @type {Array}\n */\n this.displacement_ = options.displacement;\n\n /**\n * @private\n * @type {import('../style/Style.js').DeclutterMode}\n */\n this.declutterMode_ = options.declutterMode;\n }\n\n /**\n * Clones the style.\n * @return {ImageStyle} The cloned style.\n * @api\n */\n clone() {\n const scale = this.getScale();\n return new ImageStyle({\n opacity: this.getOpacity(),\n scale: Array.isArray(scale) ? scale.slice() : scale,\n rotation: this.getRotation(),\n rotateWithView: this.getRotateWithView(),\n displacement: this.getDisplacement().slice(),\n declutterMode: this.getDeclutterMode(),\n });\n }\n\n /**\n * Get the symbolizer opacity.\n * @return {number} Opacity.\n * @api\n */\n getOpacity() {\n return this.opacity_;\n }\n\n /**\n * Determine whether the symbolizer rotates with the map.\n * @return {boolean} Rotate with map.\n * @api\n */\n getRotateWithView() {\n return this.rotateWithView_;\n }\n\n /**\n * Get the symoblizer rotation.\n * @return {number} Rotation.\n * @api\n */\n getRotation() {\n return this.rotation_;\n }\n\n /**\n * Get the symbolizer scale.\n * @return {number|import(\"../size.js\").Size} Scale.\n * @api\n */\n getScale() {\n return this.scale_;\n }\n\n /**\n * Get the symbolizer scale array.\n * @return {import(\"../size.js\").Size} Scale array.\n */\n getScaleArray() {\n return this.scaleArray_;\n }\n\n /**\n * Get the displacement of the shape\n * @return {Array} Shape's center displacement\n * @api\n */\n getDisplacement() {\n return this.displacement_;\n }\n\n /**\n * Get the declutter mode of the shape\n * @return {import(\"./Style.js\").DeclutterMode} Shape's declutter mode\n * @api\n */\n getDeclutterMode() {\n return this.declutterMode_;\n }\n\n /**\n * Get the anchor point in pixels. The anchor determines the center point for the\n * symbolizer.\n * @abstract\n * @return {Array} Anchor.\n */\n getAnchor() {\n return abstract();\n }\n\n /**\n * Get the image element for the symbolizer.\n * @abstract\n * @param {number} pixelRatio Pixel ratio.\n * @return {import('../DataTile.js').ImageLike} Image element.\n */\n getImage(pixelRatio) {\n return abstract();\n }\n\n /**\n * @abstract\n * @return {import('../DataTile.js').ImageLike} Image element.\n */\n getHitDetectionImage() {\n return abstract();\n }\n\n /**\n * Get the image pixel ratio.\n * @param {number} pixelRatio Pixel ratio.\n * @return {number} Pixel ratio.\n */\n getPixelRatio(pixelRatio) {\n return 1;\n }\n\n /**\n * @abstract\n * @return {import(\"../ImageState.js\").default} Image state.\n */\n getImageState() {\n return abstract();\n }\n\n /**\n * @abstract\n * @return {import(\"../size.js\").Size} Image size.\n */\n getImageSize() {\n return abstract();\n }\n\n /**\n * Get the origin of the symbolizer.\n * @abstract\n * @return {Array} Origin.\n */\n getOrigin() {\n return abstract();\n }\n\n /**\n * Get the size of the symbolizer (in pixels).\n * @abstract\n * @return {import(\"../size.js\").Size} Size.\n */\n getSize() {\n return abstract();\n }\n\n /**\n * Set the displacement.\n *\n * @param {Array} displacement Displacement.\n * @api\n */\n setDisplacement(displacement) {\n this.displacement_ = displacement;\n }\n\n /**\n * Set the opacity.\n *\n * @param {number} opacity Opacity.\n * @api\n */\n setOpacity(opacity) {\n this.opacity_ = opacity;\n }\n\n /**\n * Set whether to rotate the style with the view.\n *\n * @param {boolean} rotateWithView Rotate with map.\n * @api\n */\n setRotateWithView(rotateWithView) {\n this.rotateWithView_ = rotateWithView;\n }\n\n /**\n * Set the rotation.\n *\n * @param {number} rotation Rotation.\n * @api\n */\n setRotation(rotation) {\n this.rotation_ = rotation;\n }\n\n /**\n * Set the scale.\n *\n * @param {number|import(\"../size.js\").Size} scale Scale.\n * @api\n */\n setScale(scale) {\n this.scale_ = scale;\n this.scaleArray_ = toSize(scale);\n }\n\n /**\n * @abstract\n * @param {function(import(\"../events/Event.js\").default): void} listener Listener function.\n */\n listenImageChange(listener) {\n abstract();\n }\n\n /**\n * Load not yet loaded URI.\n * @abstract\n */\n load() {\n abstract();\n }\n\n /**\n * @abstract\n * @param {function(import(\"../events/Event.js\").default): void} listener Listener function.\n */\n unlistenImageChange(listener) {\n abstract();\n }\n\n /**\n * @return {Promise} `false` or Promise that resolves when the style is ready to use.\n */\n ready() {\n return Promise.resolve();\n }\n}\n\nexport default ImageStyle;\n","/**\n * RGB space.\n *\n * @module color-space/rgb\n */\n\nexport default {\n\tname: 'rgb',\n\tmin: [0,0,0],\n\tmax: [255,255,255],\n\tchannel: ['red', 'green', 'blue'],\n\talias: ['RGB']\n};\n","/**\n * CIE XYZ\n *\n * @module color-space/xyz\n */\nimport rgb from './rgb.js';\n\nvar xyz = {\n\tname: 'xyz',\n\tmin: [0,0,0],\n\tchannel: ['X','Y','Z'],\n\talias: ['XYZ', 'ciexyz', 'cie1931']\n};\n\n\n/**\n * Whitepoint reference values with observer/illuminant\n *\n * http://en.wikipedia.org/wiki/Standard_illuminant\n */\nxyz.whitepoint = {\n\t//1931 2°\n\t2: {\n\t\t//incadescent\n\t\tA:[109.85, 100, 35.585],\n\t\t// B:[],\n\t\tC: [98.074, 100, 118.232],\n\t\tD50: [96.422, 100, 82.521],\n\t\tD55: [95.682, 100, 92.149],\n\t\t//daylight\n\t\tD65: [95.045592705167, 100, 108.9057750759878],\n\t\tD75: [94.972, 100, 122.638],\n\t\t//flourescent\n\t\t// F1: [],\n\t\tF2: [99.187, 100, 67.395],\n\t\t// F3: [],\n\t\t// F4: [],\n\t\t// F5: [],\n\t\t// F6:[],\n\t\tF7: [95.044, 100, 108.755],\n\t\t// F8: [],\n\t\t// F9: [],\n\t\t// F10: [],\n\t\tF11: [100.966, 100, 64.370],\n\t\t// F12: [],\n\t\tE: [100,100,100]\n\t},\n\n\t//1964 10°\n\t10: {\n\t\t//incadescent\n\t\tA:[111.144, 100, 35.200],\n\t\tC: [97.285, 100, 116.145],\n\t\tD50: [96.720, 100, 81.427],\n\t\tD55: [95.799, 100, 90.926],\n\t\t//daylight\n\t\tD65: [94.811, 100, 107.304],\n\t\tD75: [94.416, 100, 120.641],\n\t\t//flourescent\n\t\tF2: [103.280, 100, 69.026],\n\t\tF7: [95.792, 100, 107.687],\n\t\tF11: [103.866, 100, 65.627],\n\t\tE: [100,100,100]\n\t}\n};\n\n\n/**\n * Top values are the whitepoint’s top values, default are D65\n */\nxyz.max = xyz.whitepoint[2].D65;\n\n\n/**\n * Transform xyz to rgb\n *\n * @param {Array} xyz Array of xyz values\n *\n * @return {Array} RGB values\n */\nxyz.rgb = function (_xyz, white) {\n\t//FIXME: make sure we have to divide like this. Probably we have to replace matrix as well then\n\twhite = white || xyz.whitepoint[2].E;\n\n\tvar x = _xyz[0] / white[0],\n\t\ty = _xyz[1] / white[1],\n\t\tz = _xyz[2] / white[2],\n\t\tr, g, b;\n\n\t// assume sRGB\n\t// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html\n\tr = (x * 3.240969941904521) + (y * -1.537383177570093) + (z * -0.498610760293);\n\tg = (x * -0.96924363628087) + (y * 1.87596750150772) + (z * 0.041555057407175);\n\tb = (x * 0.055630079696993) + (y * -0.20397695888897) + (z * 1.056971514242878);\n\n\tr = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)\n\t\t: r = (r * 12.92);\n\n\tg = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)\n\t\t: g = (g * 12.92);\n\n\tb = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)\n\t\t: b = (b * 12.92);\n\n\tr = Math.min(Math.max(0, r), 1);\n\tg = Math.min(Math.max(0, g), 1);\n\tb = Math.min(Math.max(0, b), 1);\n\n\treturn [r * 255, g * 255, b * 255];\n}\n\n\n\n/**\n * RGB to XYZ\n *\n * @param {Array} rgb RGB channels\n *\n * @return {Array} XYZ channels\n */\nrgb.xyz = function(rgb, white) {\n\tvar r = rgb[0] / 255,\n\t\t\tg = rgb[1] / 255,\n\t\t\tb = rgb[2] / 255;\n\n\t// assume sRGB\n\tr = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);\n\tg = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);\n\tb = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);\n\n\tvar x = (r * 0.41239079926595) + (g * 0.35758433938387) + (b * 0.18048078840183);\n\tvar y = (r * 0.21263900587151) + (g * 0.71516867876775) + (b * 0.072192315360733);\n\tvar z = (r * 0.019330818715591) + (g * 0.11919477979462) + (b * 0.95053215224966);\n\n\twhite = white || xyz.whitepoint[2].E;\n\n\treturn [x * white[0], y * white[1], z * white[2]];\n};\n\n\n\nexport default xyz;\n","/**\n * CIE LUV (C'est la vie)\n *\n * @module color-space/luv\n */\n import xyz from './xyz.js';\n\nexport default {\n\tname: 'luv',\n\t//NOTE: luv has no rigidly defined limits\n\t//easyrgb fails to get proper coords\n\t//boronine states no rigid limits\n\t//colorMine refers this ones:\n\tmin: [0,-134,-140],\n\tmax: [100,224,122],\n\tchannel: ['lightness', 'u', 'v'],\n\talias: ['LUV', 'cieluv', 'cie1976'],\n\n\txyz: function(arg, i, o){\n\t\tvar _u, _v, l, u, v, x, y, z, xn, yn, zn, un, vn;\n\t\tl = arg[0], u = arg[1], v = arg[2];\n\n\t\tif (l === 0) return [0,0,0];\n\n\t\t//get constants\n\t\t//var e = 0.008856451679035631; //(6/29)^3\n\t\tvar k = 0.0011070564598794539; //(3/29)^3\n\n\t\t//get illuminant/observer\n\t\ti = i || 'D65';\n\t\to = o || 2;\n\n\t\txn = xyz.whitepoint[o][i][0];\n\t\tyn = xyz.whitepoint[o][i][1];\n\t\tzn = xyz.whitepoint[o][i][2];\n\n\t\tun = (4 * xn) / (xn + (15 * yn) + (3 * zn));\n\t\tvn = (9 * yn) / (xn + (15 * yn) + (3 * zn));\n\t\t// un = 0.19783000664283;\n\t\t// vn = 0.46831999493879;\n\n\n\t\t_u = u / (13 * l) + un || 0;\n\t\t_v = v / (13 * l) + vn || 0;\n\n\t\ty = l > 8 ? yn * Math.pow( (l + 16) / 116 , 3) : yn * l * k;\n\n\t\t//wikipedia method\n\t\tx = y * 9 * _u / (4 * _v) || 0;\n\t\tz = y * (12 - 3 * _u - 20 * _v) / (4 * _v) || 0;\n\n\t\t//boronine method\n\t\t//https://github.com/boronine/husl/blob/master/husl.coffee#L201\n\t\t// x = 0 - (9 * y * _u) / ((_u - 4) * _v - _u * _v);\n\t\t// z = (9 * y - (15 * _v * y) - (_v * x)) / (3 * _v);\n\n\t\treturn [x, y, z];\n\t}\n};\n\n// http://www.brucelindbloom.com/index.html?Equations.html\n// https://github.com/boronine/husl/blob/master/husl.coffee\n//i - illuminant\n//o - observer\nxyz.luv = function(arg, i, o) {\n\tvar _u, _v, l, u, v, x, y, z, xn, yn, zn, un, vn;\n\n\t//get constants\n\tvar e = 0.008856451679035631; //(6/29)^3\n\tvar k = 903.2962962962961; //(29/3)^3\n\n\t//get illuminant/observer coords\n\ti = i || 'D65';\n\to = o || 2;\n\n\txn = xyz.whitepoint[o][i][0];\n\tyn = xyz.whitepoint[o][i][1];\n\tzn = xyz.whitepoint[o][i][2];\n\n\tun = (4 * xn) / (xn + (15 * yn) + (3 * zn));\n\tvn = (9 * yn) / (xn + (15 * yn) + (3 * zn));\n\n\n\tx = arg[0], y = arg[1], z = arg[2];\n\n\n\t_u = (4 * x) / (x + (15 * y) + (3 * z)) || 0;\n\t_v = (9 * y) / (x + (15 * y) + (3 * z)) || 0;\n\n\tvar yr = y/yn;\n\n\tl = yr <= e ? k * yr : 116 * Math.pow(yr, 1/3) - 16;\n\n\tu = 13 * l * (_u - un);\n\tv = 13 * l * (_v - vn);\n\n\treturn [l, u, v];\n};\n","/**\n * Cylindrical CIE LUV\n *\n * @module color-space/lchuv\n */\nimport luv from './luv.js';\nimport xyz from './xyz.js';\n\n//cylindrical luv\nvar lchuv = {\n\tname: 'lchuv',\n\tchannel: ['lightness', 'chroma', 'hue'],\n\talias: ['LCHuv', 'cielchuv'],\n\tmin: [0,0,0],\n\tmax: [100,100,360],\n\n\tluv: function(luv){\n\t\tvar l = luv[0],\n\t\tc = luv[1],\n\t\th = luv[2],\n\t\tu, v, hr;\n\n\t\thr = h / 360 * 2 * Math.PI;\n\t\tu = c * Math.cos(hr);\n\t\tv = c * Math.sin(hr);\n\t\treturn [l, u, v];\n\t},\n\n\txyz: function(arg) {\n\t\treturn luv.xyz(lchuv.luv(arg));\n\t}\n};\n\nexport default lchuv;\n\nluv.lchuv = function(luv){\n\tvar l = luv[0], u = luv[1], v = luv[2];\n\n\tvar c = Math.sqrt(u*u + v*v);\n\tvar hr = Math.atan2(v,u);\n\tvar h = hr * 360 / 2 / Math.PI;\n\tif (h < 0) {\n\t\th += 360;\n\t}\n\n\treturn [l,c,h]\n};\n\nxyz.lchuv = function(arg){\n return luv.lchuv(xyz.luv(arg));\n};\n","export default {\n\taliceblue: [240, 248, 255],\n\tantiquewhite: [250, 235, 215],\n\taqua: [0, 255, 255],\n\taquamarine: [127, 255, 212],\n\tazure: [240, 255, 255],\n\tbeige: [245, 245, 220],\n\tbisque: [255, 228, 196],\n\tblack: [0, 0, 0],\n\tblanchedalmond: [255, 235, 205],\n\tblue: [0, 0, 255],\n\tblueviolet: [138, 43, 226],\n\tbrown: [165, 42, 42],\n\tburlywood: [222, 184, 135],\n\tcadetblue: [95, 158, 160],\n\tchartreuse: [127, 255, 0],\n\tchocolate: [210, 105, 30],\n\tcoral: [255, 127, 80],\n\tcornflowerblue: [100, 149, 237],\n\tcornsilk: [255, 248, 220],\n\tcrimson: [220, 20, 60],\n\tcyan: [0, 255, 255],\n\tdarkblue: [0, 0, 139],\n\tdarkcyan: [0, 139, 139],\n\tdarkgoldenrod: [184, 134, 11],\n\tdarkgray: [169, 169, 169],\n\tdarkgreen: [0, 100, 0],\n\tdarkgrey: [169, 169, 169],\n\tdarkkhaki: [189, 183, 107],\n\tdarkmagenta: [139, 0, 139],\n\tdarkolivegreen: [85, 107, 47],\n\tdarkorange: [255, 140, 0],\n\tdarkorchid: [153, 50, 204],\n\tdarkred: [139, 0, 0],\n\tdarksalmon: [233, 150, 122],\n\tdarkseagreen: [143, 188, 143],\n\tdarkslateblue: [72, 61, 139],\n\tdarkslategray: [47, 79, 79],\n\tdarkslategrey: [47, 79, 79],\n\tdarkturquoise: [0, 206, 209],\n\tdarkviolet: [148, 0, 211],\n\tdeeppink: [255, 20, 147],\n\tdeepskyblue: [0, 191, 255],\n\tdimgray: [105, 105, 105],\n\tdimgrey: [105, 105, 105],\n\tdodgerblue: [30, 144, 255],\n\tfirebrick: [178, 34, 34],\n\tfloralwhite: [255, 250, 240],\n\tforestgreen: [34, 139, 34],\n\tfuchsia: [255, 0, 255],\n\tgainsboro: [220, 220, 220],\n\tghostwhite: [248, 248, 255],\n\tgold: [255, 215, 0],\n\tgoldenrod: [218, 165, 32],\n\tgray: [128, 128, 128],\n\tgreen: [0, 128, 0],\n\tgreenyellow: [173, 255, 47],\n\tgrey: [128, 128, 128],\n\thoneydew: [240, 255, 240],\n\thotpink: [255, 105, 180],\n\tindianred: [205, 92, 92],\n\tindigo: [75, 0, 130],\n\tivory: [255, 255, 240],\n\tkhaki: [240, 230, 140],\n\tlavender: [230, 230, 250],\n\tlavenderblush: [255, 240, 245],\n\tlawngreen: [124, 252, 0],\n\tlemonchiffon: [255, 250, 205],\n\tlightblue: [173, 216, 230],\n\tlightcoral: [240, 128, 128],\n\tlightcyan: [224, 255, 255],\n\tlightgoldenrodyellow: [250, 250, 210],\n\tlightgray: [211, 211, 211],\n\tlightgreen: [144, 238, 144],\n\tlightgrey: [211, 211, 211],\n\tlightpink: [255, 182, 193],\n\tlightsalmon: [255, 160, 122],\n\tlightseagreen: [32, 178, 170],\n\tlightskyblue: [135, 206, 250],\n\tlightslategray: [119, 136, 153],\n\tlightslategrey: [119, 136, 153],\n\tlightsteelblue: [176, 196, 222],\n\tlightyellow: [255, 255, 224],\n\tlime: [0, 255, 0],\n\tlimegreen: [50, 205, 50],\n\tlinen: [250, 240, 230],\n\tmagenta: [255, 0, 255],\n\tmaroon: [128, 0, 0],\n\tmediumaquamarine: [102, 205, 170],\n\tmediumblue: [0, 0, 205],\n\tmediumorchid: [186, 85, 211],\n\tmediumpurple: [147, 112, 219],\n\tmediumseagreen: [60, 179, 113],\n\tmediumslateblue: [123, 104, 238],\n\tmediumspringgreen: [0, 250, 154],\n\tmediumturquoise: [72, 209, 204],\n\tmediumvioletred: [199, 21, 133],\n\tmidnightblue: [25, 25, 112],\n\tmintcream: [245, 255, 250],\n\tmistyrose: [255, 228, 225],\n\tmoccasin: [255, 228, 181],\n\tnavajowhite: [255, 222, 173],\n\tnavy: [0, 0, 128],\n\toldlace: [253, 245, 230],\n\tolive: [128, 128, 0],\n\tolivedrab: [107, 142, 35],\n\torange: [255, 165, 0],\n\torangered: [255, 69, 0],\n\torchid: [218, 112, 214],\n\tpalegoldenrod: [238, 232, 170],\n\tpalegreen: [152, 251, 152],\n\tpaleturquoise: [175, 238, 238],\n\tpalevioletred: [219, 112, 147],\n\tpapayawhip: [255, 239, 213],\n\tpeachpuff: [255, 218, 185],\n\tperu: [205, 133, 63],\n\tpink: [255, 192, 203],\n\tplum: [221, 160, 221],\n\tpowderblue: [176, 224, 230],\n\tpurple: [128, 0, 128],\n\trebeccapurple: [102, 51, 153],\n\tred: [255, 0, 0],\n\trosybrown: [188, 143, 143],\n\troyalblue: [65, 105, 225],\n\tsaddlebrown: [139, 69, 19],\n\tsalmon: [250, 128, 114],\n\tsandybrown: [244, 164, 96],\n\tseagreen: [46, 139, 87],\n\tseashell: [255, 245, 238],\n\tsienna: [160, 82, 45],\n\tsilver: [192, 192, 192],\n\tskyblue: [135, 206, 235],\n\tslateblue: [106, 90, 205],\n\tslategray: [112, 128, 144],\n\tslategrey: [112, 128, 144],\n\tsnow: [255, 250, 250],\n\tspringgreen: [0, 255, 127],\n\tsteelblue: [70, 130, 180],\n\ttan: [210, 180, 140],\n\tteal: [0, 128, 128],\n\tthistle: [216, 191, 216],\n\ttomato: [255, 99, 71],\n\tturquoise: [64, 224, 208],\n\tviolet: [238, 130, 238],\n\twheat: [245, 222, 179],\n\twhite: [255, 255, 255],\n\twhitesmoke: [245, 245, 245],\n\tyellow: [255, 255, 0],\n\tyellowgreen: [154, 205, 50]\n}\n","/**\n * @module color-parse\n */\nimport names from 'color-name'\n\nexport default parse\n\n/**\n * Base hues\n * http://dev.w3.org/csswg/css-color/#typedef-named-hue\n */\n//FIXME: use external hue detector\nvar baseHues = {\n\tred: 0,\n\torange: 60,\n\tyellow: 120,\n\tgreen: 180,\n\tblue: 240,\n\tpurple: 300\n}\n\n/**\n * Parse color from the string passed\n *\n * @return {Object} A space indicator `space`, an array `values` and `alpha`\n */\nfunction parse(cstr) {\n\tvar m, parts = [], alpha = 1, space\n\n\t//numeric case\n\tif (typeof cstr === 'number') {\n\t\treturn { space: 'rgb', values: [cstr >>> 16, (cstr & 0x00ff00) >>> 8, cstr & 0x0000ff], alpha: 1 }\n\t}\n\tif (typeof cstr === 'number') return { space: 'rgb', values: [cstr >>> 16, (cstr & 0x00ff00) >>> 8, cstr & 0x0000ff], alpha: 1 }\n\n\tcstr = String(cstr).toLowerCase();\n\n\t//keyword\n\tif (names[cstr]) {\n\t\tparts = names[cstr].slice()\n\t\tspace = 'rgb'\n\t}\n\n\t//reserved words\n\telse if (cstr === 'transparent') {\n\t\talpha = 0\n\t\tspace = 'rgb'\n\t\tparts = [0, 0, 0]\n\t}\n\n\t//hex\n\telse if (cstr[0] === '#') {\n\t\tvar base = cstr.slice(1)\n\t\tvar size = base.length\n\t\tvar isShort = size <= 4\n\t\talpha = 1\n\n\t\tif (isShort) {\n\t\t\tparts = [\n\t\t\t\tparseInt(base[0] + base[0], 16),\n\t\t\t\tparseInt(base[1] + base[1], 16),\n\t\t\t\tparseInt(base[2] + base[2], 16)\n\t\t\t]\n\t\t\tif (size === 4) {\n\t\t\t\talpha = parseInt(base[3] + base[3], 16) / 255\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tparts = [\n\t\t\t\tparseInt(base[0] + base[1], 16),\n\t\t\t\tparseInt(base[2] + base[3], 16),\n\t\t\t\tparseInt(base[4] + base[5], 16)\n\t\t\t]\n\t\t\tif (size === 8) {\n\t\t\t\talpha = parseInt(base[6] + base[7], 16) / 255\n\t\t\t}\n\t\t}\n\n\t\tif (!parts[0]) parts[0] = 0\n\t\tif (!parts[1]) parts[1] = 0\n\t\tif (!parts[2]) parts[2] = 0\n\n\t\tspace = 'rgb'\n\t}\n\n\t// color space\n\telse if (m = /^((?:rgba?|hs[lvb]a?|hwba?|cmyk?|xy[zy]|gray|lab|lchu?v?|[ly]uv|lms|oklch|oklab|color))\\s*\\(([^\\)]*)\\)/.exec(cstr)) {\n\t\tvar name = m[1]\n\t\tspace = name.replace(/a$/, '')\n\t\tvar dims = space === 'cmyk' ? 4 : space === 'gray' ? 1 : 3\n\t\tparts = m[2].trim().split(/\\s*[,\\/]\\s*|\\s+/)\n\n\t\t// color(srgb-linear x x x) -> srgb-linear(x x x)\n\t\tif (space === 'color') space = parts.shift()\n\n\t\tparts = parts.map(function (x, i) {\n\t\t\t//\n\t\t\tif (x[x.length - 1] === '%') {\n\t\t\t\tx = parseFloat(x) / 100\n\t\t\t\t// alpha -> 0..1\n\t\t\t\tif (i === 3) return x\n\t\t\t\t// rgb -> 0..255\n\t\t\t\tif (space === 'rgb') return x * 255\n\t\t\t\t// hsl, hwb H -> 0..100\n\t\t\t\tif (space[0] === 'h') return x * 100\n\t\t\t\t// lch, lab L -> 0..100\n\t\t\t\tif (space[0] === 'l' && !i) return x * 100\n\t\t\t\t// lab A B -> -125..125\n\t\t\t\tif (space === 'lab') return x * 125\n\t\t\t\t// lch C -> 0..150, H -> 0..360\n\t\t\t\tif (space === 'lch') return i < 2 ? x * 150 : x * 360\n\t\t\t\t// oklch/oklab L -> 0..1\n\t\t\t\tif (space[0] === 'o' && !i) return x\n\t\t\t\t// oklab A B -> -0.4..0.4\n\t\t\t\tif (space === 'oklab') return x * 0.4\n\t\t\t\t// oklch C -> 0..0.4, H -> 0..360\n\t\t\t\tif (space === 'oklch') return i < 2 ? x * 0.4 : x * 360\n\t\t\t\t// color(xxx) -> 0..1\n\t\t\t\treturn x\n\t\t\t}\n\n\t\t\t//hue\n\t\t\tif (space[i] === 'h' || (i === 2 && space[space.length - 1] === 'h')) {\n\t\t\t\t//\n\t\t\t\tif (baseHues[x] !== undefined) return baseHues[x]\n\t\t\t\t//\n\t\t\t\tif (x.endsWith('deg')) return parseFloat(x)\n\t\t\t\t//\n\t\t\t\tif (x.endsWith('turn')) return parseFloat(x) * 360\n\t\t\t\tif (x.endsWith('grad')) return parseFloat(x) * 360 / 400\n\t\t\t\tif (x.endsWith('rad')) return parseFloat(x) * 180 / Math.PI\n\t\t\t}\n\t\t\tif (x === 'none') return 0\n\t\t\treturn parseFloat(x)\n\t\t});\n\n\t\talpha = parts.length > dims ? parts.pop() : 1\n\t}\n\n\t//named channels case\n\telse if (/[0-9](?:\\s|\\/|,)/.test(cstr)) {\n\t\tparts = cstr.match(/([0-9]+)/g).map(function (value) {\n\t\t\treturn parseFloat(value)\n\t\t})\n\n\t\tspace = cstr.match(/([a-z])/ig)?.join('')?.toLowerCase() || 'rgb'\n\t}\n\n\treturn {\n\t\tspace,\n\t\tvalues: parts,\n\t\talpha\n\t}\n}\n","/**\n * @module color-space/hsl\n */\nimport rgb from './rgb.js';\n\nexport default {\n\tname: 'hsl',\n\tmin: [0,0,0],\n\tmax: [360,100,100],\n\tchannel: ['hue', 'saturation', 'lightness'],\n\talias: ['HSL'],\n\n\trgb: function(hsl) {\n\t\tvar h = hsl[0]/360, s = hsl[1]/100, l = hsl[2]/100, t1, t2, t3, rgb, val, i=0;\n\n\t\tif (s === 0) return val = l * 255, [val, val, val];\n\n\t\tt2 = l < 0.5 ? l * (1 + s) : l + s - l * s;\n\t\tt1 = 2 * l - t2;\n\n\t\trgb = [0, 0, 0];\n\t\tfor (;i<3;) {\n\t\t\tt3 = h + 1 / 3 * - (i - 1);\n\t\t\tt3 < 0 ? t3++ : t3 > 1 && t3--;\n\t\t\tval = 6 * t3 < 1 ? t1 + (t2 - t1) * 6 * t3 :\n\t\t\t2 * t3 < 1 ? t2 :\n\t\t\t3 * t3 < 2 ? t1 + (t2 - t1) * (2 / 3 - t3) * 6 :\n\t\t\tt1;\n\t\t\trgb[i++] = val * 255;\n\t\t}\n\n\t\treturn rgb;\n\t}\n};\n\n\n//extend rgb\nrgb.hsl = function(rgb) {\n\tvar r = rgb[0]/255,\n\t\t\tg = rgb[1]/255,\n\t\t\tb = rgb[2]/255,\n\t\t\tmin = Math.min(r, g, b),\n\t\t\tmax = Math.max(r, g, b),\n\t\t\tdelta = max - min,\n\t\t\th, s, l;\n\n\tif (max === min) {\n\t\th = 0;\n\t}\n\telse if (r === max) {\n\t\th = (g - b) / delta;\n\t}\n\telse if (g === max) {\n\t\th = 2 + (b - r) / delta;\n\t}\n\telse if (b === max) {\n\t\th = 4 + (r - g)/ delta;\n\t}\n\n\th = Math.min(h * 60, 360);\n\n\tif (h < 0) {\n\t\th += 360;\n\t}\n\n\tl = (min + max) / 2;\n\n\tif (max === min) {\n\t\ts = 0;\n\t}\n\telse if (l <= 0.5) {\n\t\ts = delta / (max + min);\n\t}\n\telse {\n\t\ts = delta / (2 - max - min);\n\t}\n\n\treturn [h, s * 100, l * 100];\n};\n","/**\n * @module ol/math\n */\n\n/**\n * Takes a number and clamps it to within the provided bounds.\n * @param {number} value The input number.\n * @param {number} min The minimum value to return.\n * @param {number} max The maximum value to return.\n * @return {number} The input number if it is within bounds, or the nearest\n * number within the bounds.\n */\nexport function clamp(value, min, max) {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Returns the square of the closest distance between the point (x, y) and the\n * line segment (x1, y1) to (x2, y2).\n * @param {number} x X.\n * @param {number} y Y.\n * @param {number} x1 X1.\n * @param {number} y1 Y1.\n * @param {number} x2 X2.\n * @param {number} y2 Y2.\n * @return {number} Squared distance.\n */\nexport function squaredSegmentDistance(x, y, x1, y1, x2, y2) {\n const dx = x2 - x1;\n const dy = y2 - y1;\n if (dx !== 0 || dy !== 0) {\n const t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);\n if (t > 1) {\n x1 = x2;\n y1 = y2;\n } else if (t > 0) {\n x1 += dx * t;\n y1 += dy * t;\n }\n }\n return squaredDistance(x, y, x1, y1);\n}\n\n/**\n * Returns the square of the distance between the points (x1, y1) and (x2, y2).\n * @param {number} x1 X1.\n * @param {number} y1 Y1.\n * @param {number} x2 X2.\n * @param {number} y2 Y2.\n * @return {number} Squared distance.\n */\nexport function squaredDistance(x1, y1, x2, y2) {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return dx * dx + dy * dy;\n}\n\n/**\n * Solves system of linear equations using Gaussian elimination method.\n *\n * @param {Array>} mat Augmented matrix (n x n + 1 column)\n * in row-major order.\n * @return {Array|null} The resulting vector.\n */\nexport function solveLinearSystem(mat) {\n const n = mat.length;\n\n for (let i = 0; i < n; i++) {\n // Find max in the i-th column (ignoring i - 1 first rows)\n let maxRow = i;\n let maxEl = Math.abs(mat[i][i]);\n for (let r = i + 1; r < n; r++) {\n const absValue = Math.abs(mat[r][i]);\n if (absValue > maxEl) {\n maxEl = absValue;\n maxRow = r;\n }\n }\n\n if (maxEl === 0) {\n return null; // matrix is singular\n }\n\n // Swap max row with i-th (current) row\n const tmp = mat[maxRow];\n mat[maxRow] = mat[i];\n mat[i] = tmp;\n\n // Subtract the i-th row to make all the remaining rows 0 in the i-th column\n for (let j = i + 1; j < n; j++) {\n const coef = -mat[j][i] / mat[i][i];\n for (let k = i; k < n + 1; k++) {\n if (i == k) {\n mat[j][k] = 0;\n } else {\n mat[j][k] += coef * mat[i][k];\n }\n }\n }\n }\n\n // Solve Ax=b for upper triangular matrix A (mat)\n const x = new Array(n);\n for (let l = n - 1; l >= 0; l--) {\n x[l] = mat[l][n] / mat[l][l];\n for (let m = l - 1; m >= 0; m--) {\n mat[m][n] -= mat[m][l] * x[l];\n }\n }\n return x;\n}\n\n/**\n * Converts radians to to degrees.\n *\n * @param {number} angleInRadians Angle in radians.\n * @return {number} Angle in degrees.\n */\nexport function toDegrees(angleInRadians) {\n return (angleInRadians * 180) / Math.PI;\n}\n\n/**\n * Converts degrees to radians.\n *\n * @param {number} angleInDegrees Angle in degrees.\n * @return {number} Angle in radians.\n */\nexport function toRadians(angleInDegrees) {\n return (angleInDegrees * Math.PI) / 180;\n}\n\n/**\n * Returns the modulo of a / b, depending on the sign of b.\n *\n * @param {number} a Dividend.\n * @param {number} b Divisor.\n * @return {number} Modulo.\n */\nexport function modulo(a, b) {\n const r = a % b;\n return r * b < 0 ? r + b : r;\n}\n\n/**\n * Calculates the linearly interpolated value of x between a and b.\n *\n * @param {number} a Number\n * @param {number} b Number\n * @param {number} x Value to be interpolated.\n * @return {number} Interpolated value.\n */\nexport function lerp(a, b, x) {\n return a + x * (b - a);\n}\n\n/**\n * Returns a number with a limited number of decimal digits.\n * @param {number} n The input number.\n * @param {number} decimals The maximum number of decimal digits.\n * @return {number} The input number with a limited number of decimal digits.\n */\nexport function toFixed(n, decimals) {\n const factor = Math.pow(10, decimals);\n return Math.round(n * factor) / factor;\n}\n\n/**\n * Rounds a number to the nearest integer value considering only the given number\n * of decimal digits (with rounding on the final digit).\n * @param {number} n The input number.\n * @param {number} decimals The maximum number of decimal digits.\n * @return {number} The nearest integer.\n */\nexport function round(n, decimals) {\n return Math.round(toFixed(n, decimals));\n}\n\n/**\n * Rounds a number to the next smaller integer considering only the given number\n * of decimal digits (with rounding on the final digit).\n * @param {number} n The input number.\n * @param {number} decimals The maximum number of decimal digits.\n * @return {number} The next smaller integer.\n */\nexport function floor(n, decimals) {\n return Math.floor(toFixed(n, decimals));\n}\n\n/**\n * Rounds a number to the next bigger integer considering only the given number\n * of decimal digits (with rounding on the final digit).\n * @param {number} n The input number.\n * @param {number} decimals The maximum number of decimal digits.\n * @return {number} The next bigger integer.\n */\nexport function ceil(n, decimals) {\n return Math.ceil(toFixed(n, decimals));\n}\n","/**\n * @module ol/color\n */\nimport lchuv from 'color-space/lchuv.js';\nimport parseRgba from 'color-rgba';\nimport rgb from 'color-space/rgb.js';\nimport xyz from 'color-space/xyz.js';\nimport {clamp} from './math.js';\n\n/**\n * A color represented as a short array [red, green, blue, alpha].\n * red, green, and blue should be integers in the range 0..255 inclusive.\n * alpha should be a float in the range 0..1 inclusive. If no alpha value is\n * given then `1` will be used.\n * @typedef {Array} Color\n * @api\n */\n\n/**\n * Return the color as an rgba string.\n * @param {Color|string} color Color.\n * @return {string} Rgba string.\n * @api\n */\nexport function asString(color) {\n if (typeof color === 'string') {\n return color;\n }\n return toString(color);\n}\n\n/**\n * @type {number}\n */\nconst MAX_CACHE_SIZE = 1024;\n\n/**\n * We maintain a small cache of parsed strings. Whenever the cache grows too large,\n * we delete an arbitrary set of the entries.\n *\n * @type {Object}\n */\nconst cache = {};\n\n/**\n * @type {number}\n */\nlet cacheSize = 0;\n\n/**\n * @param {Color} color A color that may or may not have an alpha channel.\n * @return {Color} The input color with an alpha channel. If the input color has\n * an alpha channel, the input color will be returned unchanged. Otherwise, a new\n * array will be returned with the input color and an alpha channel of 1.\n */\nexport function withAlpha(color) {\n if (color.length === 4) {\n return color;\n }\n const output = color.slice();\n output[3] = 1;\n return output;\n}\n\n/**\n * @param {Color} color RGBA color.\n * @return {Color} LCHuv color with alpha.\n */\nexport function rgbaToLcha(color) {\n const output = xyz.lchuv(rgb.xyz(color));\n output[3] = color[3];\n return output;\n}\n\n/**\n * @param {Color} color LCHuv color with alpha.\n * @return {Color} RGBA color.\n */\nexport function lchaToRgba(color) {\n const output = xyz.rgb(lchuv.xyz(color));\n output[3] = color[3];\n return output;\n}\n\n/**\n * @param {string} s String.\n * @return {Color} Color.\n */\nexport function fromString(s) {\n if (cache.hasOwnProperty(s)) {\n return cache[s];\n }\n if (cacheSize >= MAX_CACHE_SIZE) {\n let i = 0;\n for (const key in cache) {\n if ((i++ & 3) === 0) {\n delete cache[key];\n --cacheSize;\n }\n }\n }\n\n const color = parseRgba(s);\n if (color.length !== 4) {\n throw new Error('failed to parse \"' + s + '\" as color');\n }\n for (const c of color) {\n if (isNaN(c)) {\n throw new Error('failed to parse \"' + s + '\" as color');\n }\n }\n normalize(color);\n cache[s] = color;\n ++cacheSize;\n return color;\n}\n\n/**\n * Return the color as an array. This function maintains a cache of calculated\n * arrays which means the result should not be modified.\n * @param {Color|string} color Color.\n * @return {Color} Color.\n * @api\n */\nexport function asArray(color) {\n if (Array.isArray(color)) {\n return color;\n }\n return fromString(color);\n}\n\n/**\n * Exported for the tests.\n * @param {Color} color Color.\n * @return {Color} Clamped color.\n */\nexport function normalize(color) {\n color[0] = clamp((color[0] + 0.5) | 0, 0, 255);\n color[1] = clamp((color[1] + 0.5) | 0, 0, 255);\n color[2] = clamp((color[2] + 0.5) | 0, 0, 255);\n color[3] = clamp(color[3], 0, 1);\n return color;\n}\n\n/**\n * @param {Color} color Color.\n * @return {string} String.\n */\nexport function toString(color) {\n let r = color[0];\n if (r != (r | 0)) {\n r = (r + 0.5) | 0;\n }\n let g = color[1];\n if (g != (g | 0)) {\n g = (g + 0.5) | 0;\n }\n let b = color[2];\n if (b != (b | 0)) {\n b = (b + 0.5) | 0;\n }\n const a = color[3] === undefined ? 1 : Math.round(color[3] * 1000) / 1000;\n return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';\n}\n\n/**\n * @param {string} s String.\n * @return {boolean} Whether the string is actually a valid color\n */\nexport function isStringColor(s) {\n try {\n fromString(s);\n return true;\n } catch (_) {\n return false;\n }\n}\n","/** @module color-rgba */\nimport parse from 'color-parse'\nimport rgb from 'color-space/rgb.js'\nimport hsl from 'color-space/hsl.js'\n\nexport default function rgba(color) {\n\t// template literals\n\tif (Array.isArray(color) && color.raw) color = String.raw(...arguments)\n\tif (color instanceof Number) color = +color\n\n\tvar values, i, l\n\n\t//attempt to parse non-array arguments\n\tvar parsed = parse(color)\n\n\tif (!parsed.space) return []\n\n\tconst min = parsed.space[0] === 'h' ? hsl.min : rgb.min\n\tconst max = parsed.space[0] === 'h' ? hsl.max : rgb.max\n\n\tvalues = Array(3)\n\tvalues[0] = Math.min(Math.max(parsed.values[0], min[0]), max[0])\n\tvalues[1] = Math.min(Math.max(parsed.values[1], min[1]), max[1])\n\tvalues[2] = Math.min(Math.max(parsed.values[2], min[2]), max[2])\n\n\tif (parsed.space[0] === 'h') {\n\t\tvalues = hsl.rgb(values)\n\t}\n\n\tvalues.push(Math.min(Math.max(parsed.alpha, 0), 1))\n\n\treturn values\n}\n","/**\n * @module ol/has\n */\n\nconst ua =\n typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined'\n ? navigator.userAgent.toLowerCase()\n : '';\n\n/**\n * User agent string says we are dealing with Firefox as browser.\n * @type {boolean}\n */\nexport const FIREFOX = ua.includes('firefox');\n\n/**\n * User agent string says we are dealing with Safari as browser.\n * @type {boolean}\n */\nexport const SAFARI = ua.includes('safari') && !ua.includes('chrom');\n\n/**\n * https://bugs.webkit.org/show_bug.cgi?id=237906\n * @type {boolean}\n */\nexport const SAFARI_BUG_237906 =\n SAFARI &&\n (ua.includes('version/15.4') ||\n /cpu (os|iphone os) 15_4 like mac os x/.test(ua));\n\n/**\n * User agent string says we are dealing with a WebKit engine.\n * @type {boolean}\n */\nexport const WEBKIT = ua.includes('webkit') && !ua.includes('edge');\n\n/**\n * User agent string says we are dealing with a Mac as platform.\n * @type {boolean}\n */\nexport const MAC = ua.includes('macintosh');\n\n/**\n * The ratio between physical pixels and device-independent pixels\n * (dips) on the device (`window.devicePixelRatio`).\n * @const\n * @type {number}\n * @api\n */\nexport const DEVICE_PIXEL_RATIO =\n typeof devicePixelRatio !== 'undefined' ? devicePixelRatio : 1;\n\n/**\n * The execution context is a worker with OffscreenCanvas available.\n * @const\n * @type {boolean}\n */\nexport const WORKER_OFFSCREEN_CANVAS =\n typeof WorkerGlobalScope !== 'undefined' &&\n typeof OffscreenCanvas !== 'undefined' &&\n self instanceof WorkerGlobalScope; //eslint-disable-line\n\n/**\n * Image.prototype.decode() is supported.\n * @type {boolean}\n */\nexport const IMAGE_DECODE =\n typeof Image !== 'undefined' && Image.prototype.decode;\n\n/**\n * createImageBitmap() is supported.\n * @type {boolean}\n */\nexport const CREATE_IMAGE_BITMAP = typeof createImageBitmap === 'function';\n\n/**\n * @type {boolean}\n */\nexport const PASSIVE_EVENT_LISTENERS = (function () {\n let passive = false;\n try {\n const options = Object.defineProperty({}, 'passive', {\n get: function () {\n passive = true;\n },\n });\n\n // @ts-ignore Ignore invalid event type '_'\n window.addEventListener('_', null, options);\n // @ts-ignore Ignore invalid event type '_'\n window.removeEventListener('_', null, options);\n } catch (error) {\n // passive not supported\n }\n return passive;\n})();\n","import {WORKER_OFFSCREEN_CANVAS} from './has.js';\n\n/**\n * @module ol/dom\n */\n\n//FIXME Move this function to the canvas module\n/**\n * Create an html canvas element and returns its 2d context.\n * @param {number} [width] Canvas width.\n * @param {number} [height] Canvas height.\n * @param {Array} [canvasPool] Canvas pool to take existing canvas from.\n * @param {CanvasRenderingContext2DSettings} [settings] CanvasRenderingContext2DSettings\n * @return {CanvasRenderingContext2D} The context.\n */\nexport function createCanvasContext2D(width, height, canvasPool, settings) {\n /** @type {HTMLCanvasElement|OffscreenCanvas} */\n let canvas;\n if (canvasPool && canvasPool.length) {\n canvas = /** @type {HTMLCanvasElement} */ (canvasPool.shift());\n } else if (WORKER_OFFSCREEN_CANVAS) {\n canvas = new OffscreenCanvas(width || 300, height || 300);\n } else {\n canvas = document.createElement('canvas');\n }\n if (width) {\n canvas.width = width;\n }\n if (height) {\n canvas.height = height;\n }\n //FIXME Allow OffscreenCanvasRenderingContext2D as return type\n return /** @type {CanvasRenderingContext2D} */ (\n canvas.getContext('2d', settings)\n );\n}\n\n/** @type {CanvasRenderingContext2D} */\nlet sharedCanvasContext;\n\n/**\n * @return {CanvasRenderingContext2D} Shared canvas context.\n */\nexport function getSharedCanvasContext2D() {\n if (!sharedCanvasContext) {\n sharedCanvasContext = createCanvasContext2D(1, 1);\n }\n return sharedCanvasContext;\n}\n\n/**\n * Releases canvas memory to avoid exceeding memory limits in Safari.\n * See https://pqina.nl/blog/total-canvas-memory-use-exceeds-the-maximum-limit/\n * @param {CanvasRenderingContext2D} context Context.\n */\nexport function releaseCanvas(context) {\n const canvas = context.canvas;\n canvas.width = 1;\n canvas.height = 1;\n context.clearRect(0, 0, 1, 1);\n}\n\n/**\n * Get the current computed width for the given element including margin,\n * padding and border.\n * Equivalent to jQuery's `$(el).outerWidth(true)`.\n * @param {!HTMLElement} element Element.\n * @return {number} The width.\n */\nexport function outerWidth(element) {\n let width = element.offsetWidth;\n const style = getComputedStyle(element);\n width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);\n\n return width;\n}\n\n/**\n * Get the current computed height for the given element including margin,\n * padding and border.\n * Equivalent to jQuery's `$(el).outerHeight(true)`.\n * @param {!HTMLElement} element Element.\n * @return {number} The height.\n */\nexport function outerHeight(element) {\n let height = element.offsetHeight;\n const style = getComputedStyle(element);\n height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);\n\n return height;\n}\n\n/**\n * @param {Node} newNode Node to replace old node\n * @param {Node} oldNode The node to be replaced\n */\nexport function replaceNode(newNode, oldNode) {\n const parent = oldNode.parentNode;\n if (parent) {\n parent.replaceChild(newNode, oldNode);\n }\n}\n\n/**\n * @param {Node} node The node to remove the children from.\n */\nexport function removeChildren(node) {\n while (node.lastChild) {\n node.lastChild.remove();\n }\n}\n\n/**\n * Transform the children of a parent node so they match the\n * provided list of children. This function aims to efficiently\n * remove, add, and reorder child nodes while maintaining a simple\n * implementation (it is not guaranteed to minimize DOM operations).\n * @param {Node} node The parent node whose children need reworking.\n * @param {Array} children The desired children.\n */\nexport function replaceChildren(node, children) {\n const oldChildren = node.childNodes;\n\n for (let i = 0; true; ++i) {\n const oldChild = oldChildren[i];\n const newChild = children[i];\n\n // check if our work is done\n if (!oldChild && !newChild) {\n break;\n }\n\n // check if children match\n if (oldChild === newChild) {\n continue;\n }\n\n // check if a new child needs to be added\n if (!oldChild) {\n node.appendChild(newChild);\n continue;\n }\n\n // check if an old child needs to be removed\n if (!newChild) {\n node.removeChild(oldChild);\n --i;\n continue;\n }\n\n // reorder\n node.insertBefore(newChild, oldChild);\n }\n}\n","/**\n * @module ol/Disposable\n */\n\n/**\n * @classdesc\n * Objects that need to clean up after themselves.\n */\nclass Disposable {\n constructor() {\n /**\n * The object has already been disposed.\n * @type {boolean}\n * @protected\n */\n this.disposed = false;\n }\n\n /**\n * Clean up.\n */\n dispose() {\n if (!this.disposed) {\n this.disposed = true;\n this.disposeInternal();\n }\n }\n\n /**\n * Extension point for disposable objects.\n * @protected\n */\n disposeInternal() {}\n}\n\nexport default Disposable;\n","/**\n * @module ol/events/Event\n */\n\n/**\n * @classdesc\n * Stripped down implementation of the W3C DOM Level 2 Event interface.\n * See https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface.\n *\n * This implementation only provides `type` and `target` properties, and\n * `stopPropagation` and `preventDefault` methods. It is meant as base class\n * for higher level events defined in the library, and works with\n * {@link module:ol/events/Target~Target}.\n */\nclass BaseEvent {\n /**\n * @param {string} type Type.\n */\n constructor(type) {\n /**\n * @type {boolean}\n */\n this.propagationStopped;\n\n /**\n * @type {boolean}\n */\n this.defaultPrevented;\n\n /**\n * The event type.\n * @type {string}\n * @api\n */\n this.type = type;\n\n /**\n * The event target.\n * @type {Object}\n * @api\n */\n this.target = null;\n }\n\n /**\n * Prevent default. This means that no emulated `click`, `singleclick` or `doubleclick` events\n * will be fired.\n * @api\n */\n preventDefault() {\n this.defaultPrevented = true;\n }\n\n /**\n * Stop event propagation.\n * @api\n */\n stopPropagation() {\n this.propagationStopped = true;\n }\n}\n\n/**\n * @param {Event|import(\"./Event.js\").default} evt Event\n */\nexport function stopPropagation(evt) {\n evt.stopPropagation();\n}\n\n/**\n * @param {Event|import(\"./Event.js\").default} evt Event\n */\nexport function preventDefault(evt) {\n evt.preventDefault();\n}\n\nexport default BaseEvent;\n","/**\n * @module ol/functions\n */\n\nimport {equals as arrayEquals} from './array.js';\n\n/**\n * Always returns true.\n * @return {boolean} true.\n */\nexport function TRUE() {\n return true;\n}\n\n/**\n * Always returns false.\n * @return {boolean} false.\n */\nexport function FALSE() {\n return false;\n}\n\n/**\n * A reusable function, used e.g. as a default for callbacks.\n *\n * @return {void} Nothing.\n */\nexport function VOID() {}\n\n/**\n * Wrap a function in another function that remembers the last return. If the\n * returned function is called twice in a row with the same arguments and the same\n * this object, it will return the value from the first call in the second call.\n *\n * @param {function(...any): ReturnType} fn The function to memoize.\n * @return {function(...any): ReturnType} The memoized function.\n * @template ReturnType\n */\nexport function memoizeOne(fn) {\n /** @type {ReturnType} */\n let lastResult;\n\n /** @type {Array|undefined} */\n let lastArgs;\n\n let lastThis;\n\n /**\n * @this {*} Only need to know if `this` changed, don't care what type\n * @return {ReturnType} Memoized value\n */\n return function () {\n const nextArgs = Array.prototype.slice.call(arguments);\n if (!lastArgs || this !== lastThis || !arrayEquals(nextArgs, lastArgs)) {\n lastThis = this;\n lastArgs = nextArgs;\n lastResult = fn.apply(this, arguments);\n }\n return lastResult;\n };\n}\n\n/**\n * @template T\n * @param {function(): (T | Promise)} getter A function that returns a value or a promise for a value.\n * @return {Promise} A promise for the value.\n */\nexport function toPromise(getter) {\n function promiseGetter() {\n let value;\n try {\n value = getter();\n } catch (err) {\n return Promise.reject(err);\n }\n if (value instanceof Promise) {\n return value;\n }\n return Promise.resolve(value);\n }\n return promiseGetter();\n}\n","/**\n * @module ol/obj\n */\n\n/**\n * Removes all properties from an object.\n * @param {Object} object The object to clear.\n */\nexport function clear(object) {\n for (const property in object) {\n delete object[property];\n }\n}\n\n/**\n * Determine if an object has any properties.\n * @param {Object} object The object to check.\n * @return {boolean} The object is empty.\n */\nexport function isEmpty(object) {\n let property;\n for (property in object) {\n return false;\n }\n return !property;\n}\n","/**\n * @module ol/events/Target\n */\nimport Disposable from '../Disposable.js';\nimport Event from './Event.js';\nimport {VOID} from '../functions.js';\nimport {clear} from '../obj.js';\n\n/**\n * @typedef {EventTarget|Target} EventTargetLike\n */\n\n/**\n * @classdesc\n * A simplified implementation of the W3C DOM Level 2 EventTarget interface.\n * See https://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget.\n *\n * There are two important simplifications compared to the specification:\n *\n * 1. The handling of `useCapture` in `addEventListener` and\n * `removeEventListener`. There is no real capture model.\n * 2. The handling of `stopPropagation` and `preventDefault` on `dispatchEvent`.\n * There is no event target hierarchy. When a listener calls\n * `stopPropagation` or `preventDefault` on an event object, it means that no\n * more listeners after this one will be called. Same as when the listener\n * returns false.\n */\nclass Target extends Disposable {\n /**\n * @param {*} [target] Default event target for dispatched events.\n */\n constructor(target) {\n super();\n\n /**\n * @private\n * @type {*}\n */\n this.eventTarget_ = target;\n\n /**\n * @private\n * @type {Object|null}\n */\n this.pendingRemovals_ = null;\n\n /**\n * @private\n * @type {Object|null}\n */\n this.dispatching_ = null;\n\n /**\n * @private\n * @type {Object>|null}\n */\n this.listeners_ = null;\n }\n\n /**\n * @param {string} type Type.\n * @param {import(\"../events.js\").Listener} listener Listener.\n */\n addEventListener(type, listener) {\n if (!type || !listener) {\n return;\n }\n const listeners = this.listeners_ || (this.listeners_ = {});\n const listenersForType = listeners[type] || (listeners[type] = []);\n if (!listenersForType.includes(listener)) {\n listenersForType.push(listener);\n }\n }\n\n /**\n * Dispatches an event and calls all listeners listening for events\n * of this type. The event parameter can either be a string or an\n * Object with a `type` property.\n *\n * @param {import(\"./Event.js\").default|string} event Event object.\n * @return {boolean|undefined} `false` if anyone called preventDefault on the\n * event object or if any of the listeners returned false.\n * @api\n */\n dispatchEvent(event) {\n const isString = typeof event === 'string';\n const type = isString ? event : event.type;\n const listeners = this.listeners_ && this.listeners_[type];\n if (!listeners) {\n return;\n }\n\n const evt = isString ? new Event(event) : /** @type {Event} */ (event);\n if (!evt.target) {\n evt.target = this.eventTarget_ || this;\n }\n const dispatching = this.dispatching_ || (this.dispatching_ = {});\n const pendingRemovals =\n this.pendingRemovals_ || (this.pendingRemovals_ = {});\n if (!(type in dispatching)) {\n dispatching[type] = 0;\n pendingRemovals[type] = 0;\n }\n ++dispatching[type];\n let propagate;\n for (let i = 0, ii = listeners.length; i < ii; ++i) {\n if ('handleEvent' in listeners[i]) {\n propagate = /** @type {import(\"../events.js\").ListenerObject} */ (\n listeners[i]\n ).handleEvent(evt);\n } else {\n propagate = /** @type {import(\"../events.js\").ListenerFunction} */ (\n listeners[i]\n ).call(this, evt);\n }\n if (propagate === false || evt.propagationStopped) {\n propagate = false;\n break;\n }\n }\n if (--dispatching[type] === 0) {\n let pr = pendingRemovals[type];\n delete pendingRemovals[type];\n while (pr--) {\n this.removeEventListener(type, VOID);\n }\n delete dispatching[type];\n }\n return propagate;\n }\n\n /**\n * Clean up.\n * @override\n */\n disposeInternal() {\n this.listeners_ && clear(this.listeners_);\n }\n\n /**\n * Get the listeners for a specified event type. Listeners are returned in the\n * order that they will be called in.\n *\n * @param {string} type Type.\n * @return {Array|undefined} Listeners.\n */\n getListeners(type) {\n return (this.listeners_ && this.listeners_[type]) || undefined;\n }\n\n /**\n * @param {string} [type] Type. If not provided,\n * `true` will be returned if this event target has any listeners.\n * @return {boolean} Has listeners.\n */\n hasListener(type) {\n if (!this.listeners_) {\n return false;\n }\n return type\n ? type in this.listeners_\n : Object.keys(this.listeners_).length > 0;\n }\n\n /**\n * @param {string} type Type.\n * @param {import(\"../events.js\").Listener} listener Listener.\n */\n removeEventListener(type, listener) {\n if (!this.listeners_) {\n return;\n }\n const listeners = this.listeners_[type];\n if (!listeners) {\n return;\n }\n const index = listeners.indexOf(listener);\n if (index !== -1) {\n if (this.pendingRemovals_ && type in this.pendingRemovals_) {\n // make listener a no-op, and remove later in #dispatchEvent()\n listeners[index] = VOID;\n ++this.pendingRemovals_[type];\n } else {\n listeners.splice(index, 1);\n if (listeners.length === 0) {\n delete this.listeners_[type];\n }\n }\n }\n }\n}\n\nexport default Target;\n","/**\n * @module ol/events/EventType\n */\n\n/**\n * @enum {string}\n * @const\n */\nexport default {\n /**\n * Generic change event. Triggered when the revision counter is increased.\n * @event module:ol/events/Event~BaseEvent#change\n * @api\n */\n CHANGE: 'change',\n\n /**\n * Generic error event. Triggered when an error occurs.\n * @event module:ol/events/Event~BaseEvent#error\n * @api\n */\n ERROR: 'error',\n\n BLUR: 'blur',\n CLEAR: 'clear',\n CONTEXTMENU: 'contextmenu',\n CLICK: 'click',\n DBLCLICK: 'dblclick',\n DRAGENTER: 'dragenter',\n DRAGOVER: 'dragover',\n DROP: 'drop',\n FOCUS: 'focus',\n KEYDOWN: 'keydown',\n KEYPRESS: 'keypress',\n LOAD: 'load',\n RESIZE: 'resize',\n TOUCHMOVE: 'touchmove',\n WHEEL: 'wheel',\n};\n","/**\n * @module ol/events\n */\nimport {clear} from './obj.js';\n\n/**\n * Key to use with {@link module:ol/Observable.unByKey}.\n * @typedef {Object} EventsKey\n * @property {ListenerFunction} listener Listener.\n * @property {import(\"./events/Target.js\").EventTargetLike} target Target.\n * @property {string} type Type.\n * @api\n */\n\n/**\n * Listener function. This function is called with an event object as argument.\n * When the function returns `false`, event propagation will stop.\n *\n * @typedef {function((Event|import(\"./events/Event.js\").default)): (void|boolean)} ListenerFunction\n * @api\n */\n\n/**\n * @typedef {Object} ListenerObject\n * @property {ListenerFunction} handleEvent HandleEvent listener function.\n */\n\n/**\n * @typedef {ListenerFunction|ListenerObject} Listener\n */\n\n/**\n * Registers an event listener on an event target. Inspired by\n * https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html\n *\n * This function efficiently binds a `listener` to a `this` object, and returns\n * a key for use with {@link module:ol/events.unlistenByKey}.\n *\n * @param {import(\"./events/Target.js\").EventTargetLike} target Event target.\n * @param {string} type Event type.\n * @param {ListenerFunction} listener Listener.\n * @param {Object} [thisArg] Object referenced by the `this` keyword in the\n * listener. Default is the `target`.\n * @param {boolean} [once] If true, add the listener as one-off listener.\n * @return {EventsKey} Unique key for the listener.\n */\nexport function listen(target, type, listener, thisArg, once) {\n if (once) {\n const originalListener = listener;\n /**\n * @this {typeof target}\n */\n listener = function () {\n target.removeEventListener(type, listener);\n originalListener.apply(thisArg ?? this, arguments);\n };\n } else if (thisArg && thisArg !== target) {\n listener = listener.bind(thisArg);\n }\n const eventsKey = {\n target: target,\n type: type,\n listener: listener,\n };\n target.addEventListener(type, listener);\n return eventsKey;\n}\n\n/**\n * Registers a one-off event listener on an event target. Inspired by\n * https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html\n *\n * This function efficiently binds a `listener` as self-unregistering listener\n * to a `this` object, and returns a key for use with\n * {@link module:ol/events.unlistenByKey} in case the listener needs to be\n * unregistered before it is called.\n *\n * When {@link module:ol/events.listen} is called with the same arguments after this\n * function, the self-unregistering listener will be turned into a permanent\n * listener.\n *\n * @param {import(\"./events/Target.js\").EventTargetLike} target Event target.\n * @param {string} type Event type.\n * @param {ListenerFunction} listener Listener.\n * @param {Object} [thisArg] Object referenced by the `this` keyword in the\n * listener. Default is the `target`.\n * @return {EventsKey} Key for unlistenByKey.\n */\nexport function listenOnce(target, type, listener, thisArg) {\n return listen(target, type, listener, thisArg, true);\n}\n\n/**\n * Unregisters event listeners on an event target. Inspired by\n * https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html\n *\n * The argument passed to this function is the key returned from\n * {@link module:ol/events.listen} or {@link module:ol/events.listenOnce}.\n *\n * @param {EventsKey} key The key.\n */\nexport function unlistenByKey(key) {\n if (key && key.target) {\n key.target.removeEventListener(key.type, key.listener);\n clear(key);\n }\n}\n","/**\n * @module ol/style/IconImageCache\n */\nimport ImageState from '../ImageState.js';\nimport {asArray} from '../color.js';\nimport {getSharedCanvasContext2D} from '../dom.js';\n\n/**\n * @classdesc\n * Singleton class. Available through {@link module:ol/style/IconImageCache.shared}.\n */\nclass IconImageCache {\n constructor() {\n /**\n * @type {!Object}\n * @private\n */\n this.cache_ = {};\n\n /**\n * @type {!Object}\n * @private\n */\n this.patternCache_ = {};\n\n /**\n * @type {number}\n * @private\n */\n this.cacheSize_ = 0;\n\n /**\n * @type {number}\n * @private\n */\n this.maxCacheSize_ = 32;\n }\n\n /**\n * FIXME empty description for jsdoc\n */\n clear() {\n this.cache_ = {};\n this.patternCache_ = {};\n this.cacheSize_ = 0;\n }\n\n /**\n * @return {boolean} Can expire cache.\n */\n canExpireCache() {\n return this.cacheSize_ > this.maxCacheSize_;\n }\n\n /**\n * FIXME empty description for jsdoc\n */\n expire() {\n if (this.canExpireCache()) {\n let i = 0;\n for (const key in this.cache_) {\n const iconImage = this.cache_[key];\n if ((i++ & 3) === 0 && !iconImage.hasListener()) {\n delete this.cache_[key];\n delete this.patternCache_[key];\n --this.cacheSize_;\n }\n }\n }\n }\n\n /**\n * @param {string} src Src.\n * @param {?string} crossOrigin Cross origin.\n * @param {import(\"../color.js\").Color|string|null} color Color.\n * @return {import(\"./IconImage.js\").default} Icon image.\n */\n get(src, crossOrigin, color) {\n const key = getCacheKey(src, crossOrigin, color);\n return key in this.cache_ ? this.cache_[key] : null;\n }\n\n /**\n * @param {string} src Src.\n * @param {?string} crossOrigin Cross origin.\n * @param {import(\"../color.js\").Color|string|null} color Color.\n * @return {CanvasPattern} Icon image.\n */\n getPattern(src, crossOrigin, color) {\n const key = getCacheKey(src, crossOrigin, color);\n return key in this.patternCache_ ? this.patternCache_[key] : null;\n }\n\n /**\n * @param {string} src Src.\n * @param {?string} crossOrigin Cross origin.\n * @param {import(\"../color.js\").Color|string|null} color Color.\n * @param {import(\"./IconImage.js\").default|null} iconImage Icon image.\n * @param {boolean} [pattern] Also cache a `'repeat'` pattern with this `iconImage`.\n */\n set(src, crossOrigin, color, iconImage, pattern) {\n const key = getCacheKey(src, crossOrigin, color);\n const update = key in this.cache_;\n this.cache_[key] = iconImage;\n if (pattern) {\n if (iconImage.getImageState() === ImageState.IDLE) {\n iconImage.load();\n }\n if (iconImage.getImageState() === ImageState.LOADING) {\n iconImage.ready().then(() => {\n this.patternCache_[key] = getSharedCanvasContext2D().createPattern(\n iconImage.getImage(1),\n 'repeat',\n );\n });\n } else {\n this.patternCache_[key] = getSharedCanvasContext2D().createPattern(\n iconImage.getImage(1),\n 'repeat',\n );\n }\n }\n if (!update) {\n ++this.cacheSize_;\n }\n }\n\n /**\n * Set the cache size of the icon cache. Default is `32`. Change this value when\n * your map uses more than 32 different icon images and you are not caching icon\n * styles on the application level.\n * @param {number} maxCacheSize Cache max size.\n * @api\n */\n setSize(maxCacheSize) {\n this.maxCacheSize_ = maxCacheSize;\n this.expire();\n }\n}\n\n/**\n * @param {string} src Src.\n * @param {?string} crossOrigin Cross origin.\n * @param {import(\"../color.js\").Color|string|null} color Color.\n * @return {string} Cache key.\n */\nexport function getCacheKey(src, crossOrigin, color) {\n const colorString = color ? asArray(color) : 'null';\n return crossOrigin + ':' + src + ':' + colorString;\n}\n\nexport default IconImageCache;\n\n/**\n * The {@link module:ol/style/IconImageCache~IconImageCache} for\n * {@link module:ol/style/Icon~Icon} images.\n * @api\n */\nexport const shared = new IconImageCache();\n","/**\n * @module ol/ImageState\n */\n\n/**\n * @enum {number}\n */\nexport default {\n IDLE: 0,\n LOADING: 1,\n LOADED: 2,\n ERROR: 3,\n EMPTY: 4,\n};\n","/**\n * @module ol/style/IconImage\n */\n\nimport EventTarget from '../events/Target.js';\nimport EventType from '../events/EventType.js';\nimport ImageState from '../ImageState.js';\nimport {asString} from '../color.js';\nimport {createCanvasContext2D} from '../dom.js';\nimport {decodeFallback} from '../Image.js';\nimport {shared as iconImageCache} from './IconImageCache.js';\n\n/**\n * @type {CanvasRenderingContext2D}\n */\nlet taintedTestContext = null;\n\nclass IconImage extends EventTarget {\n /**\n * @param {HTMLImageElement|HTMLCanvasElement|ImageBitmap|null} image Image.\n * @param {string|undefined} src Src.\n * @param {?string} crossOrigin Cross origin.\n * @param {import(\"../ImageState.js\").default|undefined} imageState Image state.\n * @param {import(\"../color.js\").Color|string|null} color Color.\n */\n constructor(image, src, crossOrigin, imageState, color) {\n super();\n\n /**\n * @private\n * @type {HTMLImageElement|HTMLCanvasElement|ImageBitmap}\n */\n this.hitDetectionImage_ = null;\n\n /**\n * @private\n * @type {HTMLImageElement|HTMLCanvasElement|ImageBitmap|null}\n */\n this.image_ = image;\n\n /**\n * @private\n * @type {string|null}\n */\n this.crossOrigin_ = crossOrigin;\n\n /**\n * @private\n * @type {Object}\n */\n this.canvas_ = {};\n\n /**\n * @private\n * @type {import(\"../color.js\").Color|string|null}\n */\n this.color_ = color;\n\n /**\n * @private\n * @type {import(\"../ImageState.js\").default}\n */\n this.imageState_ = imageState === undefined ? ImageState.IDLE : imageState;\n\n /**\n * @private\n * @type {import(\"../size.js\").Size|null}\n */\n this.size_ =\n image && image.width && image.height ? [image.width, image.height] : null;\n\n /**\n * @private\n * @type {string|undefined}\n */\n this.src_ = src;\n\n /**\n * @private\n */\n this.tainted_;\n\n /**\n * @private\n * @type {Promise|null}\n */\n this.ready_ = null;\n }\n\n /**\n * @private\n */\n initializeImage_() {\n this.image_ = new Image();\n if (this.crossOrigin_ !== null) {\n this.image_.crossOrigin = this.crossOrigin_;\n }\n }\n\n /**\n * @private\n * @return {boolean} The image canvas is tainted.\n */\n isTainted_() {\n if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {\n if (!taintedTestContext) {\n taintedTestContext = createCanvasContext2D(1, 1, undefined, {\n willReadFrequently: true,\n });\n }\n taintedTestContext.drawImage(this.image_, 0, 0);\n try {\n taintedTestContext.getImageData(0, 0, 1, 1);\n this.tainted_ = false;\n } catch (e) {\n taintedTestContext = null;\n this.tainted_ = true;\n }\n }\n return this.tainted_ === true;\n }\n\n /**\n * @private\n */\n dispatchChangeEvent_() {\n this.dispatchEvent(EventType.CHANGE);\n }\n\n /**\n * @private\n */\n handleImageError_() {\n this.imageState_ = ImageState.ERROR;\n this.dispatchChangeEvent_();\n }\n\n /**\n * @private\n */\n handleImageLoad_() {\n this.imageState_ = ImageState.LOADED;\n this.size_ = [this.image_.width, this.image_.height];\n this.dispatchChangeEvent_();\n }\n\n /**\n * @param {number} pixelRatio Pixel ratio.\n * @return {HTMLImageElement|HTMLCanvasElement|ImageBitmap} Image or Canvas element or image bitmap.\n */\n getImage(pixelRatio) {\n if (!this.image_) {\n this.initializeImage_();\n }\n this.replaceColor_(pixelRatio);\n return this.canvas_[pixelRatio] ? this.canvas_[pixelRatio] : this.image_;\n }\n\n /**\n * @param {number} pixelRatio Pixel ratio.\n * @return {number} Image or Canvas element.\n */\n getPixelRatio(pixelRatio) {\n this.replaceColor_(pixelRatio);\n return this.canvas_[pixelRatio] ? pixelRatio : 1;\n }\n\n /**\n * @return {import(\"../ImageState.js\").default} Image state.\n */\n getImageState() {\n return this.imageState_;\n }\n\n /**\n * @return {HTMLImageElement|HTMLCanvasElement|ImageBitmap} Image element.\n */\n getHitDetectionImage() {\n if (!this.image_) {\n this.initializeImage_();\n }\n if (!this.hitDetectionImage_) {\n if (this.isTainted_()) {\n const width = this.size_[0];\n const height = this.size_[1];\n const context = createCanvasContext2D(width, height);\n context.fillRect(0, 0, width, height);\n this.hitDetectionImage_ = context.canvas;\n } else {\n this.hitDetectionImage_ = this.image_;\n }\n }\n return this.hitDetectionImage_;\n }\n\n /**\n * Get the size of the icon (in pixels).\n * @return {import(\"../size.js\").Size} Image size.\n */\n getSize() {\n return this.size_;\n }\n\n /**\n * @return {string|undefined} Image src.\n */\n getSrc() {\n return this.src_;\n }\n\n /**\n * Load not yet loaded URI.\n */\n load() {\n if (this.imageState_ !== ImageState.IDLE) {\n return;\n }\n if (!this.image_) {\n this.initializeImage_();\n }\n\n this.imageState_ = ImageState.LOADING;\n try {\n if (this.src_ !== undefined) {\n /** @type {HTMLImageElement} */ (this.image_).src = this.src_;\n }\n } catch (e) {\n this.handleImageError_();\n }\n if (this.image_ instanceof HTMLImageElement) {\n decodeFallback(this.image_, this.src_)\n .then((image) => {\n this.image_ = image;\n this.handleImageLoad_();\n })\n .catch(this.handleImageError_.bind(this));\n }\n }\n\n /**\n * @param {number} pixelRatio Pixel ratio.\n * @private\n */\n replaceColor_(pixelRatio) {\n if (\n !this.color_ ||\n this.canvas_[pixelRatio] ||\n this.imageState_ !== ImageState.LOADED\n ) {\n return;\n }\n\n const image = this.image_;\n const canvas = document.createElement('canvas');\n canvas.width = Math.ceil(image.width * pixelRatio);\n canvas.height = Math.ceil(image.height * pixelRatio);\n\n const ctx = canvas.getContext('2d');\n ctx.scale(pixelRatio, pixelRatio);\n ctx.drawImage(image, 0, 0);\n\n ctx.globalCompositeOperation = 'multiply';\n ctx.fillStyle = asString(this.color_);\n ctx.fillRect(0, 0, canvas.width / pixelRatio, canvas.height / pixelRatio);\n\n ctx.globalCompositeOperation = 'destination-in';\n ctx.drawImage(image, 0, 0);\n\n this.canvas_[pixelRatio] = canvas;\n }\n\n /**\n * @return {Promise} Promise that resolves when the image is loaded.\n */\n ready() {\n if (!this.ready_) {\n this.ready_ = new Promise((resolve) => {\n if (\n this.imageState_ === ImageState.LOADED ||\n this.imageState_ === ImageState.ERROR\n ) {\n resolve();\n } else {\n const onChange = () => {\n if (\n this.imageState_ === ImageState.LOADED ||\n this.imageState_ === ImageState.ERROR\n ) {\n this.removeEventListener(EventType.CHANGE, onChange);\n resolve();\n }\n };\n this.addEventListener(EventType.CHANGE, onChange);\n }\n });\n }\n return this.ready_;\n }\n}\n\n/**\n * @param {HTMLImageElement|HTMLCanvasElement|ImageBitmap|null} image Image.\n * @param {string|undefined} cacheKey Src.\n * @param {?string} crossOrigin Cross origin.\n * @param {import(\"../ImageState.js\").default|undefined} imageState Image state.\n * @param {import(\"../color.js\").Color|string|null} color Color.\n * @param {boolean} [pattern] Also cache a `repeat` pattern with the icon image.\n * @return {IconImage} Icon image.\n */\nexport function get(image, cacheKey, crossOrigin, imageState, color, pattern) {\n let iconImage =\n cacheKey === undefined\n ? undefined\n : iconImageCache.get(cacheKey, crossOrigin, color);\n if (!iconImage) {\n iconImage = new IconImage(\n image,\n image && 'src' in image ? image.src || undefined : cacheKey,\n crossOrigin,\n imageState,\n color,\n );\n iconImageCache.set(cacheKey, crossOrigin, color, iconImage, pattern);\n }\n if (\n pattern &&\n iconImage &&\n !iconImageCache.getPattern(cacheKey, crossOrigin, color)\n ) {\n iconImageCache.set(cacheKey, crossOrigin, color, iconImage, pattern);\n }\n return iconImage;\n}\n\nexport default IconImage;\n","/**\n * @module ol/Image\n */\nimport EventTarget from './events/Target.js';\nimport EventType from './events/EventType.js';\nimport ImageState from './ImageState.js';\nimport {CREATE_IMAGE_BITMAP, IMAGE_DECODE} from './has.js';\nimport {listenOnce, unlistenByKey} from './events.js';\nimport {toPromise} from './functions.js';\n\n/**\n * A function that takes an {@link module:ol/Image~ImageWrapper} for the image and a\n * `{string}` for the src as arguments. It is supposed to make it so the\n * underlying image {@link module:ol/Image~ImageWrapper#getImage} is assigned the\n * content specified by the src. If not specified, the default is\n *\n * function(image, src) {\n * image.getImage().src = src;\n * }\n *\n * Providing a custom `imageLoadFunction` can be useful to load images with\n * post requests or - in general - through XHR requests, where the src of the\n * image element would be set to a data URI when the content is loaded.\n *\n * @typedef {function(import(\"./Image.js\").default, string): void} LoadFunction\n * @api\n */\n\n/**\n * @typedef {Object} ImageObject\n * @property {import(\"./extent.js\").Extent} [extent] Extent, if different from the requested one.\n * @property {import(\"./resolution.js\").ResolutionLike} [resolution] Resolution, if different from the requested one.\n * When x and y resolution are different, use the array type (`[xResolution, yResolution]`).\n * @property {number} [pixelRatio] Pixel ratio, if different from the requested one.\n * @property {import('./DataTile.js').ImageLike} image Image.\n */\n\n/**\n * Loader function used for image sources. Receives extent, resolution and pixel ratio as arguments.\n * For images that cover any extent and resolution (static images), the loader function should not accept\n * any arguments. The function returns an {@link import(\"./DataTile.js\").ImageLike image}, an\n * {@link import(\"./Image.js\").ImageObject image object}, or a promise for the same.\n * For loaders that generate images, the promise should not resolve until the image is loaded.\n * If the returned image does not match the extent, resolution or pixel ratio passed to the loader,\n * it has to return an {@link import(\"./Image.js\").ImageObject image object} with the `image` and the\n * correct `extent`, `resolution` and `pixelRatio`.\n *\n * @typedef {function(import(\"./extent.js\").Extent, number, number, (function(HTMLImageElement, string): void)=): import(\"./DataTile.js\").ImageLike|ImageObject|Promise} Loader\n * @api\n */\n\n/**\n * Loader function used for image sources. Receives extent, resolution and pixel ratio as arguments.\n * The function returns a promise for an {@link import(\"./Image.js\").ImageObject image object}.\n *\n * @typedef {function(import(\"./extent.js\").Extent, number, number, (function(HTMLImageElement, string): void)=): import(\"./DataTile.js\").ImageLike|ImageObject|Promise} ImageObjectPromiseLoader\n */\n\nclass ImageWrapper extends EventTarget {\n /**\n * @param {import(\"./extent.js\").Extent} extent Extent.\n * @param {number|Array|undefined} resolution Resolution. If provided as array, x and y\n * resolution will be assumed.\n * @param {number} pixelRatio Pixel ratio.\n * @param {import(\"./ImageState.js\").default|import(\"./Image.js\").Loader} stateOrLoader State.\n */\n constructor(extent, resolution, pixelRatio, stateOrLoader) {\n super();\n\n /**\n * @protected\n * @type {import(\"./extent.js\").Extent}\n */\n this.extent = extent;\n\n /**\n * @private\n * @type {number}\n */\n this.pixelRatio_ = pixelRatio;\n\n /**\n * @protected\n * @type {number|Array|undefined}\n */\n this.resolution = resolution;\n\n /**\n * @protected\n * @type {import(\"./ImageState.js\").default}\n */\n this.state =\n typeof stateOrLoader === 'function' ? ImageState.IDLE : stateOrLoader;\n\n /**\n * @private\n * @type {import('./DataTile.js').ImageLike|null}\n */\n this.image_ = null;\n\n /**\n * @protected\n * @type {import(\"./Image.js\").Loader}\n */\n this.loader = typeof stateOrLoader === 'function' ? stateOrLoader : null;\n }\n\n /**\n * @protected\n */\n changed() {\n this.dispatchEvent(EventType.CHANGE);\n }\n\n /**\n * @return {import(\"./extent.js\").Extent} Extent.\n */\n getExtent() {\n return this.extent;\n }\n\n /**\n * @return {import('./DataTile.js').ImageLike} Image.\n */\n getImage() {\n return this.image_;\n }\n\n /**\n * @return {number} PixelRatio.\n */\n getPixelRatio() {\n return this.pixelRatio_;\n }\n\n /**\n * @return {number|Array} Resolution.\n */\n getResolution() {\n return /** @type {number} */ (this.resolution);\n }\n\n /**\n * @return {import(\"./ImageState.js\").default} State.\n */\n getState() {\n return this.state;\n }\n\n /**\n * Load not yet loaded URI.\n */\n load() {\n if (this.state == ImageState.IDLE) {\n if (this.loader) {\n this.state = ImageState.LOADING;\n this.changed();\n const resolution = this.getResolution();\n const requestResolution = Array.isArray(resolution)\n ? resolution[0]\n : resolution;\n toPromise(() =>\n this.loader(\n this.getExtent(),\n requestResolution,\n this.getPixelRatio(),\n ),\n )\n .then((image) => {\n if ('image' in image) {\n this.image_ = image.image;\n }\n if ('extent' in image) {\n this.extent = image.extent;\n }\n if ('resolution' in image) {\n this.resolution = image.resolution;\n }\n if ('pixelRatio' in image) {\n this.pixelRatio_ = image.pixelRatio;\n }\n if (\n image instanceof HTMLImageElement ||\n image instanceof ImageBitmap ||\n image instanceof HTMLCanvasElement ||\n image instanceof HTMLVideoElement\n ) {\n this.image_ = image;\n }\n this.state = ImageState.LOADED;\n })\n .catch((error) => {\n this.state = ImageState.ERROR;\n console.error(error); // eslint-disable-line no-console\n })\n .finally(() => this.changed());\n }\n }\n }\n\n /**\n * @param {import('./DataTile.js').ImageLike} image The image.\n */\n setImage(image) {\n this.image_ = image;\n }\n\n /**\n * @param {number|Array} resolution Resolution.\n */\n setResolution(resolution) {\n this.resolution = resolution;\n }\n}\n\n/**\n * @param {import('./DataTile.js').ImageLike} image Image element.\n * @param {function():any} loadHandler Load callback function.\n * @param {function():any} errorHandler Error callback function.\n * @return {function():void} Callback to stop listening.\n */\nexport function listenImage(image, loadHandler, errorHandler) {\n const img = /** @type {HTMLImageElement} */ (image);\n let listening = true;\n let decoding = false;\n let loaded = false;\n\n const listenerKeys = [\n listenOnce(img, EventType.LOAD, function () {\n loaded = true;\n if (!decoding) {\n loadHandler();\n }\n }),\n ];\n\n if (img.src && IMAGE_DECODE) {\n decoding = true;\n img\n .decode()\n .then(function () {\n if (listening) {\n loadHandler();\n }\n })\n .catch(function (error) {\n if (listening) {\n if (loaded) {\n loadHandler();\n } else {\n errorHandler();\n }\n }\n });\n } else {\n listenerKeys.push(listenOnce(img, EventType.ERROR, errorHandler));\n }\n\n return function unlisten() {\n listening = false;\n listenerKeys.forEach(unlistenByKey);\n };\n}\n\n/**\n * Loads an image.\n * @param {HTMLImageElement} image Image, not yet loaded.\n * @param {string} [src] `src` attribute of the image. Optional, not required if already present.\n * @return {Promise} Promise resolving to an `HTMLImageElement`.\n * @api\n */\nexport function load(image, src) {\n return new Promise((resolve, reject) => {\n function handleLoad() {\n unlisten();\n resolve(image);\n }\n function handleError() {\n unlisten();\n reject(new Error('Image load error'));\n }\n function unlisten() {\n image.removeEventListener('load', handleLoad);\n image.removeEventListener('error', handleError);\n }\n image.addEventListener('load', handleLoad);\n image.addEventListener('error', handleError);\n if (src) {\n image.src = src;\n }\n });\n}\n\n/**\n * @param {HTMLImageElement} image Image, not yet loaded.\n * @param {string} [src] `src` attribute of the image. Optional, not required if already present.\n * @return {Promise} Promise resolving to an `HTMLImageElement`.\n */\nexport function decodeFallback(image, src) {\n if (src) {\n image.src = src;\n }\n return image.src && IMAGE_DECODE\n ? new Promise((resolve, reject) =>\n image\n .decode()\n .then(() => resolve(image))\n .catch((e) =>\n image.complete && image.width ? resolve(image) : reject(e),\n ),\n )\n : load(image);\n}\n\n/**\n * Loads an image and decodes it to an `ImageBitmap` if `createImageBitmap()` is supported. Returns\n * the loaded image otherwise.\n * @param {HTMLImageElement} image Image, not yet loaded.\n * @param {string} [src] `src` attribute of the image. Optional, not required if already present.\n * @return {Promise} Promise resolving to an `ImageBitmap` or an\n * `HTMLImageElement` if `createImageBitmap()` is not supported.\n * @api\n */\nexport function decode(image, src) {\n if (src) {\n image.src = src;\n }\n return image.src && IMAGE_DECODE && CREATE_IMAGE_BITMAP\n ? image\n .decode()\n .then(() => createImageBitmap(image))\n .catch((e) => {\n if (image.complete && image.width) {\n return image;\n }\n throw e;\n })\n : decodeFallback(image);\n}\n\nexport default ImageWrapper;\n","/**\n * @module ol/colorlike\n */\nimport ImageState from './ImageState.js';\nimport {createCanvasContext2D} from './dom.js';\nimport {get as getIconImage} from './style/IconImage.js';\nimport {shared as iconCache} from './style/IconImageCache.js';\nimport {toString} from './color.js';\n\n/**\n * @typedef {Object} PatternDescriptor\n * @property {string} src Pattern image URL\n * @property {import(\"./color.js\").Color|string} [color] Color to tint the pattern with.\n * @property {import(\"./size.js\").Size} [size] Size of the desired slice from the pattern image.\n * Use this together with `offset` when the pattern image is a sprite sheet.\n * @property {import(\"./size.js\").Size} [offset] Offset of the desired slice from the pattern image.\n * Use this together with `size` when the pattern image is a sprite sheet.\n */\n\n/**\n * A type accepted by CanvasRenderingContext2D.fillStyle\n * or CanvasRenderingContext2D.strokeStyle.\n * Represents a color, [CanvasPattern](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern),\n * or [CanvasGradient](https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient). The origin for\n * patterns and gradients as fill style is an increment of 512 css pixels from map coordinate\n * `[0, 0]`. For seamless repeat patterns, width and height of the pattern image\n * must be a factor of two (2, 4, 8, ..., 512).\n *\n * @typedef {string|CanvasPattern|CanvasGradient} ColorLike\n * @api\n */\n\n/**\n * @param {import(\"./color.js\").Color|ColorLike|PatternDescriptor|null} color Color.\n * @return {ColorLike|null} The color as an {@link ol/colorlike~ColorLike}.\n * @api\n */\nexport function asColorLike(color) {\n if (!color) {\n return null;\n }\n if (Array.isArray(color)) {\n return toString(color);\n }\n if (typeof color === 'object' && 'src' in color) {\n return asCanvasPattern(color);\n }\n return color;\n}\n\n/**\n * @param {PatternDescriptor} pattern Pattern descriptor.\n * @return {CanvasPattern|null} Canvas pattern or null if the pattern referenced in the\n * PatternDescriptor was not found in the icon image cache.\n */\nfunction asCanvasPattern(pattern) {\n if (!pattern.offset || !pattern.size) {\n return iconCache.getPattern(pattern.src, 'anonymous', pattern.color);\n }\n\n const cacheKey = pattern.src + ':' + pattern.offset;\n\n const canvasPattern = iconCache.getPattern(\n cacheKey,\n undefined,\n pattern.color,\n );\n if (canvasPattern) {\n return canvasPattern;\n }\n\n const iconImage = iconCache.get(pattern.src, 'anonymous', null);\n if (iconImage.getImageState() !== ImageState.LOADED) {\n return null;\n }\n const patternCanvasContext = createCanvasContext2D(\n pattern.size[0],\n pattern.size[1],\n );\n patternCanvasContext.drawImage(\n iconImage.getImage(1),\n pattern.offset[0],\n pattern.offset[1],\n pattern.size[0],\n pattern.size[1],\n 0,\n 0,\n pattern.size[0],\n pattern.size[1],\n );\n getIconImage(\n patternCanvasContext.canvas,\n cacheKey,\n undefined,\n ImageState.LOADED,\n pattern.color,\n true,\n );\n return iconCache.getPattern(cacheKey, undefined, pattern.color);\n}\n","/**\n * @module ol/ObjectEventType\n */\n\n/**\n * @enum {string}\n */\nexport default {\n /**\n * Triggered when a property is changed.\n * @event module:ol/Object.ObjectEvent#propertychange\n * @api\n */\n PROPERTYCHANGE: 'propertychange',\n};\n\n/**\n * @typedef {'propertychange'} Types\n */\n","/**\n * @module ol/Observable\n */\nimport EventTarget from './events/Target.js';\nimport EventType from './events/EventType.js';\nimport {listen, listenOnce, unlistenByKey} from './events.js';\n\n/***\n * @template {string} Type\n * @template {Event|import(\"./events/Event.js\").default} EventClass\n * @template Return\n * @typedef {(type: Type, listener: (event: EventClass) => ?) => Return} OnSignature\n */\n\n/***\n * @template {string} Type\n * @template Return\n * @typedef {(type: Type[], listener: (event: Event|import(\"./events/Event\").default) => ?) => Return extends void ? void : Return[]} CombinedOnSignature\n */\n\n/**\n * @typedef {'change'|'error'} EventTypes\n */\n\n/***\n * @template Return\n * @typedef {OnSignature & CombinedOnSignature} ObservableOnSignature\n */\n\n/**\n * @classdesc\n * Abstract base class; normally only used for creating subclasses and not\n * instantiated in apps.\n * An event target providing convenient methods for listener registration\n * and unregistration. A generic `change` event is always available through\n * {@link module:ol/Observable~Observable#changed}.\n *\n * @fires import(\"./events/Event.js\").default\n * @api\n */\nclass Observable extends EventTarget {\n constructor() {\n super();\n\n this.on =\n /** @type {ObservableOnSignature} */ (\n this.onInternal\n );\n\n this.once =\n /** @type {ObservableOnSignature} */ (\n this.onceInternal\n );\n\n this.un = /** @type {ObservableOnSignature} */ (this.unInternal);\n\n /**\n * @private\n * @type {number}\n */\n this.revision_ = 0;\n }\n\n /**\n * Increases the revision counter and dispatches a 'change' event.\n * @api\n */\n changed() {\n ++this.revision_;\n this.dispatchEvent(EventType.CHANGE);\n }\n\n /**\n * Get the version number for this object. Each time the object is modified,\n * its version number will be incremented.\n * @return {number} Revision.\n * @api\n */\n getRevision() {\n return this.revision_;\n }\n\n /**\n * @param {string|Array} type Type.\n * @param {function((Event|import(\"./events/Event\").default)): ?} listener Listener.\n * @return {import(\"./events.js\").EventsKey|Array} Event key.\n * @protected\n */\n onInternal(type, listener) {\n if (Array.isArray(type)) {\n const len = type.length;\n const keys = new Array(len);\n for (let i = 0; i < len; ++i) {\n keys[i] = listen(this, type[i], listener);\n }\n return keys;\n }\n return listen(this, /** @type {string} */ (type), listener);\n }\n\n /**\n * @param {string|Array} type Type.\n * @param {function((Event|import(\"./events/Event\").default)): ?} listener Listener.\n * @return {import(\"./events.js\").EventsKey|Array} Event key.\n * @protected\n */\n onceInternal(type, listener) {\n let key;\n if (Array.isArray(type)) {\n const len = type.length;\n key = new Array(len);\n for (let i = 0; i < len; ++i) {\n key[i] = listenOnce(this, type[i], listener);\n }\n } else {\n key = listenOnce(this, /** @type {string} */ (type), listener);\n }\n /** @type {Object} */ (listener).ol_key = key;\n return key;\n }\n\n /**\n * Unlisten for a certain type of event.\n * @param {string|Array} type Type.\n * @param {function((Event|import(\"./events/Event\").default)): ?} listener Listener.\n * @protected\n */\n unInternal(type, listener) {\n const key = /** @type {Object} */ (listener).ol_key;\n if (key) {\n unByKey(key);\n } else if (Array.isArray(type)) {\n for (let i = 0, ii = type.length; i < ii; ++i) {\n this.removeEventListener(type[i], listener);\n }\n } else {\n this.removeEventListener(type, listener);\n }\n }\n}\n\n/**\n * Listen for a certain type of event.\n * @function\n * @param {string|Array} type The event type or array of event types.\n * @param {function((Event|import(\"./events/Event\").default)): ?} listener The listener function.\n * @return {import(\"./events.js\").EventsKey|Array} Unique key for the listener. If\n * called with an array of event types as the first argument, the return\n * will be an array of keys.\n * @api\n */\nObservable.prototype.on;\n\n/**\n * Listen once for a certain type of event.\n * @function\n * @param {string|Array} type The event type or array of event types.\n * @param {function((Event|import(\"./events/Event\").default)): ?} listener The listener function.\n * @return {import(\"./events.js\").EventsKey|Array} Unique key for the listener. If\n * called with an array of event types as the first argument, the return\n * will be an array of keys.\n * @api\n */\nObservable.prototype.once;\n\n/**\n * Unlisten for a certain type of event.\n * @function\n * @param {string|Array} type The event type or array of event types.\n * @param {function((Event|import(\"./events/Event\").default)): ?} listener The listener function.\n * @api\n */\nObservable.prototype.un;\n\n/**\n * Removes an event listener using the key returned by `on()` or `once()`.\n * @param {import(\"./events.js\").EventsKey|Array} key The key returned by `on()`\n * or `once()` (or an array of keys).\n * @api\n */\nexport function unByKey(key) {\n if (Array.isArray(key)) {\n for (let i = 0, ii = key.length; i < ii; ++i) {\n unlistenByKey(key[i]);\n }\n } else {\n unlistenByKey(/** @type {import(\"./events.js\").EventsKey} */ (key));\n }\n}\n\nexport default Observable;\n","/**\n * @module ol/Object\n */\nimport Event from './events/Event.js';\nimport ObjectEventType from './ObjectEventType.js';\nimport Observable from './Observable.js';\nimport {getUid} from './util.js';\nimport {isEmpty} from './obj.js';\n\n/**\n * @classdesc\n * Events emitted by {@link module:ol/Object~BaseObject} instances are instances of this type.\n */\nexport class ObjectEvent extends Event {\n /**\n * @param {string} type The event type.\n * @param {string} key The property name.\n * @param {*} oldValue The old value for `key`.\n */\n constructor(type, key, oldValue) {\n super(type);\n\n /**\n * The name of the property whose value is changing.\n * @type {string}\n * @api\n */\n this.key = key;\n\n /**\n * The old value. To get the new value use `e.target.get(e.key)` where\n * `e` is the event object.\n * @type {*}\n * @api\n */\n this.oldValue = oldValue;\n }\n}\n\n/***\n * @template Return\n * @typedef {import(\"./Observable\").OnSignature &\n * import(\"./Observable\").OnSignature &\n * import(\"./Observable\").CombinedOnSignature} ObjectOnSignature\n */\n\n/**\n * @classdesc\n * Abstract base class; normally only used for creating subclasses and not\n * instantiated in apps.\n * Most non-trivial classes inherit from this.\n *\n * This extends {@link module:ol/Observable~Observable} with observable\n * properties, where each property is observable as well as the object as a\n * whole.\n *\n * Classes that inherit from this have pre-defined properties, to which you can\n * add your owns. The pre-defined properties are listed in this documentation as\n * 'Observable Properties', and have their own accessors; for example,\n * {@link module:ol/Map~Map} has a `target` property, accessed with\n * `getTarget()` and changed with `setTarget()`. Not all properties are however\n * settable. There are also general-purpose accessors `get()` and `set()`. For\n * example, `get('target')` is equivalent to `getTarget()`.\n *\n * The `set` accessors trigger a change event, and you can monitor this by\n * registering a listener. For example, {@link module:ol/View~View} has a\n * `center` property, so `view.on('change:center', function(evt) {...});` would\n * call the function whenever the value of the center property changes. Within\n * the function, `evt.target` would be the view, so `evt.target.getCenter()`\n * would return the new center.\n *\n * You can add your own observable properties with\n * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`.\n * You can listen for changes on that property value with\n * `object.on('change:prop', listener)`. You can get a list of all\n * properties with {@link module:ol/Object~BaseObject#getProperties}.\n *\n * Note that the observable properties are separate from standard JS properties.\n * You can, for example, give your map object a title with\n * `map.title='New title'` and with `map.set('title', 'Another title')`. The\n * first will be a `hasOwnProperty`; the second will appear in\n * `getProperties()`. Only the second is observable.\n *\n * Properties can be deleted by using the unset method. E.g.\n * object.unset('foo').\n *\n * @fires ObjectEvent\n * @api\n */\nclass BaseObject extends Observable {\n /**\n * @param {Object} [values] An object with key-value pairs.\n */\n constructor(values) {\n super();\n\n /***\n * @type {ObjectOnSignature}\n */\n this.on;\n\n /***\n * @type {ObjectOnSignature}\n */\n this.once;\n\n /***\n * @type {ObjectOnSignature}\n */\n this.un;\n\n // Call {@link module:ol/util.getUid} to ensure that the order of objects' ids is\n // the same as the order in which they were created. This also helps to\n // ensure that object properties are always added in the same order, which\n // helps many JavaScript engines generate faster code.\n getUid(this);\n\n /**\n * @private\n * @type {Object|null}\n */\n this.values_ = null;\n\n if (values !== undefined) {\n this.setProperties(values);\n }\n }\n\n /**\n * Gets a value.\n * @param {string} key Key name.\n * @return {*} Value.\n * @api\n */\n get(key) {\n let value;\n if (this.values_ && this.values_.hasOwnProperty(key)) {\n value = this.values_[key];\n }\n return value;\n }\n\n /**\n * Get a list of object property names.\n * @return {Array} List of property names.\n * @api\n */\n getKeys() {\n return (this.values_ && Object.keys(this.values_)) || [];\n }\n\n /**\n * Get an object of all property names and values.\n * @return {Object} Object.\n * @api\n */\n getProperties() {\n return (this.values_ && Object.assign({}, this.values_)) || {};\n }\n\n /**\n * Get an object of all property names and values.\n * @return {Object?} Object.\n */\n getPropertiesInternal() {\n return this.values_;\n }\n\n /**\n * @return {boolean} The object has properties.\n */\n hasProperties() {\n return !!this.values_;\n }\n\n /**\n * @param {string} key Key name.\n * @param {*} oldValue Old value.\n */\n notify(key, oldValue) {\n let eventType;\n eventType = `change:${key}`;\n if (this.hasListener(eventType)) {\n this.dispatchEvent(new ObjectEvent(eventType, key, oldValue));\n }\n eventType = ObjectEventType.PROPERTYCHANGE;\n if (this.hasListener(eventType)) {\n this.dispatchEvent(new ObjectEvent(eventType, key, oldValue));\n }\n }\n\n /**\n * @param {string} key Key name.\n * @param {import(\"./events.js\").Listener} listener Listener.\n */\n addChangeListener(key, listener) {\n this.addEventListener(`change:${key}`, listener);\n }\n\n /**\n * @param {string} key Key name.\n * @param {import(\"./events.js\").Listener} listener Listener.\n */\n removeChangeListener(key, listener) {\n this.removeEventListener(`change:${key}`, listener);\n }\n\n /**\n * Sets a value.\n * @param {string} key Key name.\n * @param {*} value Value.\n * @param {boolean} [silent] Update without triggering an event.\n * @api\n */\n set(key, value, silent) {\n const values = this.values_ || (this.values_ = {});\n if (silent) {\n values[key] = value;\n } else {\n const oldValue = values[key];\n values[key] = value;\n if (oldValue !== value) {\n this.notify(key, oldValue);\n }\n }\n }\n\n /**\n * Sets a collection of key-value pairs. Note that this changes any existing\n * properties and adds new ones (it does not remove any existing properties).\n * @param {Object} values Values.\n * @param {boolean} [silent] Update without triggering an event.\n * @api\n */\n setProperties(values, silent) {\n for (const key in values) {\n this.set(key, values[key], silent);\n }\n }\n\n /**\n * Apply any properties from another object without triggering events.\n * @param {BaseObject} source The source object.\n * @protected\n */\n applyProperties(source) {\n if (!source.values_) {\n return;\n }\n Object.assign(this.values_ || (this.values_ = {}), source.values_);\n }\n\n /**\n * Unsets a property.\n * @param {string} key Key name.\n * @param {boolean} [silent] Unset without triggering an event.\n * @api\n */\n unset(key, silent) {\n if (this.values_ && key in this.values_) {\n const oldValue = this.values_[key];\n delete this.values_[key];\n if (isEmpty(this.values_)) {\n this.values_ = null;\n }\n if (!silent) {\n this.notify(key, oldValue);\n }\n }\n }\n}\n\nexport default BaseObject;\n","/**\n * @module ol/render/canvas\n */\nimport BaseObject from '../Object.js';\nimport {WORKER_OFFSCREEN_CANVAS} from '../has.js';\nimport {clear} from '../obj.js';\nimport {createCanvasContext2D} from '../dom.js';\nimport {getFontParameters} from '../css.js';\n\n/**\n * @typedef {'Circle' | 'Image' | 'LineString' | 'Polygon' | 'Text' | 'Default'} BuilderType\n */\n\n/**\n * @typedef {Object} FillState\n * @property {import(\"../colorlike.js\").ColorLike} fillStyle FillStyle.\n */\n\n/**\n * @typedef Label\n * @property {number} width Width.\n * @property {number} height Height.\n * @property {Array} contextInstructions ContextInstructions.\n */\n\n/**\n * @typedef {Object} FillStrokeState\n * @property {import(\"../colorlike.js\").ColorLike} [currentFillStyle] Current FillStyle.\n * @property {import(\"../colorlike.js\").ColorLike} [currentStrokeStyle] Current StrokeStyle.\n * @property {CanvasLineCap} [currentLineCap] Current LineCap.\n * @property {Array} currentLineDash Current LineDash.\n * @property {number} [currentLineDashOffset] Current LineDashOffset.\n * @property {CanvasLineJoin} [currentLineJoin] Current LineJoin.\n * @property {number} [currentLineWidth] Current LineWidth.\n * @property {number} [currentMiterLimit] Current MiterLimit.\n * @property {number} [lastStroke] Last stroke.\n * @property {import(\"../colorlike.js\").ColorLike} [fillStyle] FillStyle.\n * @property {import(\"../colorlike.js\").ColorLike} [strokeStyle] StrokeStyle.\n * @property {CanvasLineCap} [lineCap] LineCap.\n * @property {Array} lineDash LineDash.\n * @property {number} [lineDashOffset] LineDashOffset.\n * @property {CanvasLineJoin} [lineJoin] LineJoin.\n * @property {number} [lineWidth] LineWidth.\n * @property {number} [miterLimit] MiterLimit.\n * @property {number} [fillPatternScale] Fill pattern scale.\n */\n\n/**\n * @typedef {Object} StrokeState\n * @property {CanvasLineCap} lineCap LineCap.\n * @property {Array} lineDash LineDash.\n * @property {number} lineDashOffset LineDashOffset.\n * @property {CanvasLineJoin} lineJoin LineJoin.\n * @property {number} lineWidth LineWidth.\n * @property {number} miterLimit MiterLimit.\n * @property {import(\"../colorlike.js\").ColorLike} strokeStyle StrokeStyle.\n */\n\n/**\n * @typedef {Object} TextState\n * @property {string} font Font.\n * @property {CanvasTextAlign} [textAlign] TextAlign.\n * @property {number} [repeat] Repeat.\n * @property {import(\"../style/Text.js\").TextJustify} [justify] Justify.\n * @property {CanvasTextBaseline} textBaseline TextBaseline.\n * @property {import(\"../style/Text.js\").TextPlacement} [placement] Placement.\n * @property {number} [maxAngle] MaxAngle.\n * @property {boolean} [overflow] Overflow.\n * @property {import(\"../style/Fill.js\").default} [backgroundFill] BackgroundFill.\n * @property {import(\"../style/Stroke.js\").default} [backgroundStroke] BackgroundStroke.\n * @property {import(\"../size.js\").Size} [scale] Scale.\n * @property {Array} [padding] Padding.\n */\n\n/**\n * @typedef {Object} SerializableInstructions\n * @property {Array<*>} instructions The rendering instructions.\n * @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions.\n * @property {Array} coordinates The array of all coordinates.\n * @property {!Object} [textStates] The text states (decluttering).\n * @property {!Object} [fillStates] The fill states (decluttering).\n * @property {!Object} [strokeStates] The stroke states (decluttering).\n */\n\n/**\n * @typedef {Object} DeclutterImageWithText\n */\n\n/**\n * @const\n * @type {string}\n */\nexport const defaultFont = '10px sans-serif';\n\n/**\n * @const\n * @type {string}\n */\nexport const defaultFillStyle = '#000';\n\n/**\n * @const\n * @type {CanvasLineCap}\n */\nexport const defaultLineCap = 'round';\n\n/**\n * @const\n * @type {Array}\n */\nexport const defaultLineDash = [];\n\n/**\n * @const\n * @type {number}\n */\nexport const defaultLineDashOffset = 0;\n\n/**\n * @const\n * @type {CanvasLineJoin}\n */\nexport const defaultLineJoin = 'round';\n\n/**\n * @const\n * @type {number}\n */\nexport const defaultMiterLimit = 10;\n\n/**\n * @const\n * @type {import(\"../colorlike.js\").ColorLike}\n */\nexport const defaultStrokeStyle = '#000';\n\n/**\n * @const\n * @type {CanvasTextAlign}\n */\nexport const defaultTextAlign = 'center';\n\n/**\n * @const\n * @type {CanvasTextBaseline}\n */\nexport const defaultTextBaseline = 'middle';\n\n/**\n * @const\n * @type {Array}\n */\nexport const defaultPadding = [0, 0, 0, 0];\n\n/**\n * @const\n * @type {number}\n */\nexport const defaultLineWidth = 1;\n\n/**\n * @type {BaseObject}\n */\nexport const checkedFonts = new BaseObject();\n\n/**\n * @type {CanvasRenderingContext2D}\n */\nlet measureContext = null;\n\n/**\n * @type {string}\n */\nlet measureFont;\n\n/**\n * @type {!Object}\n */\nexport const textHeights = {};\n\n/**\n * Clears the label cache when a font becomes available.\n * @param {string} fontSpec CSS font spec.\n */\nexport const registerFont = (function () {\n const retries = 100;\n const size = '32px ';\n const referenceFonts = ['monospace', 'serif'];\n const len = referenceFonts.length;\n const text = 'wmytzilWMYTZIL@#/&?$%10\\uF013';\n let interval, referenceWidth;\n\n /**\n * @param {string} fontStyle Css font-style\n * @param {string} fontWeight Css font-weight\n * @param {*} fontFamily Css font-family\n * @return {boolean} Font with style and weight is available\n */\n function isAvailable(fontStyle, fontWeight, fontFamily) {\n let available = true;\n for (let i = 0; i < len; ++i) {\n const referenceFont = referenceFonts[i];\n referenceWidth = measureTextWidth(\n fontStyle + ' ' + fontWeight + ' ' + size + referenceFont,\n text,\n );\n if (fontFamily != referenceFont) {\n const width = measureTextWidth(\n fontStyle +\n ' ' +\n fontWeight +\n ' ' +\n size +\n fontFamily +\n ',' +\n referenceFont,\n text,\n );\n // If width and referenceWidth are the same, then the fallback was used\n // instead of the font we wanted, so the font is not available.\n available = available && width != referenceWidth;\n }\n }\n if (available) {\n return true;\n }\n return false;\n }\n\n function check() {\n let done = true;\n const fonts = checkedFonts.getKeys();\n for (let i = 0, ii = fonts.length; i < ii; ++i) {\n const font = fonts[i];\n if (checkedFonts.get(font) < retries) {\n const [style, weight, family] = font.split('\\n');\n if (isAvailable(style, weight, family)) {\n clear(textHeights);\n // Make sure that loaded fonts are picked up by Safari\n measureContext = null;\n measureFont = undefined;\n checkedFonts.set(font, retries);\n } else {\n checkedFonts.set(font, checkedFonts.get(font) + 1, true);\n done = false;\n }\n }\n }\n if (done) {\n clearInterval(interval);\n interval = undefined;\n }\n }\n\n return function (fontSpec) {\n const font = getFontParameters(fontSpec);\n if (!font) {\n return;\n }\n const families = font.families;\n for (let i = 0, ii = families.length; i < ii; ++i) {\n const family = families[i];\n const key = font.style + '\\n' + font.weight + '\\n' + family;\n if (checkedFonts.get(key) === undefined) {\n checkedFonts.set(key, retries, true);\n if (!isAvailable(font.style, font.weight, family)) {\n checkedFonts.set(key, 0, true);\n if (interval === undefined) {\n interval = setInterval(check, 32);\n }\n }\n }\n }\n };\n})();\n\n/**\n * @param {string} font Font to use for measuring.\n * @return {import(\"../size.js\").Size} Measurement.\n */\nexport const measureTextHeight = (function () {\n /**\n * @type {HTMLDivElement}\n */\n let measureElement;\n return function (fontSpec) {\n let height = textHeights[fontSpec];\n if (height == undefined) {\n if (WORKER_OFFSCREEN_CANVAS) {\n const font = getFontParameters(fontSpec);\n const metrics = measureText(fontSpec, 'Žg');\n const lineHeight = isNaN(Number(font.lineHeight))\n ? 1.2\n : Number(font.lineHeight);\n height =\n lineHeight *\n (metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent);\n } else {\n if (!measureElement) {\n measureElement = document.createElement('div');\n measureElement.innerHTML = 'M';\n measureElement.style.minHeight = '0';\n measureElement.style.maxHeight = 'none';\n measureElement.style.height = 'auto';\n measureElement.style.padding = '0';\n measureElement.style.border = 'none';\n measureElement.style.position = 'absolute';\n measureElement.style.display = 'block';\n measureElement.style.left = '-99999px';\n }\n measureElement.style.font = fontSpec;\n document.body.appendChild(measureElement);\n height = measureElement.offsetHeight;\n document.body.removeChild(measureElement);\n }\n textHeights[fontSpec] = height;\n }\n return height;\n };\n})();\n\n/**\n * @param {string} font Font.\n * @param {string} text Text.\n * @return {TextMetrics} Text metrics.\n */\nfunction measureText(font, text) {\n if (!measureContext) {\n measureContext = createCanvasContext2D(1, 1);\n }\n if (font != measureFont) {\n measureContext.font = font;\n measureFont = measureContext.font;\n }\n return measureContext.measureText(text);\n}\n\n/**\n * @param {string} font Font.\n * @param {string} text Text.\n * @return {number} Width.\n */\nexport function measureTextWidth(font, text) {\n return measureText(font, text).width;\n}\n\n/**\n * Measure text width using a cache.\n * @param {string} font The font.\n * @param {string} text The text to measure.\n * @param {Object} cache A lookup of cached widths by text.\n * @return {number} The text width.\n */\nexport function measureAndCacheTextWidth(font, text, cache) {\n if (text in cache) {\n return cache[text];\n }\n const width = text\n .split('\\n')\n .reduce((prev, curr) => Math.max(prev, measureTextWidth(font, curr)), 0);\n cache[text] = width;\n return width;\n}\n\n/**\n * @param {TextState} baseStyle Base style.\n * @param {Array} chunks Text chunks to measure.\n * @return {{width: number, height: number, widths: Array, heights: Array, lineWidths: Array}}} Text metrics.\n */\nexport function getTextDimensions(baseStyle, chunks) {\n const widths = [];\n const heights = [];\n const lineWidths = [];\n let width = 0;\n let lineWidth = 0;\n let height = 0;\n let lineHeight = 0;\n for (let i = 0, ii = chunks.length; i <= ii; i += 2) {\n const text = chunks[i];\n if (text === '\\n' || i === ii) {\n width = Math.max(width, lineWidth);\n lineWidths.push(lineWidth);\n lineWidth = 0;\n height += lineHeight;\n lineHeight = 0;\n continue;\n }\n const font = chunks[i + 1] || baseStyle.font;\n const currentWidth = measureTextWidth(font, text);\n widths.push(currentWidth);\n lineWidth += currentWidth;\n const currentHeight = measureTextHeight(font);\n heights.push(currentHeight);\n lineHeight = Math.max(lineHeight, currentHeight);\n }\n return {width, height, widths, heights, lineWidths};\n}\n\n/**\n * @param {CanvasRenderingContext2D} context Context.\n * @param {number} rotation Rotation.\n * @param {number} offsetX X offset.\n * @param {number} offsetY Y offset.\n */\nexport function rotateAtOffset(context, rotation, offsetX, offsetY) {\n if (rotation !== 0) {\n context.translate(offsetX, offsetY);\n context.rotate(rotation);\n context.translate(-offsetX, -offsetY);\n }\n}\n\n/**\n * @param {CanvasRenderingContext2D|import(\"../render/canvas/ZIndexContext.js\").ZIndexContextProxy} context Context.\n * @param {import(\"../transform.js\").Transform|null} transform Transform.\n * @param {number} opacity Opacity.\n * @param {Label|HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} labelOrImage Label.\n * @param {number} originX Origin X.\n * @param {number} originY Origin Y.\n * @param {number} w Width.\n * @param {number} h Height.\n * @param {number} x X.\n * @param {number} y Y.\n * @param {import(\"../size.js\").Size} scale Scale.\n */\nexport function drawImageOrLabel(\n context,\n transform,\n opacity,\n labelOrImage,\n originX,\n originY,\n w,\n h,\n x,\n y,\n scale,\n) {\n context.save();\n\n if (opacity !== 1) {\n if (context.globalAlpha === undefined) {\n context.globalAlpha = (context) => (context.globalAlpha *= opacity);\n } else {\n context.globalAlpha *= opacity;\n }\n }\n if (transform) {\n context.transform.apply(context, transform);\n }\n\n if (/** @type {*} */ (labelOrImage).contextInstructions) {\n // label\n context.translate(x, y);\n context.scale(scale[0], scale[1]);\n executeLabelInstructions(/** @type {Label} */ (labelOrImage), context);\n } else if (scale[0] < 0 || scale[1] < 0) {\n // flipped image\n context.translate(x, y);\n context.scale(scale[0], scale[1]);\n context.drawImage(\n /** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (\n labelOrImage\n ),\n originX,\n originY,\n w,\n h,\n 0,\n 0,\n w,\n h,\n );\n } else {\n // if image not flipped translate and scale can be avoided\n context.drawImage(\n /** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (\n labelOrImage\n ),\n originX,\n originY,\n w,\n h,\n x,\n y,\n w * scale[0],\n h * scale[1],\n );\n }\n\n context.restore();\n}\n\n/**\n * @param {Label} label Label.\n * @param {CanvasRenderingContext2D} context Context.\n */\nfunction executeLabelInstructions(label, context) {\n const contextInstructions = label.contextInstructions;\n for (let i = 0, ii = contextInstructions.length; i < ii; i += 2) {\n if (Array.isArray(contextInstructions[i + 1])) {\n context[contextInstructions[i]].apply(\n context,\n contextInstructions[i + 1],\n );\n } else {\n context[contextInstructions[i]] = contextInstructions[i + 1];\n }\n }\n}\n","/**\n * @module ol/style/RegularShape\n */\n\nimport ImageState from '../ImageState.js';\nimport ImageStyle from './Image.js';\nimport {asArray} from '../color.js';\nimport {asColorLike} from '../colorlike.js';\nimport {createCanvasContext2D} from '../dom.js';\nimport {\n defaultFillStyle,\n defaultLineCap,\n defaultLineJoin,\n defaultLineWidth,\n defaultMiterLimit,\n defaultStrokeStyle,\n} from '../render/canvas.js';\n\n/**\n * Specify radius for regular polygons, or both radius and radius2 for stars.\n * @typedef {Object} Options\n * @property {import(\"./Fill.js\").default} [fill] Fill style.\n * @property {number} points Number of points for stars and regular polygons. In case of a polygon, the number of points\n * is the number of sides.\n * @property {number} radius Radius of a regular polygon.\n * @property {number} [radius2] Second radius to make a star instead of a regular polygon.\n * @property {number} [angle=0] Shape's angle in radians. A value of 0 will have one of the shape's points facing up.\n * @property {Array} [displacement=[0, 0]] Displacement of the shape in pixels.\n * Positive values will shift the shape right and up.\n * @property {import(\"./Stroke.js\").default} [stroke] Stroke style.\n * @property {number} [rotation=0] Rotation in radians (positive rotation clockwise).\n * @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view.\n * @property {number|import(\"../size.js\").Size} [scale=1] Scale. Unless two dimensional scaling is required a better\n * result may be obtained with appropriate settings for `radius` and `radius2`.\n * @property {import('./Style.js').DeclutterMode} [declutterMode] Declutter mode.\n */\n\n/**\n * @typedef {Object} RenderOptions\n * @property {import(\"../colorlike.js\").ColorLike|undefined} strokeStyle StrokeStyle.\n * @property {number} strokeWidth StrokeWidth.\n * @property {number} size Size.\n * @property {CanvasLineCap} lineCap LineCap.\n * @property {Array|null} lineDash LineDash.\n * @property {number} lineDashOffset LineDashOffset.\n * @property {CanvasLineJoin} lineJoin LineJoin.\n * @property {number} miterLimit MiterLimit.\n */\n\n/**\n * @classdesc\n * Set regular shape style for vector features. The resulting shape will be\n * a regular polygon when `radius` is provided, or a star when both `radius` and\n * `radius2` are provided.\n * @api\n */\nclass RegularShape extends ImageStyle {\n /**\n * @param {Options} options Options.\n */\n constructor(options) {\n super({\n opacity: 1,\n rotateWithView:\n options.rotateWithView !== undefined ? options.rotateWithView : false,\n rotation: options.rotation !== undefined ? options.rotation : 0,\n scale: options.scale !== undefined ? options.scale : 1,\n displacement:\n options.displacement !== undefined ? options.displacement : [0, 0],\n declutterMode: options.declutterMode,\n });\n\n /**\n * @private\n * @type {Object}\n */\n this.canvases_;\n\n /**\n * @private\n * @type {HTMLCanvasElement|null}\n */\n this.hitDetectionCanvas_ = null;\n\n /**\n * @private\n * @type {import(\"./Fill.js\").default|null}\n */\n this.fill_ = options.fill !== undefined ? options.fill : null;\n\n /**\n * @private\n * @type {Array}\n */\n this.origin_ = [0, 0];\n\n /**\n * @private\n * @type {number}\n */\n this.points_ = options.points;\n\n /**\n * @protected\n * @type {number}\n */\n this.radius = options.radius;\n\n /**\n * @private\n * @type {number|undefined}\n */\n this.radius2_ = options.radius2;\n\n /**\n * @private\n * @type {number}\n */\n this.angle_ = options.angle !== undefined ? options.angle : 0;\n\n /**\n * @private\n * @type {import(\"./Stroke.js\").default|null}\n */\n this.stroke_ = options.stroke !== undefined ? options.stroke : null;\n\n /**\n * @private\n * @type {import(\"../size.js\").Size}\n */\n this.size_;\n\n /**\n * @private\n * @type {RenderOptions}\n */\n this.renderOptions_;\n\n /**\n * @private\n */\n this.imageState_ =\n this.fill_ && this.fill_.loading()\n ? ImageState.LOADING\n : ImageState.LOADED;\n if (this.imageState_ === ImageState.LOADING) {\n this.ready().then(() => (this.imageState_ = ImageState.LOADED));\n }\n this.render();\n }\n\n /**\n * Clones the style.\n * @return {RegularShape} The cloned style.\n * @api\n * @override\n */\n clone() {\n const scale = this.getScale();\n const style = new RegularShape({\n fill: this.getFill() ? this.getFill().clone() : undefined,\n points: this.getPoints(),\n radius: this.getRadius(),\n radius2: this.getRadius2(),\n angle: this.getAngle(),\n stroke: this.getStroke() ? this.getStroke().clone() : undefined,\n rotation: this.getRotation(),\n rotateWithView: this.getRotateWithView(),\n scale: Array.isArray(scale) ? scale.slice() : scale,\n displacement: this.getDisplacement().slice(),\n declutterMode: this.getDeclutterMode(),\n });\n style.setOpacity(this.getOpacity());\n return style;\n }\n\n /**\n * Get the anchor point in pixels. The anchor determines the center point for the\n * symbolizer.\n * @return {Array} Anchor.\n * @api\n * @override\n */\n getAnchor() {\n const size = this.size_;\n const displacement = this.getDisplacement();\n const scale = this.getScaleArray();\n // anchor is scaled by renderer but displacement should not be scaled\n // so divide by scale here\n return [\n size[0] / 2 - displacement[0] / scale[0],\n size[1] / 2 + displacement[1] / scale[1],\n ];\n }\n\n /**\n * Get the angle used in generating the shape.\n * @return {number} Shape's rotation in radians.\n * @api\n */\n getAngle() {\n return this.angle_;\n }\n\n /**\n * Get the fill style for the shape.\n * @return {import(\"./Fill.js\").default|null} Fill style.\n * @api\n */\n getFill() {\n return this.fill_;\n }\n\n /**\n * Set the fill style.\n * @param {import(\"./Fill.js\").default|null} fill Fill style.\n * @api\n */\n setFill(fill) {\n this.fill_ = fill;\n this.render();\n }\n\n /**\n * @return {HTMLCanvasElement} Image element.\n * @override\n */\n getHitDetectionImage() {\n if (!this.hitDetectionCanvas_) {\n this.hitDetectionCanvas_ = this.createHitDetectionCanvas_(\n this.renderOptions_,\n );\n }\n return this.hitDetectionCanvas_;\n }\n\n /**\n * Get the image icon.\n * @param {number} pixelRatio Pixel ratio.\n * @return {HTMLCanvasElement} Image or Canvas element.\n * @api\n * @override\n */\n getImage(pixelRatio) {\n let image = this.canvases_[pixelRatio];\n if (!image) {\n const renderOptions = this.renderOptions_;\n const context = createCanvasContext2D(\n renderOptions.size * pixelRatio,\n renderOptions.size * pixelRatio,\n );\n this.draw_(renderOptions, context, pixelRatio);\n\n image = context.canvas;\n this.canvases_[pixelRatio] = image;\n }\n return image;\n }\n\n /**\n * Get the image pixel ratio.\n * @param {number} pixelRatio Pixel ratio.\n * @return {number} Pixel ratio.\n * @override\n */\n getPixelRatio(pixelRatio) {\n return pixelRatio;\n }\n\n /**\n * @return {import(\"../size.js\").Size} Image size.\n * @override\n */\n getImageSize() {\n return this.size_;\n }\n\n /**\n * @return {import(\"../ImageState.js\").default} Image state.\n * @override\n */\n getImageState() {\n return this.imageState_;\n }\n\n /**\n * Get the origin of the symbolizer.\n * @return {Array} Origin.\n * @api\n * @override\n */\n getOrigin() {\n return this.origin_;\n }\n\n /**\n * Get the number of points for generating the shape.\n * @return {number} Number of points for stars and regular polygons.\n * @api\n */\n getPoints() {\n return this.points_;\n }\n\n /**\n * Get the (primary) radius for the shape.\n * @return {number} Radius.\n * @api\n */\n getRadius() {\n return this.radius;\n }\n\n /**\n * Get the secondary radius for the shape.\n * @return {number|undefined} Radius2.\n * @api\n */\n getRadius2() {\n return this.radius2_;\n }\n\n /**\n * Get the size of the symbolizer (in pixels).\n * @return {import(\"../size.js\").Size} Size.\n * @api\n * @override\n */\n getSize() {\n return this.size_;\n }\n\n /**\n * Get the stroke style for the shape.\n * @return {import(\"./Stroke.js\").default|null} Stroke style.\n * @api\n */\n getStroke() {\n return this.stroke_;\n }\n\n /**\n * Set the stroke style.\n * @param {import(\"./Stroke.js\").default|null} stroke Stroke style.\n * @api\n */\n setStroke(stroke) {\n this.stroke_ = stroke;\n this.render();\n }\n\n /**\n * @param {function(import(\"../events/Event.js\").default): void} listener Listener function.\n * @override\n */\n listenImageChange(listener) {}\n\n /**\n * Load not yet loaded URI.\n * @override\n */\n load() {}\n\n /**\n * @param {function(import(\"../events/Event.js\").default): void} listener Listener function.\n * @override\n */\n unlistenImageChange(listener) {}\n\n /**\n * Calculate additional canvas size needed for the miter.\n * @param {string} lineJoin Line join\n * @param {number} strokeWidth Stroke width\n * @param {number} miterLimit Miter limit\n * @return {number} Additional canvas size needed\n * @private\n */\n calculateLineJoinSize_(lineJoin, strokeWidth, miterLimit) {\n if (\n strokeWidth === 0 ||\n this.points_ === Infinity ||\n (lineJoin !== 'bevel' && lineJoin !== 'miter')\n ) {\n return strokeWidth;\n }\n // m | ^\n // i | |\\ .\n // t >| #\\\n // e | |\\ \\ .\n // r \\s\\\n // | \\t\\ . .\n // \\r\\ . .\n // | \\o\\ . . . . .\n // e \\k\\ . . . .\n // | \\e\\ . . . . .\n // d \\ \\ . . . .\n // | _ _a_ _\\# . . .\n // r1 / ` . .\n // | . .\n // b / . .\n // | . .\n // / r2 . .\n // | . .\n // / . .\n // |α . .\n // / . .\n // ° center\n let r1 = this.radius;\n let r2 = this.radius2_ === undefined ? r1 : this.radius2_;\n if (r1 < r2) {\n const tmp = r1;\n r1 = r2;\n r2 = tmp;\n }\n const points =\n this.radius2_ === undefined ? this.points_ : this.points_ * 2;\n const alpha = (2 * Math.PI) / points;\n const a = r2 * Math.sin(alpha);\n const b = Math.sqrt(r2 * r2 - a * a);\n const d = r1 - b;\n const e = Math.sqrt(a * a + d * d);\n const miterRatio = e / a;\n if (lineJoin === 'miter' && miterRatio <= miterLimit) {\n return miterRatio * strokeWidth;\n }\n // Calculate the distance from center to the stroke corner where\n // it was cut short because of the miter limit.\n // l\n // ----+---- <= distance from center to here is maxr\n // /####|k ##\\\n // /#####^#####\\\n // /#### /+\\# s #\\\n // /### h/+++\\# t #\\\n // /### t/+++++\\# r #\\\n // /### a/+++++++\\# o #\\\n // /### p/++ fill +\\# k #\\\n ///#### /+++++^+++++\\# e #\\\n //#####/+++++/+\\+++++\\#####\\\n const k = strokeWidth / 2 / miterRatio;\n const l = (strokeWidth / 2) * (d / e);\n const maxr = Math.sqrt((r1 + k) * (r1 + k) + l * l);\n const bevelAdd = maxr - r1;\n if (this.radius2_ === undefined || lineJoin === 'bevel') {\n return bevelAdd * 2;\n }\n // If outer miter is over the miter limit the inner miter may reach through the\n // center and be longer than the bevel, same calculation as above but swap r1 / r2.\n const aa = r1 * Math.sin(alpha);\n const bb = Math.sqrt(r1 * r1 - aa * aa);\n const dd = r2 - bb;\n const ee = Math.sqrt(aa * aa + dd * dd);\n const innerMiterRatio = ee / aa;\n if (innerMiterRatio <= miterLimit) {\n const innerLength = (innerMiterRatio * strokeWidth) / 2 - r2 - r1;\n return 2 * Math.max(bevelAdd, innerLength);\n }\n return bevelAdd * 2;\n }\n\n /**\n * @return {RenderOptions} The render options\n * @protected\n */\n createRenderOptions() {\n let lineCap = defaultLineCap;\n let lineJoin = defaultLineJoin;\n let miterLimit = 0;\n let lineDash = null;\n let lineDashOffset = 0;\n let strokeStyle;\n let strokeWidth = 0;\n\n if (this.stroke_) {\n strokeStyle = asColorLike(this.stroke_.getColor() ?? defaultStrokeStyle);\n strokeWidth = this.stroke_.getWidth() ?? defaultLineWidth;\n lineDash = this.stroke_.getLineDash();\n lineDashOffset = this.stroke_.getLineDashOffset() ?? 0;\n lineJoin = this.stroke_.getLineJoin() ?? defaultLineJoin;\n lineCap = this.stroke_.getLineCap() ?? defaultLineCap;\n miterLimit = this.stroke_.getMiterLimit() ?? defaultMiterLimit;\n }\n\n const add = this.calculateLineJoinSize_(lineJoin, strokeWidth, miterLimit);\n const maxRadius = Math.max(this.radius, this.radius2_ || 0);\n const size = Math.ceil(2 * maxRadius + add);\n\n return {\n strokeStyle: strokeStyle,\n strokeWidth: strokeWidth,\n size: size,\n lineCap: lineCap,\n lineDash: lineDash,\n lineDashOffset: lineDashOffset,\n lineJoin: lineJoin,\n miterLimit: miterLimit,\n };\n }\n\n /**\n * @protected\n */\n render() {\n this.renderOptions_ = this.createRenderOptions();\n const size = this.renderOptions_.size;\n this.canvases_ = {};\n this.hitDetectionCanvas_ = null;\n this.size_ = [size, size];\n }\n\n /**\n * @private\n * @param {RenderOptions} renderOptions Render options.\n * @param {CanvasRenderingContext2D} context The rendering context.\n * @param {number} pixelRatio The pixel ratio.\n */\n draw_(renderOptions, context, pixelRatio) {\n context.scale(pixelRatio, pixelRatio);\n // set origin to canvas center\n context.translate(renderOptions.size / 2, renderOptions.size / 2);\n\n this.createPath_(context);\n\n if (this.fill_) {\n let color = this.fill_.getColor();\n if (color === null) {\n color = defaultFillStyle;\n }\n context.fillStyle = asColorLike(color);\n context.fill();\n }\n if (renderOptions.strokeStyle) {\n context.strokeStyle = renderOptions.strokeStyle;\n context.lineWidth = renderOptions.strokeWidth;\n if (renderOptions.lineDash) {\n context.setLineDash(renderOptions.lineDash);\n context.lineDashOffset = renderOptions.lineDashOffset;\n }\n context.lineCap = renderOptions.lineCap;\n context.lineJoin = renderOptions.lineJoin;\n context.miterLimit = renderOptions.miterLimit;\n context.stroke();\n }\n }\n\n /**\n * @private\n * @param {RenderOptions} renderOptions Render options.\n * @return {HTMLCanvasElement} Canvas containing the icon\n */\n createHitDetectionCanvas_(renderOptions) {\n let context;\n if (this.fill_) {\n let color = this.fill_.getColor();\n\n // determine if fill is transparent (or pattern or gradient)\n let opacity = 0;\n if (typeof color === 'string') {\n color = asArray(color);\n }\n if (color === null) {\n opacity = 1;\n } else if (Array.isArray(color)) {\n opacity = color.length === 4 ? color[3] : 1;\n }\n if (opacity === 0) {\n // if a transparent fill style is set, create an extra hit-detection image\n // with a default fill style\n context = createCanvasContext2D(renderOptions.size, renderOptions.size);\n this.drawHitDetectionCanvas_(renderOptions, context);\n }\n }\n return context ? context.canvas : this.getImage(1);\n }\n\n /**\n * @private\n * @param {CanvasRenderingContext2D} context The context to draw in.\n */\n createPath_(context) {\n let points = this.points_;\n const radius = this.radius;\n if (points === Infinity) {\n context.arc(0, 0, radius, 0, 2 * Math.PI);\n } else {\n const radius2 = this.radius2_ === undefined ? radius : this.radius2_;\n if (this.radius2_ !== undefined) {\n points *= 2;\n }\n const startAngle = this.angle_ - Math.PI / 2;\n const step = (2 * Math.PI) / points;\n for (let i = 0; i < points; i++) {\n const angle0 = startAngle + i * step;\n const radiusC = i % 2 === 0 ? radius : radius2;\n context.lineTo(radiusC * Math.cos(angle0), radiusC * Math.sin(angle0));\n }\n context.closePath();\n }\n }\n\n /**\n * @private\n * @param {RenderOptions} renderOptions Render options.\n * @param {CanvasRenderingContext2D} context The context.\n */\n drawHitDetectionCanvas_(renderOptions, context) {\n // set origin to canvas center\n context.translate(renderOptions.size / 2, renderOptions.size / 2);\n\n this.createPath_(context);\n\n context.fillStyle = defaultFillStyle;\n context.fill();\n if (renderOptions.strokeStyle) {\n context.strokeStyle = renderOptions.strokeStyle;\n context.lineWidth = renderOptions.strokeWidth;\n if (renderOptions.lineDash) {\n context.setLineDash(renderOptions.lineDash);\n context.lineDashOffset = renderOptions.lineDashOffset;\n }\n context.lineJoin = renderOptions.lineJoin;\n context.miterLimit = renderOptions.miterLimit;\n context.stroke();\n }\n }\n\n /**\n * @override\n */\n ready() {\n return this.fill_ ? this.fill_.ready() : Promise.resolve();\n }\n}\n\nexport default RegularShape;\n","/**\n * @module ol/style/Circle\n */\n\nimport RegularShape from './RegularShape.js';\n\n/**\n * @typedef {Object} Options\n * @property {import(\"./Fill.js\").default} [fill] Fill style.\n * @property {number} radius Circle radius.\n * @property {import(\"./Stroke.js\").default} [stroke] Stroke style.\n * @property {Array} [displacement=[0,0]] displacement\n * @property {number|import(\"../size.js\").Size} [scale=1] Scale. A two dimensional scale will produce an ellipse.\n * Unless two dimensional scaling is required a better result may be obtained with an appropriate setting for `radius`.\n * @property {number} [rotation=0] Rotation in radians\n * (positive rotation clockwise, meaningful only when used in conjunction with a two dimensional scale).\n * @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view\n * (meaningful only when used in conjunction with a two dimensional scale).\n * @property {import('./Style.js').DeclutterMode} [declutterMode] Declutter mode\n */\n\n/**\n * @classdesc\n * Set circle style for vector features.\n * @api\n */\nclass CircleStyle extends RegularShape {\n /**\n * @param {Options} [options] Options.\n */\n constructor(options) {\n options = options ? options : {radius: 5};\n\n super({\n points: Infinity,\n fill: options.fill,\n radius: options.radius,\n stroke: options.stroke,\n scale: options.scale !== undefined ? options.scale : 1,\n rotation: options.rotation !== undefined ? options.rotation : 0,\n rotateWithView:\n options.rotateWithView !== undefined ? options.rotateWithView : false,\n displacement:\n options.displacement !== undefined ? options.displacement : [0, 0],\n declutterMode: options.declutterMode,\n });\n }\n\n /**\n * Clones the style.\n * @return {CircleStyle} The cloned style.\n * @api\n * @override\n */\n clone() {\n const scale = this.getScale();\n const style = new CircleStyle({\n fill: this.getFill() ? this.getFill().clone() : undefined,\n stroke: this.getStroke() ? this.getStroke().clone() : undefined,\n radius: this.getRadius(),\n scale: Array.isArray(scale) ? scale.slice() : scale,\n rotation: this.getRotation(),\n rotateWithView: this.getRotateWithView(),\n displacement: this.getDisplacement().slice(),\n declutterMode: this.getDeclutterMode(),\n });\n style.setOpacity(this.getOpacity());\n return style;\n }\n\n /**\n * Set the circle radius.\n *\n * @param {number} radius Circle radius.\n * @api\n */\n setRadius(radius) {\n this.radius = radius;\n this.render();\n }\n}\n\nexport default CircleStyle;\n","/**\n * @module ol/style/Fill\n */\n\nimport ImageState from '../ImageState.js';\nimport {get as getIconImage} from './IconImage.js';\n\n/**\n * @typedef {Object} Options\n * @property {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike|import('../colorlike.js').PatternDescriptor|null} [color=null] A color,\n * gradient or pattern.\n * See {@link module:ol/color~Color} and {@link module:ol/colorlike~ColorLike} for possible formats. For polygon fills (not for {@link import(\"./RegularShape.js\").default} fills),\n * a pattern can also be provided as {@link module:ol/colorlike~PatternDescriptor}.\n * Default null; if null, the Canvas/renderer default black will be used.\n */\n\n/**\n * @classdesc\n * Set fill style for vector features.\n * @api\n */\nclass Fill {\n /**\n * @param {Options} [options] Options.\n */\n constructor(options) {\n options = options || {};\n\n /**\n * @private\n * @type {import(\"./IconImage.js\").default|null}\n */\n this.patternImage_ = null;\n\n /**\n * @private\n * @type {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike|import('../colorlike.js').PatternDescriptor|null}\n */\n this.color_ = null;\n if (options.color !== undefined) {\n this.setColor(options.color);\n }\n }\n\n /**\n * Clones the style. The color is not cloned if it is a {@link module:ol/colorlike~ColorLike}.\n * @return {Fill} The cloned style.\n * @api\n */\n clone() {\n const color = this.getColor();\n return new Fill({\n color: Array.isArray(color) ? color.slice() : color || undefined,\n });\n }\n\n /**\n * Get the fill color.\n * @return {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike|import('../colorlike.js').PatternDescriptor|null} Color.\n * @api\n */\n getColor() {\n return this.color_;\n }\n\n /**\n * Set the color.\n *\n * @param {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike|import('../colorlike.js').PatternDescriptor|null} color Color.\n * @api\n */\n setColor(color) {\n if (color !== null && typeof color === 'object' && 'src' in color) {\n const patternImage = getIconImage(\n null,\n color.src,\n 'anonymous',\n undefined,\n color.offset ? null : color.color ? color.color : null,\n !(color.offset && color.size),\n );\n patternImage.ready().then(() => {\n this.patternImage_ = null;\n });\n if (patternImage.getImageState() === ImageState.IDLE) {\n patternImage.load();\n }\n if (patternImage.getImageState() === ImageState.LOADING) {\n this.patternImage_ = patternImage;\n }\n }\n this.color_ = color;\n }\n\n /**\n * @return {boolean} The fill style is loading an image pattern.\n */\n loading() {\n return !!this.patternImage_;\n }\n\n /**\n * @return {Promise} `false` or a promise that resolves when the style is ready to use.\n */\n ready() {\n return this.patternImage_ ? this.patternImage_.ready() : Promise.resolve();\n }\n}\n\nexport default Fill;\n","/**\n * @module ol/style/Stroke\n */\n\n/**\n * @typedef {Object} Options\n * @property {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike} [color] A color, gradient or pattern.\n * See {@link module:ol/color~Color} and {@link module:ol/colorlike~ColorLike} for possible formats.\n * Default null; if null, the Canvas/renderer default black will be used.\n * @property {CanvasLineCap} [lineCap='round'] Line cap style: `butt`, `round`, or `square`.\n * @property {CanvasLineJoin} [lineJoin='round'] Line join style: `bevel`, `round`, or `miter`.\n * @property {Array} [lineDash] Line dash pattern. Default is `null` (no dash).\n * @property {number} [lineDashOffset=0] Line dash offset.\n * @property {number} [miterLimit=10] Miter limit.\n * @property {number} [width] Width.\n */\n\n/**\n * @classdesc\n * Set stroke style for vector features.\n * Note that the defaults given are the Canvas defaults, which will be used if\n * option is not defined. The `get` functions return whatever was entered in\n * the options; they will not return the default.\n * @api\n */\nclass Stroke {\n /**\n * @param {Options} [options] Options.\n */\n constructor(options) {\n options = options || {};\n\n /**\n * @private\n * @type {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike}\n */\n this.color_ = options.color !== undefined ? options.color : null;\n\n /**\n * @private\n * @type {CanvasLineCap|undefined}\n */\n this.lineCap_ = options.lineCap;\n\n /**\n * @private\n * @type {Array|null}\n */\n this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;\n\n /**\n * @private\n * @type {number|undefined}\n */\n this.lineDashOffset_ = options.lineDashOffset;\n\n /**\n * @private\n * @type {CanvasLineJoin|undefined}\n */\n this.lineJoin_ = options.lineJoin;\n\n /**\n * @private\n * @type {number|undefined}\n */\n this.miterLimit_ = options.miterLimit;\n\n /**\n * @private\n * @type {number|undefined}\n */\n this.width_ = options.width;\n }\n\n /**\n * Clones the style.\n * @return {Stroke} The cloned style.\n * @api\n */\n clone() {\n const color = this.getColor();\n return new Stroke({\n color: Array.isArray(color) ? color.slice() : color || undefined,\n lineCap: this.getLineCap(),\n lineDash: this.getLineDash() ? this.getLineDash().slice() : undefined,\n lineDashOffset: this.getLineDashOffset(),\n lineJoin: this.getLineJoin(),\n miterLimit: this.getMiterLimit(),\n width: this.getWidth(),\n });\n }\n\n /**\n * Get the stroke color.\n * @return {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike} Color.\n * @api\n */\n getColor() {\n return this.color_;\n }\n\n /**\n * Get the line cap type for the stroke.\n * @return {CanvasLineCap|undefined} Line cap.\n * @api\n */\n getLineCap() {\n return this.lineCap_;\n }\n\n /**\n * Get the line dash style for the stroke.\n * @return {Array|null} Line dash.\n * @api\n */\n getLineDash() {\n return this.lineDash_;\n }\n\n /**\n * Get the line dash offset for the stroke.\n * @return {number|undefined} Line dash offset.\n * @api\n */\n getLineDashOffset() {\n return this.lineDashOffset_;\n }\n\n /**\n * Get the line join type for the stroke.\n * @return {CanvasLineJoin|undefined} Line join.\n * @api\n */\n getLineJoin() {\n return this.lineJoin_;\n }\n\n /**\n * Get the miter limit for the stroke.\n * @return {number|undefined} Miter limit.\n * @api\n */\n getMiterLimit() {\n return this.miterLimit_;\n }\n\n /**\n * Get the stroke width.\n * @return {number|undefined} Width.\n * @api\n */\n getWidth() {\n return this.width_;\n }\n\n /**\n * Set the color.\n *\n * @param {import(\"../color.js\").Color|import(\"../colorlike.js\").ColorLike} color Color.\n * @api\n */\n setColor(color) {\n this.color_ = color;\n }\n\n /**\n * Set the line cap.\n *\n * @param {CanvasLineCap|undefined} lineCap Line cap.\n * @api\n */\n setLineCap(lineCap) {\n this.lineCap_ = lineCap;\n }\n\n /**\n * Set the line dash.\n *\n * @param {Array|null} lineDash Line dash.\n * @api\n */\n setLineDash(lineDash) {\n this.lineDash_ = lineDash;\n }\n\n /**\n * Set the line dash offset.\n *\n * @param {number|undefined} lineDashOffset Line dash offset.\n * @api\n */\n setLineDashOffset(lineDashOffset) {\n this.lineDashOffset_ = lineDashOffset;\n }\n\n /**\n * Set the line join.\n *\n * @param {CanvasLineJoin|undefined} lineJoin Line join.\n * @api\n */\n setLineJoin(lineJoin) {\n this.lineJoin_ = lineJoin;\n }\n\n /**\n * Set the miter limit.\n *\n * @param {number|undefined} miterLimit Miter limit.\n * @api\n */\n setMiterLimit(miterLimit) {\n this.miterLimit_ = miterLimit;\n }\n\n /**\n * Set the width.\n *\n * @param {number|undefined} width Width.\n * @api\n */\n setWidth(width) {\n this.width_ = width;\n }\n}\n\nexport default Stroke;\n","/**\n * @module ol/style/Style\n */\n\nimport CircleStyle from './Circle.js';\nimport Fill from './Fill.js';\nimport Stroke from './Stroke.js';\nimport {assert} from '../asserts.js';\n\n/**\n * Defines how symbols and text are decluttered on layers ith `declutter` set to `true`\n * * **declutter**: Overlapping symbols and text are decluttered.\n * * **obstacle**: Symbols and text are rendered, but serve as obstacle for subsequent attempts\n * to place a symbol or text at the same location.\n * * **none**: No decluttering is done.\n *\n * @typedef {\"declutter\"|\"obstacle\"|\"none\"} DeclutterMode\n */\n\n/**\n * A function that takes a {@link module:ol/Feature~Feature} and a `{number}`\n * representing the view's resolution. The function should return a\n * {@link module:ol/style/Style~Style} or an array of them. This way e.g. a\n * vector layer can be styled. If the function returns `undefined`, the\n * feature will not be rendered.\n *\n * @typedef {function(import(\"../Feature.js\").FeatureLike, number):(Style|Array