UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

477 lines (476 loc) 37.4 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { htmlFor as uHtmlFor } from 'uhtml/keyed'; import { html as uHtml } from 'uhtml'; import { v4 as uuidv4 } from 'uuid'; import DrawingFeature, { DrawingShape } from '../../tools/drawing/drawingFeature.js'; import OlDrawing from '../../tools/drawing/olDrawing.js'; import CesiumDrawing from '../../tools/drawing/cesiumDrawing.js'; import { GeoJSON, GPX, KML } from 'ol/format.js'; import { Polygon } from 'ol/geom.js'; import Feature from 'ol/Feature.js'; import GirafeHTMLElement from '../../base/GirafeHTMLElement.js'; import { download } from '../../tools/export/download.js'; import MapComponent from '../map/component.js'; import GirafeColorPicker from '../../tools/utils/girafecolorpicker.js'; import checkedIcon from '../../assets/icons/checked-full.svg?raw'; import noCheckedIcon from '../../assets/icons/checked-no.svg?raw'; import trashIcon from './assets/trash.svg?raw'; import locateIcon from './assets/locate.svg?raw'; import visibleIcon from './assets/visible.svg?raw'; import notVisibleIcon from './assets/notVisible.svg?raw'; import LayerDrawing from '../../models/layers/layerdrawing.js'; import { UsedInTemplateOnly } from '../../decorators.js'; export const DrawingStateLocation = 'drawing'; export default class DrawingComponent extends GirafeHTMLElement { templateUrl = null; styleUrls = null; template = () => { return uHtml `<style> .hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}button,input,select,textarea{font:inherit}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height) / 1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad1);background-color:var(--bkg-color-grad2)}.gg-button:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed;border:none}.gg-button:disabled img{filter:opacity(.6)}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-icon{justify-content:center;align-items:center;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height) / 1.2);min-height:calc(var(--app-standard-height) / 1.2);max-height:calc(var(--app-standard-height) / 1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height) / 2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height) / 2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height) / 2);min-height:calc(var(--app-standard-height) / 2);max-height:calc(var(--app-standard-height) / 2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height) / 3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height) / 3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover:not(:disabled),.gg-select:hover:not(:disabled),.gg-input:hover:not(:disabled),.gg-textarea:hover:not(:disabled),.gg-icon-button:hover:not(:disabled){background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;grid-auto-flow:column;padding-bottom:1rem;font-size:1rem;display:grid}.gg-tab{border:none;border-bottom:var(--app-standard-border);cursor:pointer;color:var(--text-color);background:0 0;padding:.5rem}.gg-tab.active{border-bottom:solid 1px var(--text-color)}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height) / 2);height:calc(var(--app-standard-height) / 2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}.girafe-onboarding-theme{background-color:var(--bkg-color)!important;color:var(--text-color)!important}.girafe-onboarding-theme button{background-color:var(--bkg-color)!important;color:var(--text-color)!important;text-shadow:none!important}.girafe-onboarding-theme button.driver-popover-close-btn{z-index:10000}.tippy-box{background-color:var(--bkg-color)!important;border:var(--app-standard-border)!important}.tippy-content{color:var(--text-color)!important}.tippy-arrow{color:var(--bkg-color)!important}.tippy-box[data-theme~=error]{background-color:var(--error-color);white-space:pre-line}div.tippy-box[data-theme=image-popup]{border:var(--app-standard-border)!important;& div.tippy-content{background-color:var(--bkg-color)!important;& img{max-width:20rem;max-height:20rem}}& div.tippy-arrow{color:var(--bkg-color)!important}} </style><style> input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var(--bkg-color-input);padding-right:0}input:focus{border:1px solid var(--lt-color-gray-900)}button{border:solid 1px var(--bkg-color);color:var(--text-color);background-color:var(--bkg-color);cursor:pointer;padding:0}button:hover{border:solid 1px var(--text-color-grad2)}button.selected{border:solid 1px var(--text-color-grad2);background-color:var(--bkg-color-grad1);color:var(--bkg-color)}#toolbar{flex-wrap:wrap;display:flex}.label-container{flex-grow:1}.text-end{text-align:end}.icon{width:1.2rem;height:1.2rem;fill:var(--text-color);flex-shrink:0;margin:3px}.icon:hover{fill:var(--text-color-grad2)}.gg-icon-button:hover{border:none}.colorPicker{border:1px solid var(--text-color);border-radius:5px}.picker_wrapper{transform:translate(-250px)}.picker_arrow{transform:translate(200px)rotate(90deg)scaleX(-1)!important}.picker_wrapper,.picker_arrow:after,.picker_arrow:before{background-color:var(--bkg-color)!important}.picker_wrapper,.picker_arrow:before{box-shadow:0 0 10px 1px var(--text-color-grad1)!important}button:disabled,input:disabled,.disabled{pointer-events:none;opacity:.5}fieldset.field-wrapper{border:0;margin:0;padding:0}#panel{height:100%;color:var(--text-color);flex-direction:column;display:flex}#panel>div{padding:1rem}#toolParametersContainer{& .title{margin-top:.5rem;margin-bottom:.75rem;display:inline-block}}.toolParameters{flex-direction:column;justify-content:left;row-gap:.25rem;display:flex}.toolParameters>label{padding:0 5px}#drawingListContainer{flex-grow:1;overflow:hidden auto;padding-top:0!important;& .titleContainer{margin-bottom:1rem;display:flex;& .title{flex-grow:1;align-items:center;display:flex}}}#drawingList{flex-flow:column;gap:.15rem;display:flex}#drawingList .girafe{border:solid 1px var(--text-color);flex-wrap:wrap;align-items:center;padding:.3rem;display:flex;position:relative}#drawingList .girafe span{flex-grow:1;padding-left:5px}#options{box-shadow:0px -2px 3px -1px var(--text-color-grad1);z-index:100;flex-direction:column;display:flex;position:relative}#optionsTitle{text-align:center;margin-bottom:20px;font-size:1.2rem}#options hr{width:100%;margin:8px 0}#optionsTextLines,#optionsColorLine,#optionsLineLines{align-items:center;display:grid}#optionsText{margin:auto}#optionsTextLines{grid-template-columns:.825fr 2.5fr 1fr 4em .5fr .5fr}#optionsTextLines button:hover{border:none}#optionsColor,#optionsLine,#optionsExport,#optionsArrow{margin:auto}#optionsColorLine{display:flex}#optionsColorLine>div{align-items:center;width:100%;display:flex}#optionsLineLines,#optionsArrowStyle{grid-template-columns:3fr 4em 1fr auto}#optionsExportLine{grid-template-columns:60px 60px 45px 45px;gap:.2rem;display:flex}span.label{padding-left:.5rem;padding-right:.5rem;&.first{padding-left:0}}span.unit{padding-left:.5rem} </style> <style>${this.customStyle}</style> <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="label first" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="label" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="measureColorPicker"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="text-end" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div> ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`; }; isPanelVisible = false; panelTitle = 'drawing-panel'; panelTogglePath = 'interface.drawingPanelVisible'; checkedIcon = checkedIcon; noCheckedIcon = noCheckedIcon; trashIcon = trashIcon; locateIcon = locateIcon; visibleIcon = visibleIcon; notVisibleIcon = notVisibleIcon; renderedOnce = false; drawingState; colorPickers = []; buttons = [ { id: 'disable', tool: null }, { id: 'point', tool: DrawingShape.Point }, { id: 'line', tool: DrawingShape.Polyline }, { id: 'square', tool: DrawingShape.Square }, { id: 'rectangle', tool: DrawingShape.Rectangle }, { id: 'polygon', tool: DrawingShape.Polygon }, { id: 'circle', tool: DrawingShape.Disk }, { id: 'freeline', tool: DrawingShape.FreehandPolyline }, { id: 'freepolygon', tool: DrawingShape.FreehandPolygon } ]; toolSelected = null; arrowStyles = { none: '⸻', start: 'ᐸ⸺', end: '⸺ᐳ', both: 'ᐸ—ᐳ' }; arrowPositions = { whole: 'ᐸ⸺⸺ᐳ', each: 'ᐸ—ᐳᐸ—ᐳ', mid: '⸺ᐸ⸺' }; lineStyles = { full: '————', dash: '━ ━ ━', dot: '• • • • • •' }; measureInformationsSingle = { none: 'measure-information-none', simple: 'measure-information-simple', full: 'measure-information-full' }; measureInformationsMultiple = { ...this.measureInformationsSingle, mixed: 'measure-information-mixed' }; get measureInformations() { if (this.selectedFeatures.length == 0) return {}; return this.haveSameMeasureInformation ? this.measureInformationsSingle : this.measureInformationsMultiple; } get selectedFeatureMeasureInformation() { return this.haveSameMeasureInformation ? this.selectedFeatures[0].measureInformation : 'mixed'; } get measureInformationTooltip() { return this.selectedFeatures.length == 1 ? 'measure-information-tooltip' : 'measure-information-mixed-tooltip'; } get haveSameMeasureInformation() { return this.selectedFeatures.every((feature, index, features) => { if (index === 0) { return true; } return feature.measureInformation === features[0].measureInformation; }); } activeDrawingLayer; defaultLayerName = 'My Drawing'; olDrawing; cesiumDrawing; fixedLengthEnabled = false; // Batch Create mode is currently not used. Batch mode allows the user to create multiple shapes without re-selecting // the drawing tool. It possibly will be part of advanced drawing/editing tools. batchCreateMode = false; constructor(name = 'drawing') { super(name); } render() { super.render(); if (this.isPanelVisible) { this.renderComponent(); this.state.layers.isSnappingActive = true; } else { this.hide(); this.state.layers.isSnappingActive = false; } super.girafeTranslate(); } renderComponent() { this.show(); if (!this.renderedOnce) { this.renderedOnce = true; this.buttons.forEach((b) => { this.getById(b.id).addEventListener('pointerup', () => { this.setTool(b.tool); if (b.tool) { this.deselectAllFeatures(); this.refreshRender(); } }); }); this.addColorPicker('nameColorPicker', (c) => this.selectedFeatures.forEach((f) => (f.nameColor = c.hex)), () => this.selectedFeatures[0].nameColor); this.addColorPicker('measureColorPicker', (c) => this.selectedFeatures.forEach((f) => (f.measureColor = c.hex)), () => this.selectedFeatures[0].measureColor); this.addColorPicker('fillPicker', (c) => this.selectedFeatures.forEach((f) => (f.fillColor = c.hex)), () => this.selectedFeatures[0].fillColor); this.addColorPicker('strokePicker', (c) => this.selectedFeatures.forEach((f) => (f.strokeColor = c.hex)), () => this.selectedFeatures[0].strokeColor); this.getById('optionsTitle').oninput = (e) => { this.selectedFeatures[0].name = e.target.value; this.refreshRender(); }; this.setTool(); } this.warnWhenInWebMercator(); } refreshRender() { // Set the color picker color to the properties of the first selected feature. Necessary, so subsequently // selected features do not change color immediately upon selecting them, but only after manually setting // the color via color picker. if (this.selectedFeatures.length === 1) { this.colorPickers.forEach((val) => val[0].setColor(val[1](), false)); } super.refreshRender(); } registerEvents() { this.olDrawing.registerInteractions(); if (this.state.globe.loaded) this.cesiumDrawing.registerInteractions(); } unregisterEvents() { this.olDrawing.unregisterInteractions(); this.cesiumDrawing.unregisterInteractions(); } addColorPicker(id, set, get) { const parent = this.getById(id); const picker = new GirafeColorPicker({ parent: parent, popup: 'top' }, !this.state.interface.isMobile); const update = (c) => { set(c); parent.style.backgroundColor = c.hex; }; picker.onChange = update; picker.onDone = update; this.colorPickers.push([picker, get]); } setTool(tool = null) { if (tool) { this.activateLayerInTreeAndMap(); } if (this.toolSelected !== null) { this.toolSelected.classList.remove('selected'); } this.toolSelected = this.getById(this.buttons.find((x) => x.tool == tool).id); this.toolSelected.classList.add('selected'); this.drawingState.activeTool = tool; this.showToolParameters(tool); } showToolParameters(tool = null) { const toolName = tool ? DrawingShape[tool] : 'Pointer'; const toolParametersList = this.shadowRoot.querySelectorAll('.toolParameters'); let hasParameters = false; toolParametersList.forEach((toolParameters) => { const isParametersForTool = toolParameters.dataset.tool?.includes(toolName) ?? false; toolParameters.style.display = isParametersForTool ? 'flex' : 'none'; hasParameters ||= isParametersForTool; }); this.shadow.querySelector('#toolParametersContainer span.title').style.display = hasParameters ? 'inline-block' : 'none'; } fixedDimensionValueChangedHandler(e) { const details = e.detail; switch (details.id) { case 'fixedLineLength': this.olDrawing.setFixedLineLength(details.value); break; case 'fixedSquareSide': this.olDrawing.setFixedSquareSide(details.value); break; case 'fixedRectangleWidth': this.olDrawing.setFixedRectangleWidth(details.value); break; case 'fixedRectangleHeight': this.olDrawing.setFixedRectangleHeight(details.value); break; default: console.debug('Value changed for unknown Dimension with id', details.id); } } connectedCallback() { super.connectedCallback(); if (!this.state.extendedState[DrawingStateLocation]) { throw new Error(`ExtendedState '${DrawingStateLocation}' has to be defined in main typescript file.`); } this.drawingState = this.state.extendedState[DrawingStateLocation]; const map = this.context.componentManager.getComponents(MapComponent)[0]; this.olDrawing = new OlDrawing(map, DrawingStateLocation, this.context, (featureId) => { const drawingFeature = this.drawingState.features.find((drawingFeature) => drawingFeature.id === featureId); if (drawingFeature) { this.deleteFeature(drawingFeature); } }); this.cesiumDrawing = new CesiumDrawing(map, DrawingStateLocation, this.context); this.render(); this.subscribe(`extendedState.${DrawingStateLocation}.features`, (olds, news) => this.onFeaturesChanged(olds, news)); this.subscribe('projection', (olds, news) => this.onProjectionChanged(olds, news)); this.subscribe('globe.loaded', () => { if (this.state.globe.loaded && this.isPanelVisible) { this.cesiumDrawing.registerInteractions(); } else { this.cesiumDrawing.unregisterInteractions(); } }); this.subscribe('projection', (_, newProjection) => this.warnWhenInWebMercator(newProjection)); } togglePanel(visible) { if (this.isPanelVisible == visible) return; this.isPanelVisible = visible; if (this.isPanelVisible) { this.registerEvents(); if (this.activeDrawingLayer) { this.activateLayerInTreeAndMap(); } } else { this.setTool(null); // Deselect features so the vertex symbology disappears this.deselectAllFeatures(); // Unregister events, remove interactions this.unregisterEvents(); } this.render(); } get selectedFeatures() { return this.drawingState.features.filter((f) => f.selected); } deselectAllFeatures() { this.drawingState.features.forEach((feature) => (feature.selected = false)); } onFeaturesChanged(oldFeatures, newFeatures) { oldFeatures = oldFeatures ?? []; const newIds = newFeatures.map((f) => f.id); const oldIds = oldFeatures.map((f) => f.id); const deleted = oldFeatures.filter((f) => !newIds.includes(f.id)); const added = newFeatures.filter((f) => !oldIds.includes(f.id)); // Update the current feature selection if (!this.isPanelVisible || this.batchCreateMode) { // If the component isn't visible (e.g. if features are added via shared state), deselect all features this.deselectAllFeatures(); } else if (added.length > 0) { // Only select the newly created feature this.drawingState.features.forEach((feature) => (feature.selected = added.map((f) => f.id).includes(feature.id))); } // Toggle the drawing layer in the tree and map if (newIds.length > 0) { this.activateLayerInTreeAndMap(); } else { this.deactivateLayerInTreeAnMap(); } // Update drawing source if (deleted.length > 0) this.olDrawing.deleteFeatures(deleted); if (added.length > 0) this.olDrawing.addFeatures(added); // OlCesium is currently managing features in Cesium //this.cesiumDrawing.addFeatures(added) //this.cesiumDrawing.deleteFeatures(deleted) if (this.isPanelVisible) { // Deactivate the drawing tool after finishing the shape if (!this.batchCreateMode) { this.setTool(null); } this.refreshRender(); } } onProjectionChanged(oldProj, newProj) { if (oldProj != null && oldProj != newProj) { const geoJson = new GeoJSON(); const features = [...this.drawingState.features]; features.forEach((f) => { // TODO Handle the case of disks f.geojson = geoJson.writeFeatureObject(geoJson.readFeature(f.geojson, { dataProjection: oldProj, featureProjection: newProj })); }); // Refresh all the listeners this.drawingState.features = []; this.drawingState.features = features; } } onToggleBatchMode() { this.batchCreateMode = !this.batchCreateMode; if (this.batchCreateMode) { this.deselectAllFeatures(); } this.refreshRender(); } onToggleFeatureSelection(feature) { this.setTool(null); feature.selected = !feature.selected; this.refreshRender(); } activateLayerInTreeAndMap(layerName = this.defaultLayerName) { this.activeDrawingLayer ??= new LayerDrawing(layerName, this.olDrawing.drawingLayer); // Activate the layer by adding it to the tree and making it visible in the map this.context.userLayerManager.addUserLayerToTree(this.activeDrawingLayer); } deactivateLayerInTreeAnMap() { if (this.activeDrawingLayer) { this.context.userLayerManager.removeUserLayerFromTree(this.activeDrawingLayer); this.activeDrawingLayer = undefined; } } getOptionsTitle() { if (this.selectedFeatures.length < 2) { return this.selectedFeatures[0]?.name || ''; } else { return this.context.i18nManager .getTranslation('multiple-shapes-selected') .replace('${shape-count}', this.selectedFeatures.length.toString()); } } isDisplayNameEnabled() { return this.selectedFeatures.some((f) => f.displayName); } isLineStyleEnabled() { return this.selectedFeatures.some((f) => f.type == DrawingShape.Polyline || f.type == DrawingShape.FreehandPolyline); } isFillColorEnabled() { return !this.selectedFeatures.every((f) => f.isPointOrPolyline()); } async deleteFeature(feature) { const confirm = await window.gConfirm('Do you want to remove "${feature.name}" ?', 'Delete Feature', (translatedMessage) => { return translatedMessage.replace('${feature.name}', feature.name); }); if (confirm) { const idx = this.drawingState.features.findIndex((f) => f.id === feature.id); if (idx > -1) { this.drawingState.features.splice(idx, 1); this.refreshRender(); } } } async deleteAllFeatures() { const confirm = await window.gConfirm('Do you want to remove all features?', 'Delete Features'); if (confirm) { this.drawingState.features = []; this.refreshRender(); } } onOptionsChange() { const nameFontSize = parseInt(this.getById('optionsNameFontSize').value); const measureFontSize = parseInt(this.getById('optionsMeasuresFontSize').value); const strokeWidth = parseInt(this.getById('optionsStrokeWidth').value); const lineStroke = this.getById('line-style').value; const measureInformation = this.getById('measure-information').value; this.selectedFeatures.forEach((f) => (f.nameFontSize = nameFontSize)); this.selectedFeatures.forEach((f) => (f.measureFontSize = measureFontSize)); this.selectedFeatures.forEach((f) => (f.strokeWidth = strokeWidth)); this.selectedFeatures.forEach((f) => (f.lineStroke = lineStroke)); if (measureInformation !== 'mixed') { this.selectedFeatures.forEach((f) => (f.measureInformation = measureInformation)); } this.refreshRender(); } onArrowsChange() { const arrowStyle = this.getById('arrow-style').value; const arrowPosition = this.getById('arrow-position').value; this.selectedFeatures.forEach((f) => (f.arrowStyle = arrowStyle)); this.selectedFeatures.forEach((f) => (f.arrowPosition = arrowPosition)); } toggleNameVisibility() { const currentVisibility = this.selectedFeatures.some((f) => f.displayName); this.selectedFeatures.forEach((f) => (f.displayName = !currentVisibility)); this.refreshRender(); } warnWhenInWebMercator(projection = this.state.projection) { if (this.isPanelVisible && projection === 'EPSG:3857') { const errorMessage = 'Web Mercator projection distorts distances and areas'; this.context.stateManager.state.infobox.elements.push({ id: uuidv4(), text: this.context.i18nManager.getTranslation(errorMessage), type: 'warning' }); } } exportSelectedFeatures(format) { const olFeatures = []; const fileName = this.selectedFeatures.length === 1 ? this.selectedFeatures[0].name : 'drawing_export'; this.selectedFeatures.forEach((feature) => { let olFeature = this.olDrawing.createOlFeature(feature); if (feature.type == DrawingShape.Disk) { const geojson = feature.geojson; const style = olFeature.getStyle(); olFeature = new Feature(new Polygon([DrawingFeature.circleToPolygon(geojson.geometry.center, geojson.geometry.radius)])); olFeature.setStyle(style); } olFeatures.push(olFeature); }); switch (format) { case 'geojson': return download(JSON.stringify(new GeoJSON().writeFeaturesObject(olFeatures, { featureProjection: this.state.projection })), fileName + '.geojson', 'application/geo+json'); case 'kml': return download(new KML().writeFeatures(olFeatures, { featureProjection: this.state.projection }), fileName + '.kml', 'application/vnd.google-earth.kml+xml'); case 'gpx': if (this.selectedFeatures.some((f) => !f.isPointOrPolyline())) { this.context.errorManager.pushMessage('export-gpx-error', 'The GPX format only supports points and polylines', 'warning'); } else { return download(new GPX().writeFeatures(olFeatures, { featureProjection: this.state.projection }), fileName + '.gpx', 'application/gpx+xml'); } } } } __decorate([ UsedInTemplateOnly() ], DrawingComponent.prototype, "fixedDimensionValueChangedHandler", null); __decorate([ UsedInTemplateOnly('Currently not used, will possibly be part of advanced editing/drawing tools') ], DrawingComponent.prototype, "onToggleBatchMode", null); __decorate([ UsedInTemplateOnly() ], DrawingComponent.prototype, "getOptionsTitle", null); __decorate([ UsedInTemplateOnly() ], DrawingComponent.prototype, "isDisplayNameEnabled", null); __decorate([ UsedInTemplateOnly() ], DrawingComponent.prototype, "isLineStyleEnabled", null); __decorate([ UsedInTemplateOnly() ], DrawingComponent.prototype, "isFillColorEnabled", null);