@geogirafe/lib-geoportal 1.2.0-dev.2750042516 → 1.2.0-dev.2751114005
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/assets/i18n/de.json +2 -0
- package/assets/i18n/en.json +2 -0
- package/assets/i18n/fr.json +2 -0
- package/assets/i18n/it.json +2 -0
- package/assets/icons/text-font.svg +1 -0
- package/components/drawing/component-mobile.js +1 -1
- package/components/drawing/component.d.ts +8 -1
- package/components/drawing/component.js +61 -6
- package/components/treeview/treeviewitem/component.js +9 -2
- package/models/layers/layerdrawing.d.ts +1 -0
- package/models/layers/layerdrawing.js +2 -1
- package/package.json +1 -1
- package/templates/public/about.json +1 -1
- package/tools/drawing/cesiumDrawing.d.ts +7 -0
- package/tools/drawing/cesiumDrawing.js +24 -1
- package/tools/drawing/drawingFeature.d.ts +3 -2
- package/tools/drawing/drawingFeature.js +5 -2
- package/tools/drawing/olDrawing.d.ts +41 -0
- package/tools/drawing/olDrawing.js +170 -15
package/LICENSE
CHANGED
|
@@ -246,6 +246,7 @@ From:
|
|
|
246
246
|
- https://www.svgrepo.com/svg/310166/thumb-dislike
|
|
247
247
|
- https://www.svgrepo.com/svg/309871/photo-filter
|
|
248
248
|
- https://www.svgrepo.com/svg/309651/full-screen-zoom
|
|
249
|
+
- https://www.svgrepo.com/svg/310134/text-font
|
|
249
250
|
|
|
250
251
|
|
|
251
252
|
The MIT License (MIT)
|
package/assets/i18n/de.json
CHANGED
|
@@ -524,6 +524,8 @@
|
|
|
524
524
|
"system": "System",
|
|
525
525
|
"Take measure": "Messen",
|
|
526
526
|
"Test for Translation": "Test für Übersetzung",
|
|
527
|
+
"Text": "Text",
|
|
528
|
+
"Text to draw": "Zu zeichnender Text",
|
|
527
529
|
"Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Vielen Dank für Ihre Nachricht. Falls erforderlich, wird sich so schnell wie möglich jemand mit Ihnen in Verbindung setzen.",
|
|
528
530
|
"The CSV export has been truncated to the maximum number of features allowed for download": "Der CSV-Export wurde auf die maximal zulässige Anzahl herunterladbarer Objekte begrenzt.",
|
|
529
531
|
"The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "Der folgende Link wird mit Ihrer Nachricht gesendet. Er enthält Ihre aktuelle Konfiguration und hilft uns, Ihr Problem zu verstehen.",
|
package/assets/i18n/en.json
CHANGED
|
@@ -527,6 +527,8 @@
|
|
|
527
527
|
"system": "System",
|
|
528
528
|
"Take measure": "Take measure",
|
|
529
529
|
"Test for Translation": "Test for Translation",
|
|
530
|
+
"Text": "Text",
|
|
531
|
+
"Text to draw": "Text to draw",
|
|
530
532
|
"Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Thanks for your message. If necessary, someone will get in touch with you as soon as possible.",
|
|
531
533
|
"The CSV export has been truncated to the maximum number of features allowed for download": "The CSV export has been truncated to the maximum number of features allowed for download.",
|
|
532
534
|
"The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.",
|
package/assets/i18n/fr.json
CHANGED
|
@@ -524,6 +524,8 @@
|
|
|
524
524
|
"system": "Système",
|
|
525
525
|
"Take measure": "Prendre une mesure",
|
|
526
526
|
"Test for Translation": "Test de traduction",
|
|
527
|
+
"Text": "Texte",
|
|
528
|
+
"Text to draw": "Texte à dessiner",
|
|
527
529
|
"Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Merci pour votre message. Si nécessaire, quelqu'un prendra contact avec vous dès que possible.",
|
|
528
530
|
"The CSV export has been truncated to the maximum number of features allowed for download": "L’export CSV a été limité au nombre maximal d’entités autorisées au téléchargement.",
|
|
529
531
|
"The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "Le lien ci-dessous sera envoyé avec votre message. Il contient votre configuration courante et nous aidera à analyser votre demande.",
|
package/assets/i18n/it.json
CHANGED
|
@@ -524,6 +524,8 @@
|
|
|
524
524
|
"system": "Sistema",
|
|
525
525
|
"Take measure": "Prendi misura",
|
|
526
526
|
"Test for Translation": "Test di traduzione",
|
|
527
|
+
"Text": "Testo",
|
|
528
|
+
"Text to draw": "Testo da disegnare",
|
|
527
529
|
"Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Grazie per il tuo messaggio. Se necessario, qualcuno ti contatterà il prima possibile.",
|
|
528
530
|
"The CSV export has been truncated to the maximum number of features allowed for download": "L'esportazione CSV è stata limitata al numero massimo di elementi consentiti per il download.",
|
|
529
531
|
"The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "Il seguente link verrà inviato con il tuo messaggio. Contiene la tua configurazione attuale e ci aiuterà a capire il problema.",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 24 24"><title>ic_fluent_text_font_24_regular</title><path fill="#212121" fill-rule="nonzero" d="M14.503 6a.75.75 0 0 1 .697.474l5.555 14.03h.494a.75.75 0 0 1 0 1.5H18.75a.75.75 0 0 1 0-1.5h.392l-1.19-3.004h-6.91L9.85 20.504h.4a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 1 1 0-1.5h.486l5.57-14.03A.75.75 0 0 1 14.503 6m0 2.785L11.637 16h5.72zM7 2a.75.75 0 0 1 .703.49l3.187 8.619-.828 2.086-.627-1.694H4.564L3.45 14.51a.75.75 0 0 1-1.407-.521l4.253-11.5A.75.75 0 0 1 7 2m0 2.913L5.118 10h3.763z"/></svg>
|
|
@@ -14,7 +14,7 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
|
|
|
14
14
|
button.selected{background-color:#daebff!important;border:1px solid #54a4ff!important}#toolbar{box-sizing:border-box;grid-template-columns:repeat(5,1fr);gap:20px;width:100%;display:grid}#toolbar>button{box-sizing:border-box;filter:drop-shadow(0 4px 7px #0004382c);background-color:#fff;border-radius:8px;padding:10px}#toolbar>button>img{opacity:.7;width:100%}.icon{width:1.5rem;height:1.5rem}.gg-icon-button:hover{background-color:#0000}#panel{font-size:1.5em}#drawingList{gap:10px}#drawingList .girafe{filter:drop-shadow(0 4px 7px #0004381c);background:#fff;border:none;border-radius:8px;padding:10px}#optionsTitle{text-align:center;filter:drop-shadow(0 4px 7px #0004381c);color:#4553ff;border-radius:8px;width:100%;margin-bottom:20px;padding:10px;font-size:1.6rem}.gg-input:hover{filter:drop-shadow(0 4px 7px #4c58ff70);background-color:#fff}#optionsColorLine{flex-direction:column;display:flex}#optionsColorLine>div{text-align:end;width:100%}.selectable-label{font-size:1.3em}#optionsNameFontSize,#optionsMeasuresFontSize,#optionsStrokeWidth,#line-style,#arrow-style,#arrow-position{text-align:right;border:1px solid #e3e3e3;padding:1px;font-size:1em}.popup.popup_top{translate:15px -15px}#optionsExportLine{display:none}
|
|
15
15
|
</style>
|
|
16
16
|
<style>${this.customStyle}</style>
|
|
17
|
-
<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.
|
|
17
|
+
<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> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.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 class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></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="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" 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="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" 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 class="${this.isLineStyleEnabled() ? '' : 'disabled'}" 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.isLineArrowEnabled() ? '' : '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>
|
|
18
18
|
${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
19
19
|
};
|
|
20
20
|
constructor() {
|
|
@@ -44,6 +44,7 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
|
|
|
44
44
|
olDrawing: OlDrawing;
|
|
45
45
|
cesiumDrawing: CesiumDrawing;
|
|
46
46
|
fixedLengthEnabled: boolean;
|
|
47
|
+
drawingText: string;
|
|
47
48
|
batchCreateMode: boolean;
|
|
48
49
|
constructor(name?: string);
|
|
49
50
|
render(): void;
|
|
@@ -55,6 +56,7 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
|
|
|
55
56
|
setTool(tool?: DrawingShape | null): void;
|
|
56
57
|
showToolParameters(tool?: DrawingShape | null): void;
|
|
57
58
|
protected fixedDimensionValueChangedHandler(e: CustomEvent): void;
|
|
59
|
+
protected drawingTextChangedHandler(e: Event): void;
|
|
58
60
|
protected connectedCallback(): void;
|
|
59
61
|
togglePanel(visible: boolean): void;
|
|
60
62
|
get selectedFeatures(): DrawingFeature[];
|
|
@@ -67,8 +69,12 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
|
|
|
67
69
|
deactivateLayerInTreeAnMap(): void;
|
|
68
70
|
protected getOptionsTitle(): string;
|
|
69
71
|
protected isDisplayNameEnabled(): boolean;
|
|
70
|
-
protected
|
|
72
|
+
protected isLineArrowEnabled(): boolean;
|
|
71
73
|
protected isFillColorEnabled(): boolean;
|
|
74
|
+
protected isStrokeColorEnabled(): boolean;
|
|
75
|
+
protected isLineStyleEnabled(): boolean;
|
|
76
|
+
protected isMeasureInformationEnabled(): boolean;
|
|
77
|
+
private selectedFeaturesHasTextFeature;
|
|
72
78
|
deleteFeature(feature: DrawingFeature): Promise<void>;
|
|
73
79
|
deleteAllFeatures(): Promise<void>;
|
|
74
80
|
onOptionsChange(): void;
|
|
@@ -76,5 +82,6 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
|
|
|
76
82
|
toggleNameVisibility(): void;
|
|
77
83
|
private warnWhenInWebMercator;
|
|
78
84
|
exportSelectedFeatures(format: 'geojson' | 'kml' | 'gpx'): void;
|
|
85
|
+
private onRemovalRequested;
|
|
79
86
|
}
|
|
80
87
|
export {};
|
|
@@ -25,6 +25,7 @@ import visibleIcon from './assets/visible.svg?raw';
|
|
|
25
25
|
import notVisibleIcon from './assets/notVisible.svg?raw';
|
|
26
26
|
import LayerDrawing from '../../models/layers/layerdrawing.js';
|
|
27
27
|
import { UsedInTemplateOnly } from '../../decorators.js';
|
|
28
|
+
import { noop } from '../../tools/utils/async.js';
|
|
28
29
|
export const DrawingStateLocation = 'drawing';
|
|
29
30
|
export default class DrawingComponent extends GirafeHTMLElement {
|
|
30
31
|
templateUrl = null;
|
|
@@ -36,7 +37,7 @@ export default class DrawingComponent extends GirafeHTMLElement {
|
|
|
36
37
|
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}
|
|
37
38
|
</style>
|
|
38
39
|
<style>${this.customStyle}</style>
|
|
39
|
-
<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.
|
|
40
|
+
<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> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.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 class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></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="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" 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="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" 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 class="${this.isLineStyleEnabled() ? '' : 'disabled'}" 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.isLineArrowEnabled() ? '' : '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>
|
|
40
41
|
${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
41
42
|
};
|
|
42
43
|
isPanelVisible = false;
|
|
@@ -60,7 +61,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
60
61
|
{ id: 'polygon', tool: DrawingShape.Polygon },
|
|
61
62
|
{ id: 'circle', tool: DrawingShape.Disk },
|
|
62
63
|
{ id: 'freeline', tool: DrawingShape.FreehandPolyline },
|
|
63
|
-
{ id: 'freepolygon', tool: DrawingShape.FreehandPolygon }
|
|
64
|
+
{ id: 'freepolygon', tool: DrawingShape.FreehandPolygon },
|
|
65
|
+
{ id: 'text', tool: DrawingShape.Text }
|
|
64
66
|
];
|
|
65
67
|
toolSelected = null;
|
|
66
68
|
arrowStyles = {
|
|
@@ -112,6 +114,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
112
114
|
olDrawing;
|
|
113
115
|
cesiumDrawing;
|
|
114
116
|
fixedLengthEnabled = false;
|
|
117
|
+
// Text used by the "Text" drawing tool for the shapes it creates.
|
|
118
|
+
drawingText = '';
|
|
115
119
|
// Batch Create mode is currently not used. Batch mode allows the user to create multiple shapes without re-selecting
|
|
116
120
|
// the drawing tool. It possibly will be part of advanced drawing/editing tools.
|
|
117
121
|
batchCreateMode = false;
|
|
@@ -168,6 +172,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
168
172
|
this.olDrawing.registerInteractions();
|
|
169
173
|
if (this.state.globe.loaded)
|
|
170
174
|
this.cesiumDrawing.registerInteractions();
|
|
175
|
+
this.subscribe(/layers\.layersList\..*\.removalRequested/, (oldValue, value, parents) => this.onRemovalRequested(oldValue, value, parents));
|
|
171
176
|
}
|
|
172
177
|
unregisterEvents() {
|
|
173
178
|
this.olDrawing.unregisterInteractions();
|
|
@@ -228,6 +233,11 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
228
233
|
console.debug('Value changed for unknown Dimension with id', details.id);
|
|
229
234
|
}
|
|
230
235
|
}
|
|
236
|
+
drawingTextChangedHandler(e) {
|
|
237
|
+
this.drawingText = e.target.value;
|
|
238
|
+
this.olDrawing.setDrawingText(this.drawingText);
|
|
239
|
+
this.cesiumDrawing.setDrawingText(this.drawingText);
|
|
240
|
+
}
|
|
231
241
|
connectedCallback() {
|
|
232
242
|
super.connectedCallback();
|
|
233
243
|
if (!this.state.extendedState[DrawingStateLocation]) {
|
|
@@ -244,6 +254,12 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
244
254
|
this.cesiumDrawing = new CesiumDrawing(map, DrawingStateLocation, this.context);
|
|
245
255
|
this.render();
|
|
246
256
|
this.subscribe(`extendedState.${DrawingStateLocation}.features`, (olds, news) => this.onFeaturesChanged(olds, news));
|
|
257
|
+
// Keep the shape list and the options in sync when the selection is changed in the map
|
|
258
|
+
this.subscribe(new RegExp(String.raw `extendedState.${DrawingStateLocation}.features.*\.selected`, 'g'), () => {
|
|
259
|
+
if (this.isPanelVisible) {
|
|
260
|
+
this.refreshRender();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
247
263
|
this.subscribe('projection', (olds, news) => this.onProjectionChanged(olds, news));
|
|
248
264
|
this.subscribe('globe.loaded', () => {
|
|
249
265
|
if (this.state.globe.loaded && this.isPanelVisible) {
|
|
@@ -258,6 +274,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
258
274
|
togglePanel(visible) {
|
|
259
275
|
if (this.isPanelVisible == visible)
|
|
260
276
|
return;
|
|
277
|
+
if (this.activeDrawingLayer?.removalRequested) {
|
|
278
|
+
this.deleteAllFeatures().then(noop);
|
|
279
|
+
}
|
|
261
280
|
this.isPanelVisible = visible;
|
|
262
281
|
if (this.isPanelVisible) {
|
|
263
282
|
this.registerEvents();
|
|
@@ -369,11 +388,27 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
369
388
|
isDisplayNameEnabled() {
|
|
370
389
|
return this.selectedFeatures.some((f) => f.displayName);
|
|
371
390
|
}
|
|
372
|
-
|
|
391
|
+
isLineArrowEnabled() {
|
|
373
392
|
return this.selectedFeatures.some((f) => f.type == DrawingShape.Polyline || f.type == DrawingShape.FreehandPolyline);
|
|
374
393
|
}
|
|
375
394
|
isFillColorEnabled() {
|
|
376
|
-
return
|
|
395
|
+
return this.selectedFeatures.every((f) => f.type == DrawingShape.Polygon ||
|
|
396
|
+
f.type == DrawingShape.FreehandPolygon ||
|
|
397
|
+
f.type == DrawingShape.Disk ||
|
|
398
|
+
f.type == DrawingShape.Rectangle ||
|
|
399
|
+
f.type == DrawingShape.Square);
|
|
400
|
+
}
|
|
401
|
+
isStrokeColorEnabled() {
|
|
402
|
+
return !this.selectedFeaturesHasTextFeature();
|
|
403
|
+
}
|
|
404
|
+
isLineStyleEnabled() {
|
|
405
|
+
return !this.selectedFeaturesHasTextFeature();
|
|
406
|
+
}
|
|
407
|
+
isMeasureInformationEnabled() {
|
|
408
|
+
return !this.selectedFeaturesHasTextFeature();
|
|
409
|
+
}
|
|
410
|
+
selectedFeaturesHasTextFeature() {
|
|
411
|
+
return this.selectedFeatures.some((f) => f.type == DrawingShape.Text);
|
|
377
412
|
}
|
|
378
413
|
async deleteFeature(feature) {
|
|
379
414
|
const confirm = await window.gConfirm('Do you want to remove "${feature.name}" ?', 'Delete Feature', (translatedMessage) => {
|
|
@@ -393,6 +428,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
393
428
|
this.drawingState.features = [];
|
|
394
429
|
this.refreshRender();
|
|
395
430
|
}
|
|
431
|
+
else {
|
|
432
|
+
this.activeDrawingLayer.removalRequested = false;
|
|
433
|
+
}
|
|
396
434
|
}
|
|
397
435
|
onOptionsChange() {
|
|
398
436
|
const nameFontSize = parseInt(this.getById('optionsNameFontSize').value);
|
|
@@ -449,7 +487,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
449
487
|
case 'kml':
|
|
450
488
|
return download(new KML().writeFeatures(olFeatures, { featureProjection: this.state.projection }), fileName + '.kml', 'application/vnd.google-earth.kml+xml');
|
|
451
489
|
case 'gpx':
|
|
452
|
-
if (this.selectedFeatures.some((f) => !f.
|
|
490
|
+
if (this.selectedFeatures.some((f) => !f.isPointPolylineOrText())) {
|
|
453
491
|
this.context.errorManager.pushMessage('export-gpx-error', 'The GPX format only supports points and polylines', 'warning');
|
|
454
492
|
}
|
|
455
493
|
else {
|
|
@@ -457,10 +495,18 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
457
495
|
}
|
|
458
496
|
}
|
|
459
497
|
}
|
|
498
|
+
onRemovalRequested(_oldValue, value, parents) {
|
|
499
|
+
if (value === true && parents.removalRequested) {
|
|
500
|
+
this.deleteAllFeatures().then(noop);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
460
503
|
}
|
|
461
504
|
__decorate([
|
|
462
505
|
UsedInTemplateOnly()
|
|
463
506
|
], DrawingComponent.prototype, "fixedDimensionValueChangedHandler", null);
|
|
507
|
+
__decorate([
|
|
508
|
+
UsedInTemplateOnly()
|
|
509
|
+
], DrawingComponent.prototype, "drawingTextChangedHandler", null);
|
|
464
510
|
__decorate([
|
|
465
511
|
UsedInTemplateOnly('Currently not used, will possibly be part of advanced editing/drawing tools')
|
|
466
512
|
], DrawingComponent.prototype, "onToggleBatchMode", null);
|
|
@@ -472,7 +518,16 @@ __decorate([
|
|
|
472
518
|
], DrawingComponent.prototype, "isDisplayNameEnabled", null);
|
|
473
519
|
__decorate([
|
|
474
520
|
UsedInTemplateOnly()
|
|
475
|
-
], DrawingComponent.prototype, "
|
|
521
|
+
], DrawingComponent.prototype, "isLineArrowEnabled", null);
|
|
476
522
|
__decorate([
|
|
477
523
|
UsedInTemplateOnly()
|
|
478
524
|
], DrawingComponent.prototype, "isFillColorEnabled", null);
|
|
525
|
+
__decorate([
|
|
526
|
+
UsedInTemplateOnly()
|
|
527
|
+
], DrawingComponent.prototype, "isStrokeColorEnabled", null);
|
|
528
|
+
__decorate([
|
|
529
|
+
UsedInTemplateOnly()
|
|
530
|
+
], DrawingComponent.prototype, "isLineStyleEnabled", null);
|
|
531
|
+
__decorate([
|
|
532
|
+
UsedInTemplateOnly()
|
|
533
|
+
], DrawingComponent.prototype, "isMeasureInformationEnabled", null);
|
|
@@ -8,6 +8,7 @@ import LayerWmts from '../../../models/layers/layerwmts.js';
|
|
|
8
8
|
import TreeViewElement from '../tools/treeviewelement.js';
|
|
9
9
|
import { isSnappableLayer } from '../../../models/layers/snappablelayer.js';
|
|
10
10
|
import WmsLegendHelper from '../../../tools/wms/wmslegendhelper.js';
|
|
11
|
+
import LayerDrawing from '../../../models/layers/layerdrawing.js';
|
|
11
12
|
class TreeViewItemComponent extends TreeViewElement {
|
|
12
13
|
templateUrl = null;
|
|
13
14
|
styleUrls = null;
|
|
@@ -224,8 +225,14 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
224
225
|
this.context.mapManager.zoomToExtent(this.layer.extent);
|
|
225
226
|
}
|
|
226
227
|
deleteLayer() {
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
if (this.layer instanceof LayerDrawing) {
|
|
229
|
+
this.layer.removalRequested = true;
|
|
230
|
+
this.context.stateManager.state.interface.drawingPanelVisible = true;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
this.context.layerManager.toggleLayer(this.layer, 'off');
|
|
234
|
+
this.removeFromParent();
|
|
235
|
+
}
|
|
229
236
|
}
|
|
230
237
|
connectedCallback() {
|
|
231
238
|
super.connectedCallback();
|
|
@@ -9,9 +9,10 @@ import Layer from './layer.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export default class LayerDrawing extends Layer {
|
|
11
11
|
_oLayer;
|
|
12
|
+
removalRequested = false;
|
|
12
13
|
constructor(name, oLayer) {
|
|
13
14
|
super(0, name, 0, { isDefaultChecked: true });
|
|
14
|
-
this.isRemovable =
|
|
15
|
+
this.isRemovable = true;
|
|
15
16
|
this.isSwipeable = false;
|
|
16
17
|
this.isDraggable = false;
|
|
17
18
|
this._oLayer = oLayer;
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.2.0-dev.
|
|
1
|
+
{"version":"1.2.0-dev.2751114005", "build":"2751114005", "date":"11/08/2026"}
|
|
@@ -9,12 +9,19 @@ export default class CesiumDrawing {
|
|
|
9
9
|
private handler;
|
|
10
10
|
private entities;
|
|
11
11
|
private fixedLineLength;
|
|
12
|
+
private drawingText;
|
|
12
13
|
private readonly context;
|
|
13
14
|
private get state();
|
|
14
15
|
private get drawingState();
|
|
15
16
|
private get config();
|
|
16
17
|
constructor(map: MapComponent, stateLocation: string, context: IGirafeContext);
|
|
17
18
|
setFixedLineLength(length: number): void;
|
|
19
|
+
setDrawingText(text: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* A text shape displays the text entered in the tool parameters instead of its generated default name.
|
|
22
|
+
* If no text has been entered, the default name is kept so that the shape stays visible on the globe.
|
|
23
|
+
*/
|
|
24
|
+
private getTextOf;
|
|
18
25
|
private activateTool;
|
|
19
26
|
private deactivateTool;
|
|
20
27
|
private pickOnGlobe;
|
|
@@ -15,6 +15,7 @@ export default class CesiumDrawing {
|
|
|
15
15
|
handler = undefined;
|
|
16
16
|
entities = undefined;
|
|
17
17
|
fixedLineLength = 0;
|
|
18
|
+
drawingText = '';
|
|
18
19
|
context;
|
|
19
20
|
get state() {
|
|
20
21
|
return this.context.stateManager.state;
|
|
@@ -40,6 +41,16 @@ export default class CesiumDrawing {
|
|
|
40
41
|
setFixedLineLength(length) {
|
|
41
42
|
this.fixedLineLength = Number.isNaN(length) ? 0 : length;
|
|
42
43
|
}
|
|
44
|
+
setDrawingText(text) {
|
|
45
|
+
this.drawingText = text;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A text shape displays the text entered in the tool parameters instead of its generated default name.
|
|
49
|
+
* If no text has been entered, the default name is kept so that the shape stays visible on the globe.
|
|
50
|
+
*/
|
|
51
|
+
getTextOf(feature) {
|
|
52
|
+
return this.drawingText.trim().length > 0 ? this.drawingText : feature.name;
|
|
53
|
+
}
|
|
43
54
|
activateTool(tool) {
|
|
44
55
|
if (!this.canExecute('globe.draw')) {
|
|
45
56
|
return;
|
|
@@ -83,6 +94,9 @@ export default class CesiumDrawing {
|
|
|
83
94
|
const newCesiumEntities = this.getShapes(tool, newCesiumEntityPoints, new DrawingFeature(tool, this.drawingState, this.config.drawing));
|
|
84
95
|
this.activeShapes.forEach((e) => this.entities.remove(e));
|
|
85
96
|
const newFeature = new DrawingFeature(tool, this.drawingState, this.config.drawing);
|
|
97
|
+
if (tool === DrawingShape.Text) {
|
|
98
|
+
newFeature.name = this.getTextOf(newFeature);
|
|
99
|
+
}
|
|
86
100
|
// The following code will be useful when we will remove OLCesium
|
|
87
101
|
/*newCesiumEntities.forEach((e) => this.entities!.add(e));
|
|
88
102
|
const updateStyle = (feature:DrawingFeature) => {
|
|
@@ -164,7 +178,7 @@ export default class CesiumDrawing {
|
|
|
164
178
|
this.activeShapes.forEach((e) => this.entities.add(e));
|
|
165
179
|
}
|
|
166
180
|
// Tools that automatically terminate the shape after a fixed number of points
|
|
167
|
-
if ((tool === DrawingShape.Point && this.activeShapePoints.length === 2) ||
|
|
181
|
+
if (((tool === DrawingShape.Point || tool === DrawingShape.Text) && this.activeShapePoints.length === 2) ||
|
|
168
182
|
(tool === DrawingShape.Disk && this.activeShapePoints.length === 3) ||
|
|
169
183
|
(tool === DrawingShape.Square && this.activeShapePoints.length === 3) ||
|
|
170
184
|
(tool === DrawingShape.Rectangle && this.activeShapePoints.length === 3)) {
|
|
@@ -221,6 +235,15 @@ export default class CesiumDrawing {
|
|
|
221
235
|
label: this.createLabel(feature.getCoordText(this.getPosition(pos[0])), font)
|
|
222
236
|
})
|
|
223
237
|
];
|
|
238
|
+
case DrawingShape.Text:
|
|
239
|
+
return [
|
|
240
|
+
new Entity({
|
|
241
|
+
position: pos[0],
|
|
242
|
+
// The anchor point is needed to export the shape as a KML placemark
|
|
243
|
+
point: this.createPoint(strokeColor),
|
|
244
|
+
label: this.createLabel(this.getTextOf(feature), font)
|
|
245
|
+
})
|
|
246
|
+
];
|
|
224
247
|
case DrawingShape.Polyline:
|
|
225
248
|
return [
|
|
226
249
|
new Entity({ polyline: this.getPolyline(pos, feature) }),
|
|
@@ -12,7 +12,8 @@ export declare enum DrawingShape {
|
|
|
12
12
|
Rectangle = 4,
|
|
13
13
|
Disk = 5,
|
|
14
14
|
FreehandPolyline = 6,
|
|
15
|
-
FreehandPolygon = 7
|
|
15
|
+
FreehandPolygon = 7,
|
|
16
|
+
Text = 8
|
|
16
17
|
}
|
|
17
18
|
export type ArrowStyle = 'none' | 'start' | 'end' | 'both';
|
|
18
19
|
export type ArrowPosition = 'whole' | 'each' | 'mid';
|
|
@@ -100,7 +101,7 @@ export default class DrawingFeature {
|
|
|
100
101
|
getAreaText(area: number): string;
|
|
101
102
|
getCoordText(coord: number[]): string;
|
|
102
103
|
getAzimuthText(circle: CircleGeom): string;
|
|
103
|
-
|
|
104
|
+
isPointPolylineOrText(): boolean;
|
|
104
105
|
getVertexStyle(activeNode?: boolean): Style;
|
|
105
106
|
static deserialize(serializedFeature: SerializedFeature, context: IGirafeContext, drawingState: DrawingState): DrawingFeature;
|
|
106
107
|
static circleToPolygon(center: number[], radius: number, nbEdges?: number): number[][];
|
|
@@ -8,6 +8,7 @@ import GeoJSON from 'ol/format/GeoJSON.js';
|
|
|
8
8
|
import { getAreaAsMetricText, getAzimuthAsText, getLengthAsMetricText } from '../utils/olutils.js';
|
|
9
9
|
import { fromCircle } from 'ol/geom/Polygon.js';
|
|
10
10
|
import LineString from 'ol/geom/LineString.js';
|
|
11
|
+
// New shapes must be appended at the end, as the numeric values are used to serialize features.
|
|
11
12
|
export var DrawingShape;
|
|
12
13
|
(function (DrawingShape) {
|
|
13
14
|
DrawingShape[DrawingShape["Point"] = 0] = "Point";
|
|
@@ -18,6 +19,7 @@ export var DrawingShape;
|
|
|
18
19
|
DrawingShape[DrawingShape["Disk"] = 5] = "Disk";
|
|
19
20
|
DrawingShape[DrawingShape["FreehandPolyline"] = 6] = "FreehandPolyline";
|
|
20
21
|
DrawingShape[DrawingShape["FreehandPolygon"] = 7] = "FreehandPolygon";
|
|
22
|
+
DrawingShape[DrawingShape["Text"] = 8] = "Text";
|
|
21
23
|
})(DrawingShape || (DrawingShape = {}));
|
|
22
24
|
export default class DrawingFeature {
|
|
23
25
|
static geoJSONFormat = new GeoJSON();
|
|
@@ -206,10 +208,11 @@ export default class DrawingFeature {
|
|
|
206
208
|
getAzimuthText(circle) {
|
|
207
209
|
return getAzimuthAsText(this.measureInformation === 'none' ? undefined : (circle.getProperties()['azimuth'] ?? undefined));
|
|
208
210
|
}
|
|
209
|
-
|
|
211
|
+
isPointPolylineOrText() {
|
|
210
212
|
return (this.type === DrawingShape.Point ||
|
|
211
213
|
this.type === DrawingShape.Polyline ||
|
|
212
|
-
this.type === DrawingShape.FreehandPolyline
|
|
214
|
+
this.type === DrawingShape.FreehandPolyline ||
|
|
215
|
+
this.type === DrawingShape.Text);
|
|
213
216
|
}
|
|
214
217
|
getVertexStyle(activeNode = false) {
|
|
215
218
|
const defaultConfig = this.defaultDrawingConfig;
|
|
@@ -21,12 +21,16 @@ export default class OlDrawing {
|
|
|
21
21
|
private fixedSquareSide;
|
|
22
22
|
private fixedRectangleWidth;
|
|
23
23
|
private fixedRectangleHeight;
|
|
24
|
+
private drawingText;
|
|
24
25
|
readonly drawingSource: VectorSource;
|
|
25
26
|
readonly drawingLayer: VectorLayer;
|
|
26
27
|
private lastClosestFeature;
|
|
27
28
|
private translate;
|
|
28
29
|
private transform;
|
|
29
30
|
private readonly modifyFeatureChangeListeners;
|
|
31
|
+
private hoverListener;
|
|
32
|
+
private selectListener;
|
|
33
|
+
private hoveredTextFeatureId;
|
|
30
34
|
defaultStyle: StyleFunction | undefined;
|
|
31
35
|
private readonly updateGeometryInState;
|
|
32
36
|
private get state();
|
|
@@ -46,6 +50,22 @@ export default class OlDrawing {
|
|
|
46
50
|
private addEditContextMenu;
|
|
47
51
|
private addEditInteractions;
|
|
48
52
|
private removeEditInteractions;
|
|
53
|
+
private addHoverListener;
|
|
54
|
+
private removeHoverListener;
|
|
55
|
+
private addSelectListener;
|
|
56
|
+
private removeSelectListener;
|
|
57
|
+
/**
|
|
58
|
+
* Lets the user select the shapes to edit directly in the map, as an alternative to the drawing list:
|
|
59
|
+
* a click selects the shape under the cursor exclusively, a click with the platform modifier key (ctrl / meta)
|
|
60
|
+
* adds the shape to the current selection or removes it from it. A click outside of any shape clears the selection.
|
|
61
|
+
*/
|
|
62
|
+
private selectClickedFeature;
|
|
63
|
+
/**
|
|
64
|
+
* Only writes to the state if the selection of the shape really changed, so that the map and the drawing
|
|
65
|
+
* panel are not refreshed for the shapes that are not concerned by the click.
|
|
66
|
+
*/
|
|
67
|
+
private setFeatureSelected;
|
|
68
|
+
private findFeatureIdAtPixel;
|
|
49
69
|
private readonly prepareMenuEntriesForVertex;
|
|
50
70
|
private readonly prepareMenuEntriesForShape;
|
|
51
71
|
/**
|
|
@@ -98,6 +118,12 @@ export default class OlDrawing {
|
|
|
98
118
|
setFixedSquareSide(length: number): void;
|
|
99
119
|
setFixedRectangleWidth(width: number): void;
|
|
100
120
|
setFixedRectangleHeight(height: number): void;
|
|
121
|
+
setDrawingText(text: string): void;
|
|
122
|
+
/**
|
|
123
|
+
* A text shape displays the text entered in the tool parameters instead of its generated default name.
|
|
124
|
+
* If no text has been entered, the default name is kept so that the shape stays visible on the map.
|
|
125
|
+
*/
|
|
126
|
+
private applyDrawingText;
|
|
101
127
|
private createLineStringFixedLength;
|
|
102
128
|
private createSquareFixedLength;
|
|
103
129
|
private createPolygonFixedLength;
|
|
@@ -106,11 +132,24 @@ export default class OlDrawing {
|
|
|
106
132
|
private addDrawInteraction;
|
|
107
133
|
centerViewOnFeature(drawingFeature: DrawingFeature): void;
|
|
108
134
|
private getStyle;
|
|
135
|
+
/**
|
|
136
|
+
* Vertices are rendered on the shapes selected for editing. A text shape is an exception: as its geometry is
|
|
137
|
+
* a single anchor point hidden behind the text, its vertex is only revealed while the cursor hovers the text.
|
|
138
|
+
*/
|
|
139
|
+
private shouldRenderVertices;
|
|
109
140
|
private removeDrawInteraction;
|
|
110
141
|
private removeModifyInteraction;
|
|
111
142
|
private getDrawingShapeType;
|
|
112
143
|
private isSquareOrRectangle;
|
|
144
|
+
private isSquareRectangleOrText;
|
|
113
145
|
private shouldAllowVertexInsertionAtEvent;
|
|
146
|
+
/**
|
|
147
|
+
* A text shape is only made of its text: its anchor point is hidden behind it. To let the user know where to
|
|
148
|
+
* grab the shape, the anchor vertex is revealed as long as the cursor hovers the text.
|
|
149
|
+
*/
|
|
150
|
+
private updateHoveredTextFeature;
|
|
151
|
+
private setHoveredTextFeature;
|
|
152
|
+
private findHoveredTextFeatureIdAtPixel;
|
|
114
153
|
private getHoveredModifiableFeatureAtPixel;
|
|
115
154
|
private applyScaleRotateOnModifyStyle;
|
|
116
155
|
private clearModifyFeatureChangeListeners;
|
|
@@ -119,5 +158,7 @@ export default class OlDrawing {
|
|
|
119
158
|
registerInteractions(): void;
|
|
120
159
|
unregisterInteractions(): void;
|
|
121
160
|
private canExecute;
|
|
161
|
+
private disableContextMenu;
|
|
162
|
+
private enableContextMenu;
|
|
122
163
|
private fixLastLength;
|
|
123
164
|
}
|
|
@@ -8,7 +8,8 @@ import { Draw, Modify, Snap, Translate } from 'ol/interaction.js';
|
|
|
8
8
|
import VectorSource from 'ol/source/Vector.js';
|
|
9
9
|
import VectorLayer from 'ol/layer/Vector.js';
|
|
10
10
|
import { getPointResolution } from 'ol/proj.js';
|
|
11
|
-
import { always, never, primaryAction } from 'ol/events/condition.js';
|
|
11
|
+
import { always, never, platformModifierKeyOnly, primaryAction } from 'ol/events/condition.js';
|
|
12
|
+
import { unByKey } from 'ol/Observable.js';
|
|
12
13
|
import { ensurePolygonIsProperlyClosed, getAreaOfPolygon, getDistance, getHalfPoint, getLabelStyle, getLengthAsMetricText, getRadiusDataForCircle, unByKeyAll } from '../utils/olutils.js';
|
|
13
14
|
import { ContextMenu, EntryInteractionType } from '../../components/map/tools/contextmenu.js';
|
|
14
15
|
import { formatCoordinates } from '../geometrytools.js';
|
|
@@ -64,12 +65,16 @@ export default class OlDrawing {
|
|
|
64
65
|
fixedSquareSide = 0;
|
|
65
66
|
fixedRectangleWidth = 0;
|
|
66
67
|
fixedRectangleHeight = 0;
|
|
68
|
+
drawingText = '';
|
|
67
69
|
drawingSource;
|
|
68
70
|
drawingLayer;
|
|
69
71
|
lastClosestFeature = null;
|
|
70
72
|
translate = null;
|
|
71
73
|
transform = null;
|
|
72
74
|
modifyFeatureChangeListeners = [];
|
|
75
|
+
hoverListener = null;
|
|
76
|
+
selectListener = null;
|
|
77
|
+
hoveredTextFeatureId = null;
|
|
73
78
|
defaultStyle;
|
|
74
79
|
updateGeometryInState = (olFeature) => {
|
|
75
80
|
const idx = this.drawingState.features.findIndex((f) => f.id === olFeature.getId());
|
|
@@ -324,21 +329,78 @@ export default class OlDrawing {
|
|
|
324
329
|
if (!this.editContextMenu) {
|
|
325
330
|
this.addEditContextMenu();
|
|
326
331
|
}
|
|
332
|
+
this.addHoverListener();
|
|
327
333
|
// Always recreate snap interaction to get snapping behavior on latest features
|
|
328
334
|
this.addSnapInteraction();
|
|
329
335
|
}
|
|
330
336
|
removeEditInteractions() {
|
|
331
337
|
this.removeModifyInteraction();
|
|
332
338
|
this.removeEditContextMenu();
|
|
339
|
+
this.removeHoverListener();
|
|
333
340
|
if (!this.draw) {
|
|
334
341
|
this.removeSnapInteraction();
|
|
335
342
|
}
|
|
336
343
|
}
|
|
344
|
+
addHoverListener() {
|
|
345
|
+
this.hoverListener ??= this.map.olMap.on('pointermove', (e) => this.updateHoveredTextFeature(e));
|
|
346
|
+
}
|
|
347
|
+
removeHoverListener() {
|
|
348
|
+
if (this.hoverListener) {
|
|
349
|
+
unByKey(this.hoverListener);
|
|
350
|
+
this.hoverListener = null;
|
|
351
|
+
}
|
|
352
|
+
this.setHoveredTextFeature(null);
|
|
353
|
+
}
|
|
354
|
+
addSelectListener() {
|
|
355
|
+
this.selectListener ??= this.map.olMap.on('singleclick', (e) => this.selectClickedFeature(e));
|
|
356
|
+
}
|
|
357
|
+
removeSelectListener() {
|
|
358
|
+
if (this.selectListener) {
|
|
359
|
+
unByKey(this.selectListener);
|
|
360
|
+
this.selectListener = null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Lets the user select the shapes to edit directly in the map, as an alternative to the drawing list:
|
|
365
|
+
* a click selects the shape under the cursor exclusively, a click with the platform modifier key (ctrl / meta)
|
|
366
|
+
* adds the shape to the current selection or removes it from it. A click outside of any shape clears the selection.
|
|
367
|
+
*/
|
|
368
|
+
selectClickedFeature(e) {
|
|
369
|
+
// While drawing, moving or transforming a shape, the click belongs to the interaction in progress
|
|
370
|
+
if (this.draw || this.translate || this.transform || !this.canExecute('map.select')) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const clickedFeatureId = this.findFeatureIdAtPixel(e.pixel);
|
|
374
|
+
if (platformModifierKeyOnly(e)) {
|
|
375
|
+
const clickedFeature = this.drawingState.features.find((f) => f.id === clickedFeatureId);
|
|
376
|
+
if (clickedFeature) {
|
|
377
|
+
this.setFeatureSelected(clickedFeature, !clickedFeature.selected);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
this.drawingState.features.forEach((f) => this.setFeatureSelected(f, f.id === clickedFeatureId));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Only writes to the state if the selection of the shape really changed, so that the map and the drawing
|
|
386
|
+
* panel are not refreshed for the shapes that are not concerned by the click.
|
|
387
|
+
*/
|
|
388
|
+
setFeatureSelected(dFeature, selected) {
|
|
389
|
+
if (dFeature.selected !== selected) {
|
|
390
|
+
dFeature.selected = selected;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
findFeatureIdAtPixel(pixel) {
|
|
394
|
+
const clickedFeature = this.map.olMap.forEachFeatureAtPixel(pixel, (feature, layer) => (feature instanceof Feature && layer === this.drawingLayer ? feature : undefined), {
|
|
395
|
+
hitTolerance: this.map.pixelTolerance
|
|
396
|
+
});
|
|
397
|
+
return clickedFeature ? (clickedFeature.getId() ?? null) : null;
|
|
398
|
+
}
|
|
337
399
|
prepareMenuEntriesForVertex = (_e, mapCoordinate) => {
|
|
338
400
|
if (!this.hasEditableVertexAtCoordinate(mapCoordinate)) {
|
|
339
401
|
return EntryInteractionType.NOT_SHOWN;
|
|
340
402
|
}
|
|
341
|
-
if (this.
|
|
403
|
+
if (this.isSquareRectangleOrText(this.getDrawingShapeType(this.lastClosestFeature))) {
|
|
342
404
|
return EntryInteractionType.NOT_SHOWN;
|
|
343
405
|
}
|
|
344
406
|
return EntryInteractionType.ENABLED;
|
|
@@ -476,6 +538,7 @@ export default class OlDrawing {
|
|
|
476
538
|
}
|
|
477
539
|
const olFeature = e.feature;
|
|
478
540
|
const dFeature = new DrawingFeature(this.currentShape, this.drawingState, this.config.drawing);
|
|
541
|
+
this.applyDrawingText(dFeature);
|
|
479
542
|
olFeature.setId(dFeature.id);
|
|
480
543
|
dFeature.geojson = DrawingFeature.geojsonFromOlFeature(olFeature, dFeature.type);
|
|
481
544
|
dFeature.addToState();
|
|
@@ -507,6 +570,18 @@ export default class OlDrawing {
|
|
|
507
570
|
setFixedRectangleHeight(height) {
|
|
508
571
|
this.fixedRectangleHeight = Number.isNaN(height) ? 0 : height;
|
|
509
572
|
}
|
|
573
|
+
setDrawingText(text) {
|
|
574
|
+
this.drawingText = text;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* A text shape displays the text entered in the tool parameters instead of its generated default name.
|
|
578
|
+
* If no text has been entered, the default name is kept so that the shape stays visible on the map.
|
|
579
|
+
*/
|
|
580
|
+
applyDrawingText(dFeature) {
|
|
581
|
+
if (dFeature.type === DrawingShape.Text && this.drawingText.trim().length > 0) {
|
|
582
|
+
dFeature.name = this.drawingText;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
510
585
|
createLineStringFixedLength(coordinates, geom) {
|
|
511
586
|
this.fixLastLength(this.fixedLineLength, coordinates);
|
|
512
587
|
geom = geom ?? new LineString(coordinates);
|
|
@@ -582,8 +657,6 @@ export default class OlDrawing {
|
|
|
582
657
|
}
|
|
583
658
|
addDrawInteraction(tool) {
|
|
584
659
|
this.removeDrawInteraction();
|
|
585
|
-
// Block feature selection while drawing by registering 'map.select' exclusively
|
|
586
|
-
this.context.userInteractionManager.registerListener('map.select', true, this.stateLocation);
|
|
587
660
|
this.currentShape = tool;
|
|
588
661
|
let geomFunction = undefined;
|
|
589
662
|
let olTool;
|
|
@@ -617,6 +690,9 @@ export default class OlDrawing {
|
|
|
617
690
|
case DrawingShape.FreehandPolygon:
|
|
618
691
|
olTool = 'Polygon';
|
|
619
692
|
break;
|
|
693
|
+
case DrawingShape.Text:
|
|
694
|
+
olTool = 'Point';
|
|
695
|
+
break;
|
|
620
696
|
}
|
|
621
697
|
this.draw = new Draw({
|
|
622
698
|
source: this.drawingSource,
|
|
@@ -627,9 +703,17 @@ export default class OlDrawing {
|
|
|
627
703
|
// Default condition for ol drawing is noModifierKeys(e)
|
|
628
704
|
// canExecute: If another tool is exclusively drawing, this interaction will be prevented from reacting
|
|
629
705
|
condition: (e) => primaryAction(e) && this.canExecute('map.draw'),
|
|
630
|
-
style: (f) =>
|
|
706
|
+
style: (f) => {
|
|
707
|
+
const sketchFeature = new DrawingFeature(tool, this.drawingState, this.config.drawing);
|
|
708
|
+
this.applyDrawingText(sketchFeature);
|
|
709
|
+
return this.getStyle(sketchFeature, f);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
this.draw.on('drawstart', () => {
|
|
713
|
+
this.disableContextMenu();
|
|
631
714
|
});
|
|
632
715
|
this.draw.on('drawend', () => {
|
|
716
|
+
this.enableContextMenu();
|
|
633
717
|
this.draw?.removeLastPoint();
|
|
634
718
|
this.draw?.finishDrawing();
|
|
635
719
|
});
|
|
@@ -663,17 +747,26 @@ export default class OlDrawing {
|
|
|
663
747
|
lineDash: getLineStroke(dFeature.lineStroke, dFeature.strokeWidth)
|
|
664
748
|
}),
|
|
665
749
|
fill: new Fill({ color: dFeature.fillColor }),
|
|
666
|
-
image:
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
750
|
+
image: dFeature.type === DrawingShape.Text
|
|
751
|
+
? // A text shape is only made of its text, its anchor point is not drawn
|
|
752
|
+
undefined
|
|
753
|
+
: new Circle({
|
|
754
|
+
radius: dFeature.strokeWidth, // Points are using default stroke parameters
|
|
755
|
+
fill: new Fill({ color: dFeature.strokeColor })
|
|
756
|
+
}),
|
|
670
757
|
text: new Text({
|
|
671
758
|
text: dFeature.displayName ? dFeature.name : '',
|
|
672
759
|
font: nameFont,
|
|
673
760
|
textBaseline: 'middle',
|
|
674
761
|
/*offsetY: dFeature.type == DrawingShape.Point ? 2 * dFeature.nameFontSize : 1.2 * dFeature.nameFontSize,*/
|
|
675
762
|
fill: new Fill({ color: dFeature.nameColor }),
|
|
676
|
-
overflow: true
|
|
763
|
+
overflow: true,
|
|
764
|
+
stroke: dFeature.type == DrawingShape.Text
|
|
765
|
+
? new Stroke({
|
|
766
|
+
color: '#ffffff',
|
|
767
|
+
width: 8
|
|
768
|
+
})
|
|
769
|
+
: undefined
|
|
677
770
|
})
|
|
678
771
|
});
|
|
679
772
|
const labelStyle = new Style({
|
|
@@ -768,7 +861,10 @@ export default class OlDrawing {
|
|
|
768
861
|
}
|
|
769
862
|
}
|
|
770
863
|
}
|
|
771
|
-
if (dFeature.type == DrawingShape.
|
|
864
|
+
if (dFeature.type == DrawingShape.Text) {
|
|
865
|
+
// A text shape displays only its text, there is no measure to add
|
|
866
|
+
}
|
|
867
|
+
else if (dFeature.type == DrawingShape.Point || geometry.getType() === 'Point') {
|
|
772
868
|
addLabel(geometry, dFeature.getCoordText(geometry.getCoordinates()));
|
|
773
869
|
}
|
|
774
870
|
else if (dFeature.type == DrawingShape.Polyline) {
|
|
@@ -792,7 +888,7 @@ export default class OlDrawing {
|
|
|
792
888
|
appendText(getLengthAsMetricText(lineString.getLength()));
|
|
793
889
|
}
|
|
794
890
|
}
|
|
795
|
-
else if (dFeature.type == DrawingShape.Disk) {
|
|
891
|
+
else if (dFeature.type == DrawingShape.Disk && geometry instanceof CircleGeom) {
|
|
796
892
|
if (dFeature.measureInformation !== 'none') {
|
|
797
893
|
const radiusDataForCircle = getRadiusDataForCircle(geometry, this.state.projection, defaultStyle, new Stroke({ color: measureColor, width: 2 }));
|
|
798
894
|
styles.push(radiusDataForCircle.style);
|
|
@@ -830,7 +926,7 @@ export default class OlDrawing {
|
|
|
830
926
|
}
|
|
831
927
|
addLabel(square.getInteriorPoint(), dFeature.getAreaText(getAreaOfPolygon(square, this.state.projection)));
|
|
832
928
|
}
|
|
833
|
-
if (
|
|
929
|
+
if (this.shouldRenderVertices(dFeature)) {
|
|
834
930
|
const vertexStyle = dFeature.getVertexStyle();
|
|
835
931
|
// Add a node style to every vertex of the geometry
|
|
836
932
|
vertexStyle.setGeometry(function (f) {
|
|
@@ -843,13 +939,21 @@ export default class OlDrawing {
|
|
|
843
939
|
}
|
|
844
940
|
return styles;
|
|
845
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Vertices are rendered on the shapes selected for editing. A text shape is an exception: as its geometry is
|
|
944
|
+
* a single anchor point hidden behind the text, its vertex is only revealed while the cursor hovers the text.
|
|
945
|
+
*/
|
|
946
|
+
shouldRenderVertices(dFeature) {
|
|
947
|
+
if (!dFeature.selected || this.transform != null) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
return dFeature.type !== DrawingShape.Text || dFeature.id === this.hoveredTextFeatureId;
|
|
951
|
+
}
|
|
846
952
|
removeDrawInteraction() {
|
|
847
953
|
if (this.draw) {
|
|
848
954
|
this.map.olMap.removeInteraction(this.draw);
|
|
849
955
|
this.draw = null;
|
|
850
956
|
}
|
|
851
|
-
// Reactivate feature selection by unregistering 'map.select'
|
|
852
|
-
this.context.userInteractionManager.unregisterListener('map.select', this.stateLocation);
|
|
853
957
|
}
|
|
854
958
|
removeModifyInteraction() {
|
|
855
959
|
this.clearModifyFeatureChangeListeners();
|
|
@@ -867,10 +971,50 @@ export default class OlDrawing {
|
|
|
867
971
|
isSquareOrRectangle(shapeType) {
|
|
868
972
|
return shapeType === DrawingShape.Square || shapeType === DrawingShape.Rectangle;
|
|
869
973
|
}
|
|
974
|
+
isSquareRectangleOrText(shapeType) {
|
|
975
|
+
return shapeType === DrawingShape.Square || shapeType === DrawingShape.Rectangle || shapeType === DrawingShape.Text;
|
|
976
|
+
}
|
|
870
977
|
shouldAllowVertexInsertionAtEvent(e) {
|
|
871
978
|
const hoveredFeature = this.getHoveredModifiableFeatureAtPixel(e.pixel);
|
|
872
979
|
return shouldAllowVertexInsertionForShape(this.getDrawingShapeType(hoveredFeature));
|
|
873
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* A text shape is only made of its text: its anchor point is hidden behind it. To let the user know where to
|
|
983
|
+
* grab the shape, the anchor vertex is revealed as long as the cursor hovers the text.
|
|
984
|
+
*/
|
|
985
|
+
updateHoveredTextFeature(e) {
|
|
986
|
+
if (e.dragging) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
// Only the selected shapes display their vertices, so there is nothing to look for otherwise
|
|
990
|
+
const hasSelectedText = this.drawingState.features.some((f) => f.selected && f.type === DrawingShape.Text);
|
|
991
|
+
this.setHoveredTextFeature(hasSelectedText ? this.findHoveredTextFeatureIdAtPixel(e.pixel) : null);
|
|
992
|
+
}
|
|
993
|
+
setHoveredTextFeature(featureId) {
|
|
994
|
+
if (featureId === this.hoveredTextFeatureId) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const previousFeatureId = this.hoveredTextFeatureId;
|
|
998
|
+
this.hoveredTextFeatureId = featureId;
|
|
999
|
+
// Redraw the shapes whose hovered state changed, so that their vertex appears or disappears
|
|
1000
|
+
for (const id of [previousFeatureId, featureId]) {
|
|
1001
|
+
if (id) {
|
|
1002
|
+
this.getOlFeatureFromDrawingSource(id)?.changed();
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
findHoveredTextFeatureIdAtPixel(pixel) {
|
|
1007
|
+
// The text of a shape takes part in the hit detection, so hovering anywhere on it is enough
|
|
1008
|
+
const hoveredFeature = this.map.olMap.forEachFeatureAtPixel(pixel, (feature, layer) => {
|
|
1009
|
+
if (!(feature instanceof Feature) || layer !== this.drawingLayer) {
|
|
1010
|
+
return undefined;
|
|
1011
|
+
}
|
|
1012
|
+
return this.getDrawingShapeType(feature) === DrawingShape.Text ? feature : undefined;
|
|
1013
|
+
}, {
|
|
1014
|
+
hitTolerance: this.map.pixelTolerance
|
|
1015
|
+
});
|
|
1016
|
+
return hoveredFeature instanceof Feature ? (hoveredFeature.getId() ?? null) : null;
|
|
1017
|
+
}
|
|
874
1018
|
getHoveredModifiableFeatureAtPixel(pixel) {
|
|
875
1019
|
const modifiableFeatureSet = new Set(this.modifiableFeatures.getArray());
|
|
876
1020
|
const hoveredFeature = this.map.olMap.forEachFeatureAtPixel(pixel, (feature, layer) => {
|
|
@@ -941,17 +1085,28 @@ export default class OlDrawing {
|
|
|
941
1085
|
this.context.userInteractionManager.registerListener('map.draw', true, this.stateLocation);
|
|
942
1086
|
this.context.userInteractionManager.registerListener('map.modify', true, this.stateLocation);
|
|
943
1087
|
this.context.userInteractionManager.registerListener('map.snap', true, this.stateLocation);
|
|
1088
|
+
// Clicking on the map selects the drawn shapes, so feature selection is taken over from the other tools
|
|
1089
|
+
this.context.userInteractionManager.registerListener('map.select', true, this.stateLocation);
|
|
1090
|
+
this.addSelectListener();
|
|
944
1091
|
}
|
|
945
1092
|
unregisterInteractions() {
|
|
946
1093
|
this.removeDrawInteraction();
|
|
947
1094
|
this.removeEditInteractions();
|
|
1095
|
+
this.removeSelectListener();
|
|
948
1096
|
this.context.userInteractionManager.unregisterListener('map.draw', this.stateLocation);
|
|
949
1097
|
this.context.userInteractionManager.unregisterListener('map.modify', this.stateLocation);
|
|
950
1098
|
this.context.userInteractionManager.unregisterListener('map.snap', this.stateLocation);
|
|
1099
|
+
this.context.userInteractionManager.unregisterListener('map.select', this.stateLocation);
|
|
951
1100
|
}
|
|
952
1101
|
canExecute(event) {
|
|
953
1102
|
return this.context.userInteractionManager.canListenerExecute(event, this.stateLocation);
|
|
954
1103
|
}
|
|
1104
|
+
disableContextMenu() {
|
|
1105
|
+
this.context.userInteractionManager.registerListener('map.contextmenu', true, this.stateLocation);
|
|
1106
|
+
}
|
|
1107
|
+
enableContextMenu() {
|
|
1108
|
+
this.context.userInteractionManager.unregisterListener('map.contextmenu', this.stateLocation);
|
|
1109
|
+
}
|
|
955
1110
|
fixLastLength(length, coordinates, scale = 1) {
|
|
956
1111
|
const coord = coordinates;
|
|
957
1112
|
if (coord.length > 1 && length > 0) {
|