UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

614 lines (608 loc) 32.6 kB
import { htmlFor as uHtmlFor } from 'uhtml/keyed'; import { html as uHtml } from 'uhtml'; import { v4 as uuidv4 } from 'uuid'; import DrawingFeature, { DrawingState, DrawingShape } from './drawingFeature'; import OlDrawing from './olDrawing'; import CesiumDrawing from './cesiumDrawing'; import { KML, GeoJSON, GPX } from 'ol/format'; import { Polygon } from 'ol/geom'; import Feature from 'ol/Feature'; import GirafeHTMLElement from '../../base/GirafeHTMLElement'; import { download } from '../../tools/export/download'; import MapComponent from '../map/component'; import GirafeColorPicker from '../../tools/utils/girafecolorpicker'; import checkedIcon from '../../assets/icons/checked-full.svg?raw'; import noCheckedIcon from '../../assets/icons/checked-no.svg?raw'; import trashIcon from '../../assets/icons/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 I18nManager from '../../tools/i18n/i18nmanager'; import ErrorManager from '../../tools/error/errormanager'; export default class DrawingComponent extends GirafeHTMLElement { constructor() { super('drawing'); Object.defineProperty(this, "template", { enumerable: true, configurable: true, writable: true, value: () => { return uHtml `<style> *{font-family:Arial,sans-serif}.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}.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-grad2);background-color:var(--text-color-grad2);color:var(--bkg-color)}.gg-button:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3;border:none}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3}.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-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,.gg-select:hover,.gg-input:hover,.gg-textarea:hover,.gg-icon-button:hover{background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;border-bottom:1px solid gray;justify-content:left;align-items:end;gap:1rem;height:2rem;margin:.5rem auto 1rem;display:flex}.gg-tab{cursor:pointer;color:var(--text-color);background:0 0;border:none;padding:.5rem}.gg-tab.active{border-bottom:solid 2px var(--text-color-grad2);font-weight:600}.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} </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}.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}.disabled{pointer-events:none;opacity:.5}#panel{height:100%;color:var(--text-color);flex-direction:column;display:flex}#panel>div{padding:1rem}.toolParameters{grid-template-columns:auto auto 60px auto;justify-content:left;align-items:center;margin-top:10px;display:grid}.toolParameters>label{padding:0 5px}#drawingListContainer{flex-grow:1;overflow:hidden auto}#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}#optionsTextLines,#optionsColorLine,#optionsLineLines{align-items:center;display:grid}#optionsText{margin:auto}#optionsTextLines{grid-template-columns:65px 40px 35px 40px 20px 25px}#optionsTextLines button:hover{border:none}#optionsColor,#optionsLine,#optionsExport{margin:auto}#optionsColorLine{grid-template-columns:77px 70px 56px 26px}#optionsLineLines{grid-template-columns:60px 40px 20px}#optionsExportLine{grid-template-columns:60px 60px 45px 45px;gap:.2rem;display:flex} </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 class="toolParameters"> <button id="fixedLengthEnabled" class="${this.fixedLengthOptionAllowed() ? 'gg-icon-button icon' : 'gg-icon-button icon disabled'}" onclick="${() => this.onToggleFixedLength()}"> ${this.htmlUnsafe(this.fixedLengthEnabled ? this.checkedIcon : this.noCheckedIcon)} </button> <label class="${this.fixedLengthEnabled && this.fixedLengthOptionAllowed() ? 'gg-label' : 'gg-label disabled'}" for="fixedLengthValue" i18n="Fix length to" >Fix length to </label> <input class="${this.fixedLengthEnabled && this.fixedLengthOptionAllowed() ? 'gg-input' : 'gg-input disabled'}" type="number" id="fixedLengthValue" /> <label class="${this.fixedLengthEnabled && this.fixedLengthOptionAllowed() ? 'gg-label' : 'gg-label disabled'}" for="fixedLengthValue" i18n="meters" >meters</label > </div> </div> <div id="drawingListContainer"> <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'}"> <input id="optionsTitle" type="text" value="${this.getOptionsTitle()}" class="${this.selectedFeatures.length > 1 ? 'gg-input disabled' : 'gg-input'}" /> <div id="optionsText"> <div id="optionsTextLines"> <span 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 i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}" /> <span>pt</span> <div class="icon colorPicker" id="nameColorPicker"></div> <span i18n="Measures">Measures</span> <button class="gg-icon-button icon" id="visibleIconMeasure" tip="Show or hide measure information" onclick="${() => this.toggleMeasureVisibility()}"> ${this.htmlUnsafe(this.isDisplayMeasureEnabled() ? this.visibleIcon : this.notVisibleIcon)} </button> <span i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" oninput="${() => this.onOptionsChange()}" /> <span>pt</span> <div class="icon colorPicker" id="measureColorPicker"></div> </div> </div> <hr /> <div id="optionsColor"> <div id="optionsColorLine"> <span i18n="Stroke color">Stroke color</span> <div class="icon colorPicker" id="strokePicker"></div> <span id="fillPickerSpan" class="${this.isFillColorEnabled() ? '' : 'disabled'}" i18n="Fill color" >Fill color</span > <div class="${'icon colorPicker ' + (this.isFillColorEnabled() ? '' : 'disabled')}" id="fillPicker"></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>px</span> </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> </div> </div> `; } }); Object.defineProperty(this, "checkedIcon", { enumerable: true, configurable: true, writable: true, value: checkedIcon }); Object.defineProperty(this, "noCheckedIcon", { enumerable: true, configurable: true, writable: true, value: noCheckedIcon }); Object.defineProperty(this, "trashIcon", { enumerable: true, configurable: true, writable: true, value: trashIcon }); Object.defineProperty(this, "locateIcon", { enumerable: true, configurable: true, writable: true, value: locateIcon }); Object.defineProperty(this, "visibleIcon", { enumerable: true, configurable: true, writable: true, value: visibleIcon }); Object.defineProperty(this, "notVisibleIcon", { enumerable: true, configurable: true, writable: true, value: notVisibleIcon }); Object.defineProperty(this, "visible", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "renderedOnce", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "drawingState", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "colorPickers", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "buttons", { enumerable: true, configurable: true, writable: true, value: [ { 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 } ] }); Object.defineProperty(this, "toolSelected", { enumerable: true, configurable: true, writable: true, value: null }); Object.defineProperty(this, "olDrawing", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "cesiumDrawing", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "fixedLengthEnabled", { enumerable: true, configurable: true, writable: true, value: 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. Object.defineProperty(this, "batchCreateMode", { enumerable: true, configurable: true, writable: true, value: false }); if (!this.state.extendedState.drawing) { this.state.extendedState.drawing = new DrawingState(); } this.drawingState = this.state.extendedState.drawing; const map = this.componentManager.getComponents(MapComponent)[0]; this.olDrawing = new OlDrawing(map, this.name); this.cesiumDrawing = new CesiumDrawing(map, this.name); this.subscribe('extendedState.drawing.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.visible) { this.cesiumDrawing.registerInteractions(); } else { this.cesiumDrawing.unregisterInteractions(); } }); } render() { super.render(); if (this.visible) { this.renderComponent(); } else { this.hide(); } super.girafeTranslate(); } renderComponent() { this.show(); if (!this.renderedOnce) { this.renderedOnce = true; this.buttons.forEach((b) => { this.getById(b.id).addEventListener('click', () => { 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.getById('fixedLengthValue').oninput = (e) => { const val = parseFloat(e.target.value); this.olDrawing.setFixedLength(val); this.cesiumDrawing.setFixedLength(val); }; this.getById('fixedLengthEnabled').onchange = (e) => { const elements = Array.from(this.shadowRoot.querySelectorAll('.fixedLengthElement')); const option = e.target; if (!option.disabled && option.checked) { elements.forEach((e) => e.classList.remove('disabled')); this.getById('fixedLengthValue').dispatchEvent(new Event('input')); } else { elements.forEach((e) => e.classList.add('disabled')); this.olDrawing.setFixedLength(0); this.cesiumDrawing.setFixedLength(0); } }; 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' }, true); 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 (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; } fixedLengthOptionAllowed() { // Enable / disable fix length checkbox based on current tool return ![ null, DrawingShape.Point, DrawingShape.Rectangle, DrawingShape.FreehandPolyline, DrawingShape.FreehandPolygon ].includes(this.drawingState.activeTool); } connectedCallback() { this.loadConfig().then(() => { this.render(); this.subscribe('interface.drawingPanelVisible', (_, newValue) => this.togglePanel(newValue)); this.subscribe('projection', (_, newProjection) => this.warnWhenInWebMercator(newProjection)); }); } togglePanel(visible) { this.visible = visible; if (this.visible) { this.registerEvents(); } 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.visible || this.batchCreateMode) { // If component isn't visible (e.g. if features are added via share link) // or user is in batch mode, 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))); } // 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.visible) { // 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() { // Currently not used, will possibly be part of advanced editing/drawing tools this.batchCreateMode = !this.batchCreateMode; if (this.batchCreateMode) { this.deselectAllFeatures(); } this.refreshRender(); } onToggleFixedLength() { this.fixedLengthEnabled = !this.fixedLengthEnabled; const val = this.fixedLengthEnabled ? parseFloat(this.getById('fixedLengthValue').value) : 0; this.olDrawing.setFixedLength(val); this.cesiumDrawing.setFixedLength(val); this.refreshRender(); } onToggleFeatureSelection(feature) { feature.selected = !feature.selected; this.refreshRender(); } getOptionsTitle() { if (this.selectedFeatures.length < 2) { return this.selectedFeatures[0]?.name || ''; } else { return `${this.selectedFeatures.length} Shapes selected`; } } isDisplayNameEnabled() { return this.selectedFeatures.some((f) => f.displayName); } isDisplayMeasureEnabled() { return this.selectedFeatures.some((f) => f.displayMeasure); } 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'); if (confirm) { const idx = this.drawingState.features.findIndex((f) => f.id === feature.id); if (idx > -1) { this.drawingState.features.splice(idx, 1); this.refreshRender(); } } } onOptionsChange() { const nameFontSize = parseInt(this.getById('optionsNameFontSize').value); const measureFontSize = parseInt(this.getById('optionsMeasuresFontSize').value); const strokeWidth = parseInt(this.getById('optionsStrokeWidth').value); this.selectedFeatures.forEach((f) => (f.nameFontSize = nameFontSize)); this.selectedFeatures.forEach((f) => (f.measureFontSize = measureFontSize)); this.selectedFeatures.forEach((f) => (f.strokeWidth = strokeWidth)); } toggleNameVisibility() { const currentVisibility = this.selectedFeatures.some((f) => f.displayName); this.selectedFeatures.forEach((f) => (f.displayName = !currentVisibility)); this.refreshRender(); } toggleMeasureVisibility() { const currentVisibility = this.selectedFeatures.some((f) => f.displayMeasure); this.selectedFeatures.forEach((f) => (f.displayMeasure = !currentVisibility)); this.refreshRender(); } warnWhenInWebMercator(projection = this.state.projection) { if (this.visible && projection === 'EPSG:3857') { const errorMessage = 'Web Mercator projection distorts distances and areas'; this.stateManager.state.infobox.elements.push({ id: uuidv4(), text: I18nManager.getInstance().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())) { ErrorManager.getInstance().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'); } } } }