@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
609 lines (603 loc) • 32.6 kB
JavaScript
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}.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{color:var(--link-color)}a:visited{color:var(--link-color)}@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}100%{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-iteration-count:infinite;animation-timing-function:linear}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-iteration-count:infinite;animation-timing-function:linear}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}.gg-button,.gg-input,.gg-select,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);outline:0;border-radius:3px;border:var(--app-standard-border);display:inline-block;box-sizing:border-box;padding:0 0 0 .5rem;margin:0;cursor:pointer}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;display:inline-block;padding:0;margin:0}.gg-button,.gg-input,.gg-label,.gg-select{min-height:calc(var(--app-standard-height)/ 1.5);max-height:calc(var(--app-standard-height)/ 1.5);line-height:calc(var(--app-standard-height) / 1.5)}.gg-textarea{padding:.5rem;height:6rem}.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{background-color:#d3d3d3;color:grey;border:none;cursor:not-allowed}.gg-button>img{vertical-align:middle}.gg-icon-button{border:none;background-color:transparent;display:flex;flex-direction:column;color:var(--text-color);padding:0;align-items:center;justify-content:center;cursor:pointer}.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{margin:0;width:calc(var(--app-standard-height) - 1.5rem)}.gg-big-withtext span{font-size:.9rem;font-variant:small-caps;padding:0 1rem}.gg-medium,.gg-medium-withtext{flex-direction:row;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)}.gg-medium img{margin:0;width:calc(var(--app-standard-height)/ 2.4)}.gg-medium-withtext img{margin-left:.5rem;width:calc(var(--app-standard-height)/ 2.4)}.gg-medium-withtext span{font-size:.9rem;padding:0 1rem;padding-left:.5rem}.gg-small,.gg-small-withtext{flex-direction:row;min-width:calc(var(--app-standard-height)/ 2);min-height:calc(var(--app-standard-height)/ 2);max-height:calc(var(--app-standard-height)/ 2)}.gg-small img{margin:0;width:calc(var(--app-standard-height)/ 3)}.gg-small-withtext img{margin-left:.5rem;width:calc(var(--app-standard-height)/ 3)}.gg-small-withtext span{font-size:.9rem;padding:0 .5rem;padding-left:.3rem}.gg-button:hover,.gg-icon-button:hover,.gg-input:hover,.gg-select:hover,.gg-textarea:hover{background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{background-color:transparent;opacity:1}.gg-tabs{display:flex;justify-content:left;margin:.5rem auto 0;border-bottom:1px solid grey;cursor:pointer;height:2rem;align-items:end;gap:1rem;margin-bottom:1rem}.gg-tab{border:none;background:0 0;cursor:pointer;padding:.5rem;color:var(--text-color)}.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{border:none;background-color:transparent;display:flex;flex-direction:column;color:var(--text-color)}.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(1)}.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{margin:.3rem;height:2rem}.girafe-button-large span{line-height:2rem;height:2rem;margin:.3rem}.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{width:100%;text-align:left;overflow:hidden;text-overflow:ellipsis}.girafe-button-tiny{width:1rem;height:1rem;align-items:center;padding:0}.girafe-button-tiny img{overflow:hidden}
</style><style>
input{border:1px solid var(--bkg-color-grad1);width:inherit;padding-right:0;background-color:var(--bkg-color-input)}input:focus{border:1px solid var(--lt-color-gray-900)}button{border:solid 1px var(--bkg-color);color:var(--text-color);padding:0;background-color:var(--bkg-color);cursor:pointer}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{display:flex;flex-wrap:wrap}.label-container{flex-grow:1}.icon{width:1.2rem;height:1.2rem;fill:var(--text-color);margin:3px;flex-shrink:0}.icon:hover{fill:var(--text-color-grad2)}.gg-icon-button:hover{border:none}.colorPicker{border-radius:5px;border:1px solid var(--text-color)}.picker_wrapper{transform:translateX(-250px)}.picker_arrow{transform:translateX(200px) rotate(90deg) scaleX(-1)}.picker_arrow::after,.picker_arrow::before,.picker_wrapper{background-color:var(--bkg-color)}.picker_arrow::before,.picker_wrapper{box-shadow:0 0 10px 1px var(--text-color-grad1)}.disabled{pointer-events:none;opacity:.5}#panel{display:flex;flex-direction:column;height:100%;color:var(--text-color)}#panel>div{padding:1rem}.toolParameters{display:grid;justify-content:left;grid-template-columns:auto auto 60px auto;align-items:center;margin-top:10px}.toolParameters>label{padding:0 5px}#drawingListContainer{overflow-y:auto;overflow-x:hidden;flex-grow:1}#drawingList{display:flex;flex-flow:column;gap:.15rem}#drawingList .girafe{position:relative;display:flex;flex-wrap:wrap;align-items:center;padding:.3rem;border:solid 1px var(--text-color)}#drawingList .girafe span{flex-grow:1;padding-left:5px}#options{display:flex;flex-direction:column;box-shadow:0 -2px 3px -1px var(--text-color-grad1);position:relative;z-index:100}#optionsTitle{margin-bottom:20px;text-align:center;font-size:1.2rem}#options hr{width:100%;margin:8px}#optionsColorLine,#optionsLineLines,#optionsTextLines{display:grid;align-items:center}#optionsText{margin:auto}#optionsTextLines{grid-template-columns:65px 40px 35px 40px 20px 25px}#optionsTextLines button:hover{border:none}#optionsColor,#optionsExport,#optionsLine{margin:auto}#optionsColorLine{grid-template-columns:77px 70px 56px 26px}#optionsLineLines{grid-template-columns:60px 40px 20px}#optionsExportLine{display:flex;gap:.2rem;grid-template-columns:60px 60px 45px 45px}
</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();
this.visible ? this.renderComponent() : 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');
}
}
}
}