@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
477 lines (476 loc) • 37.4 kB
JavaScript
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}.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);color:var(--text-color)}.girafe-onboarding-theme button{background-color:var(--bkg-color);color:var(--text-color);text-shadow:none}.girafe-onboarding-theme button.driver-popover-close-btn{z-index:10000}.tippy-box{background-color:var(--bkg-color);border:var(--app-standard-border)}.tippy-content{color:var(--text-color)}.tippy-arrow{color:var(--bkg-color)}.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);& div.tippy-content{background-color:var(--bkg-color);& img{max-width:20rem;max-height:20rem}}& div.tippy-arrow{color:var(--bkg-color)}}
</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)}.picker_wrapper,.picker_arrow:after,.picker_arrow:before{background-color:var(--bkg-color)}.picker_wrapper,.picker_arrow:before{box-shadow:0 0 10px 1px var(--text-color-grad1)}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;& .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);