@geogirafe/lib-geoportal 1.0.2149179221 → 1.0.2152254142
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/assets/i18n/de.json +4 -0
- package/assets/i18n/en.json +4 -0
- package/assets/i18n/fr.json +4 -0
- package/assets/i18n/it.json +4 -0
- package/components/drawing/component.js +10 -2
- package/components/drawing/olDrawing.d.ts +17 -2
- package/components/drawing/olDrawing.js +278 -27
- package/components/map/component.js +1 -1
- package/components/map/tools/contextmenu.d.ts +10 -1
- package/components/map/tools/contextmenu.js +37 -5
- package/components/modals/component.js +8 -7
- package/main.tools.d.ts +1 -1
- package/package.json +1 -1
- package/templates/public/about.json +1 -1
package/assets/i18n/de.json
CHANGED
|
@@ -217,6 +217,7 @@
|
|
|
217
217
|
"Message sent": "Nachricht gesendet",
|
|
218
218
|
"meters": "Meter",
|
|
219
219
|
"Move": "Verschieben",
|
|
220
|
+
"Move Shape": "Zeichnung verschieben",
|
|
220
221
|
"My Drawing": "Meine Zeichnung",
|
|
221
222
|
"My selection": "Meine Auswahl",
|
|
222
223
|
"Name": "Name",
|
|
@@ -309,7 +310,9 @@
|
|
|
309
310
|
"Remove filter": "Filter entfernen",
|
|
310
311
|
"Remove group": "Gruppe entfernen",
|
|
311
312
|
"Remove layer": "Layer entfernen",
|
|
313
|
+
"Remove Shape": "Zeichnung entfernen",
|
|
312
314
|
"Remove theme": "Thema entfernen",
|
|
315
|
+
"Remove Vertex": "Eckpunkt entfernen",
|
|
313
316
|
"Report an error": "Einen Fehler melden",
|
|
314
317
|
"Request secure access": "Sicheren Zugang anfordern",
|
|
315
318
|
"Reset All": "Alle zurücksetzen",
|
|
@@ -317,6 +320,7 @@
|
|
|
317
320
|
"Reset filter": "Filter zurücksetzen",
|
|
318
321
|
"Reset preferences": "Einstellungen zurücksetzen",
|
|
319
322
|
"Reset user preferences back to default values?": "Einstellungen zurück auf Standardwerte setzen?",
|
|
323
|
+
"Rotate/Scale Shape": "Zeichnung rotieren/skalieren",
|
|
320
324
|
"Rotation": "Rotation",
|
|
321
325
|
"same as system": "Systemeinstellung",
|
|
322
326
|
"Save": "Speichern",
|
package/assets/i18n/en.json
CHANGED
|
@@ -218,6 +218,7 @@
|
|
|
218
218
|
"Message sent": "Message sent",
|
|
219
219
|
"meters": "meters",
|
|
220
220
|
"Move": "Move",
|
|
221
|
+
"Move Shape": "Move Shape",
|
|
221
222
|
"My Drawing": "My Drawing",
|
|
222
223
|
"My selection": "My selection",
|
|
223
224
|
"Name": "Name",
|
|
@@ -310,7 +311,9 @@
|
|
|
310
311
|
"Remove filter": "Remove filter",
|
|
311
312
|
"Remove group": "Remove group",
|
|
312
313
|
"Remove layer": "Remove Layer",
|
|
314
|
+
"Remove Shape": "Remove Shape",
|
|
313
315
|
"Remove theme": "Remove theme",
|
|
316
|
+
"Remove Vertex": "Remove vertex",
|
|
314
317
|
"Report an error": "Report an error",
|
|
315
318
|
"Request secure access": "Request secure access",
|
|
316
319
|
"Reset All": "Reset All",
|
|
@@ -318,6 +321,7 @@
|
|
|
318
321
|
"Reset filter": "Reset filter",
|
|
319
322
|
"Reset preferences": "Reset preferences",
|
|
320
323
|
"Reset user preferences back to default values?": "Reset user preferences back to default values?",
|
|
324
|
+
"Rotate/Scale Shape": "Rotate/Scale Shape",
|
|
321
325
|
"Rotation": "Rotation",
|
|
322
326
|
"same as system": "Same as system",
|
|
323
327
|
"Save": "Save",
|
package/assets/i18n/fr.json
CHANGED
|
@@ -217,6 +217,7 @@
|
|
|
217
217
|
"Message sent": "Message envoyé",
|
|
218
218
|
"meters": "mètres",
|
|
219
219
|
"Move": "Déplacer",
|
|
220
|
+
"Move Shape": "Déplacer la forme",
|
|
220
221
|
"My Drawing": "Mon dessin",
|
|
221
222
|
"My selection": "Ma sélection",
|
|
222
223
|
"Name": "Nom",
|
|
@@ -309,7 +310,9 @@
|
|
|
309
310
|
"Remove filter": "Supprimer le filtre",
|
|
310
311
|
"Remove group": "Supprimer le groupe",
|
|
311
312
|
"Remove layer": "Supprimer la couche",
|
|
313
|
+
"Remove Shape": "Supprimer la forme",
|
|
312
314
|
"Remove theme": "Supprimer le thème",
|
|
315
|
+
"Remove Vertex": "Supprimer le sommet",
|
|
313
316
|
"Report an error": "Signaler un problème",
|
|
314
317
|
"Request secure access": "Demander un accès sécurisé",
|
|
315
318
|
"Reset All": "Réinitialiser tout",
|
|
@@ -317,6 +320,7 @@
|
|
|
317
320
|
"Reset filter": "Réinitialiser le filtre",
|
|
318
321
|
"Reset preferences": "Réinitialiser les préférences",
|
|
319
322
|
"Reset user preferences back to default values?": "Réinitialiser tous les paramètres à la valeur par défaut?",
|
|
323
|
+
"Rotate/Scale Shape": "Faire pivoter/redimensionner la forme",
|
|
320
324
|
"Rotation": "Rotation",
|
|
321
325
|
"same as system": "Identique au système",
|
|
322
326
|
"Save": "Enregistrer",
|
package/assets/i18n/it.json
CHANGED
|
@@ -217,6 +217,7 @@
|
|
|
217
217
|
"Message sent": "Messaggio inviato",
|
|
218
218
|
"meters": "metri",
|
|
219
219
|
"Move": "Muovi",
|
|
220
|
+
"Move Shape": "Sposta forma",
|
|
220
221
|
"My Drawing": "Il mio disegno",
|
|
221
222
|
"My selection": "La mia selezione",
|
|
222
223
|
"Name": "Nome",
|
|
@@ -309,7 +310,9 @@
|
|
|
309
310
|
"Remove filter": "Rimuovi filtro",
|
|
310
311
|
"Remove group": "Rimuovi gruppo",
|
|
311
312
|
"Remove layer": "Rimuovi layer",
|
|
313
|
+
"Remove Shape": "Rimuovi forma",
|
|
312
314
|
"Remove theme": "Rimuovi tema",
|
|
315
|
+
"Remove Vertex": "Rimuovi vertice",
|
|
313
316
|
"Report an error": "Segnala un errore",
|
|
314
317
|
"Request secure access": "Richiedi accesso sicuro",
|
|
315
318
|
"Reset All": "Azzeramento di tutti",
|
|
@@ -317,6 +320,7 @@
|
|
|
317
320
|
"Reset filter": "Azzeramento del filtro",
|
|
318
321
|
"Reset preferences": "Reimposta preferenze",
|
|
319
322
|
"Reset user preferences back to default values?": "Ripristinare tutte le impostazioni al valore predefinito?",
|
|
323
|
+
"Rotate/Scale Shape": "Ruota/Ridimensiona forma",
|
|
320
324
|
"Rotation": "Rotazione",
|
|
321
325
|
"same as system": "Come il sistema",
|
|
322
326
|
"Save": "Salva",
|
|
@@ -186,7 +186,12 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
|
|
|
186
186
|
}
|
|
187
187
|
this.drawingState = this.state.extendedState.drawing;
|
|
188
188
|
const map = this.context.componentManager.getComponents(MapComponent)[0];
|
|
189
|
-
this.olDrawing = new OlDrawing(map, this.name, this.context)
|
|
189
|
+
this.olDrawing = new OlDrawing(map, this.name, this.context, (featureId) => {
|
|
190
|
+
const drawingFeature = this.drawingState.features.find((drawingFeature) => drawingFeature.id === featureId);
|
|
191
|
+
if (drawingFeature) {
|
|
192
|
+
this.deleteFeature(drawingFeature);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
190
195
|
this.cesiumDrawing = new CesiumDrawing(map, this.name, this.context);
|
|
191
196
|
this.render();
|
|
192
197
|
this.subscribe('extendedState.drawing.features', (olds, news) => this.onFeaturesChanged(olds, news));
|
|
@@ -294,6 +299,7 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
|
|
|
294
299
|
this.refreshRender();
|
|
295
300
|
}
|
|
296
301
|
onToggleFeatureSelection(feature) {
|
|
302
|
+
this.setTool(null);
|
|
297
303
|
feature.selected = !feature.selected;
|
|
298
304
|
this.refreshRender();
|
|
299
305
|
}
|
|
@@ -329,7 +335,9 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
|
|
|
329
335
|
return !this.selectedFeatures.every((f) => f.isPointOrPolyline());
|
|
330
336
|
}
|
|
331
337
|
async deleteFeature(feature) {
|
|
332
|
-
const confirm = await window.gConfirm(
|
|
338
|
+
const confirm = await window.gConfirm('Do you want to remove "${feature.name}" ?', 'Delete Feature', (translatedMessage) => {
|
|
339
|
+
return translatedMessage.replace('${feature.name}', feature.name);
|
|
340
|
+
});
|
|
333
341
|
if (confirm) {
|
|
334
342
|
const idx = this.drawingState.features.findIndex((f) => f.id === feature.id);
|
|
335
343
|
if (idx > -1) {
|
|
@@ -4,17 +4,19 @@ import { Collection, Feature } from 'ol';
|
|
|
4
4
|
import { Geometry, Polygon, SimpleGeometry } from 'ol/geom';
|
|
5
5
|
import { SketchCoordType } from 'ol/interaction/Draw';
|
|
6
6
|
import { Style } from 'ol/style';
|
|
7
|
-
import { Modify, Snap,
|
|
7
|
+
import { Draw, Modify, Snap, Translate } from 'ol/interaction';
|
|
8
8
|
import VectorSource, { VectorSourceEvent } from 'ol/source/Vector';
|
|
9
9
|
import VectorLayer from 'ol/layer/Vector';
|
|
10
10
|
import { Projection } from 'ol/proj';
|
|
11
11
|
import { Coordinate } from 'ol/coordinate';
|
|
12
12
|
import { ContextMenu } from '../map/tools/contextmenu';
|
|
13
13
|
import IGirafeContext from '../../tools/context/icontext';
|
|
14
|
+
import { StyleFunction } from 'ol/style/Style';
|
|
14
15
|
export default class OlDrawing {
|
|
15
16
|
private readonly map;
|
|
16
17
|
private readonly toolName;
|
|
17
18
|
private readonly context;
|
|
19
|
+
private readonly deleteHandler;
|
|
18
20
|
modifiableFeatures: Collection<Feature>;
|
|
19
21
|
draw: Draw | null;
|
|
20
22
|
modify: Modify | null;
|
|
@@ -24,12 +26,21 @@ export default class OlDrawing {
|
|
|
24
26
|
fixedLength: number;
|
|
25
27
|
drawingSource: VectorSource;
|
|
26
28
|
drawingLayer: VectorLayer;
|
|
29
|
+
lastClosestFeature: Feature | null;
|
|
30
|
+
translate: Translate | null;
|
|
31
|
+
rotateAndScale: Modify | null;
|
|
32
|
+
defaultStyle: StyleFunction | undefined;
|
|
33
|
+
private readonly updateGeometryInState;
|
|
27
34
|
private get state();
|
|
28
35
|
private get drawingState();
|
|
29
36
|
private get config();
|
|
30
|
-
constructor(map: MapComponent, toolName: string, context: IGirafeContext);
|
|
37
|
+
constructor(map: MapComponent, toolName: string, context: IGirafeContext, deleteHandler: (featureId: string) => void);
|
|
31
38
|
private addModifyInteraction;
|
|
32
39
|
private addSnapInteraction;
|
|
40
|
+
private addTranslateInteraction;
|
|
41
|
+
private removeTranslateInteraction;
|
|
42
|
+
private addRotateAndScaleInteraction;
|
|
43
|
+
private removeRotateAndScaleInteraction;
|
|
33
44
|
/**
|
|
34
45
|
* Adds a context menu to the map with a single entry 'remove vertex'. The menu is configured to open
|
|
35
46
|
* when the user does an alternate click ( = context event) on or near a vertex of a modifiable feature.
|
|
@@ -37,10 +48,13 @@ export default class OlDrawing {
|
|
|
37
48
|
private addEditContextMenu;
|
|
38
49
|
addEditInteractions(): void;
|
|
39
50
|
removeEditInteractions(): void;
|
|
51
|
+
private readonly prepareMenuEntriesForVertex;
|
|
52
|
+
private readonly prepareMenuEntriesForShape;
|
|
40
53
|
/**
|
|
41
54
|
Check if there is a vertex of a selected (=editable) feature within the pixel tolerance of the clicked coordinates.
|
|
42
55
|
*/
|
|
43
56
|
hasEditableVertexAtCoordinate(coordinate: Coordinate): boolean;
|
|
57
|
+
hasEditableShapeAtCoordinate(coordinate: Coordinate): boolean;
|
|
44
58
|
/**
|
|
45
59
|
Returns the closest vertex and feature to a coordinate from the drawing source.
|
|
46
60
|
The feature source can be pre-filtered via an optional filter function.
|
|
@@ -51,6 +65,7 @@ export default class OlDrawing {
|
|
|
51
65
|
modify option properties 'condition' and 'insertVertexCondition'.
|
|
52
66
|
*/
|
|
53
67
|
private removeLastInteractedVertex;
|
|
68
|
+
private removeLastInteractedShape;
|
|
54
69
|
/**
|
|
55
70
|
* Adds features to the drawing source if they are missing and updates their style each time a property changes.
|
|
56
71
|
* Adding them to the source is only necessary if the feature originates from a deserialized state and not
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import DrawingFeature, { DrawingShape } from './drawingFeature';
|
|
2
2
|
import { Collection, Feature } from 'ol';
|
|
3
|
-
import {
|
|
3
|
+
import { Circle as CircleGeom, Geometry, LinearRing, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon } from 'ol/geom';
|
|
4
4
|
import { createBox, createRegularPolygon } from 'ol/interaction/Draw';
|
|
5
|
-
import {
|
|
6
|
-
import { Modify, Snap,
|
|
5
|
+
import { Circle, Fill, RegularShape, Stroke, Style, Text } from 'ol/style';
|
|
6
|
+
import { Draw, Modify, Snap, Translate } from 'ol/interaction';
|
|
7
7
|
import VectorSource from 'ol/source/Vector';
|
|
8
8
|
import VectorLayer from 'ol/layer/Vector';
|
|
9
9
|
import GeoJSON from 'ol/format/GeoJSON';
|
|
10
10
|
import { getPointResolution } from 'ol/proj';
|
|
11
|
-
import { never,
|
|
12
|
-
import {
|
|
13
|
-
import { ContextMenu } from '../map/tools/contextmenu';
|
|
11
|
+
import { never, primaryAction } from 'ol/events/condition';
|
|
12
|
+
import { getArea, getDistance } from '../../tools/utils/olutils';
|
|
13
|
+
import { ContextMenu, EntryInteractionType } from '../map/tools/contextmenu';
|
|
14
14
|
import { formatCoordinates } from '../../tools/geometrytools';
|
|
15
15
|
import { v4 as uuidv4 } from 'uuid';
|
|
16
16
|
import { isAlternateMouseClick, isPrimaryPointerAction } from '../../tools/state/userinteractionevent';
|
|
17
|
+
import { getCenter, getHeight, getWidth } from 'ol/extent';
|
|
18
|
+
import CircleStyle from 'ol/style/Circle';
|
|
17
19
|
function getLineStroke(strokeType, lineWidth) {
|
|
18
20
|
switch (strokeType) {
|
|
19
21
|
case 'full':
|
|
@@ -49,10 +51,51 @@ function extractVerticesFromGeometry(geometry) {
|
|
|
49
51
|
}
|
|
50
52
|
return new MultiPoint(vertices);
|
|
51
53
|
}
|
|
54
|
+
function calculateCenter(geometry) {
|
|
55
|
+
let center, coordinates, minRadius;
|
|
56
|
+
if (geometry instanceof Polygon) {
|
|
57
|
+
let x = 0;
|
|
58
|
+
let y = 0;
|
|
59
|
+
let i = 0;
|
|
60
|
+
coordinates = geometry.getCoordinates()[0].slice();
|
|
61
|
+
for (const coordinate of coordinates) {
|
|
62
|
+
x += coordinate[0];
|
|
63
|
+
y += coordinate[1];
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
center = [x / i, y / i];
|
|
67
|
+
}
|
|
68
|
+
else if (geometry instanceof LineString) {
|
|
69
|
+
center = geometry.getCoordinateAt(0.5);
|
|
70
|
+
coordinates = geometry.getCoordinates();
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
center = getCenter(geometry.getExtent());
|
|
74
|
+
}
|
|
75
|
+
let sqDistances;
|
|
76
|
+
if (coordinates) {
|
|
77
|
+
sqDistances = coordinates.map(function (coordinate) {
|
|
78
|
+
const dx = coordinate[0] - center[0];
|
|
79
|
+
const dy = coordinate[1] - center[1];
|
|
80
|
+
return dx * dx + dy * dy;
|
|
81
|
+
});
|
|
82
|
+
minRadius = Math.sqrt(Math.max(...sqDistances)) / 3;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
minRadius = Math.max(getWidth(geometry.getExtent()), getHeight(geometry.getExtent())) / 3;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
center: center,
|
|
89
|
+
coordinates: coordinates,
|
|
90
|
+
minRadius: minRadius,
|
|
91
|
+
sqDistances: sqDistances
|
|
92
|
+
};
|
|
93
|
+
}
|
|
52
94
|
export default class OlDrawing {
|
|
53
95
|
map;
|
|
54
96
|
toolName;
|
|
55
97
|
context;
|
|
98
|
+
deleteHandler;
|
|
56
99
|
modifiableFeatures = new Collection([]);
|
|
57
100
|
draw = null;
|
|
58
101
|
modify = null;
|
|
@@ -62,6 +105,17 @@ export default class OlDrawing {
|
|
|
62
105
|
fixedLength = 0;
|
|
63
106
|
drawingSource;
|
|
64
107
|
drawingLayer;
|
|
108
|
+
lastClosestFeature = null;
|
|
109
|
+
translate = null;
|
|
110
|
+
rotateAndScale = null;
|
|
111
|
+
defaultStyle;
|
|
112
|
+
updateGeometryInState = (olFeature) => {
|
|
113
|
+
const idx = this.drawingState.features.findIndex((f) => f.id === olFeature.getId());
|
|
114
|
+
const drawingFeature = this.drawingState.features[idx];
|
|
115
|
+
if (idx > -1) {
|
|
116
|
+
this.drawingState.features[idx].geojson = DrawingFeature.geojsonFromOlFeature(olFeature, drawingFeature.type);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
65
119
|
get state() {
|
|
66
120
|
return this.context.stateManager.state;
|
|
67
121
|
}
|
|
@@ -71,10 +125,11 @@ export default class OlDrawing {
|
|
|
71
125
|
get config() {
|
|
72
126
|
return this.context.configManager.Config;
|
|
73
127
|
}
|
|
74
|
-
constructor(map, toolName, context) {
|
|
128
|
+
constructor(map, toolName, context, deleteHandler) {
|
|
75
129
|
this.map = map;
|
|
76
130
|
this.toolName = toolName;
|
|
77
131
|
this.context = context;
|
|
132
|
+
this.deleteHandler = deleteHandler;
|
|
78
133
|
this.drawingSource = new VectorSource({ features: new Collection() });
|
|
79
134
|
this.drawingSource.on('addfeature', (e) => this.onFeatureAdded(e));
|
|
80
135
|
this.drawingLayer = new VectorLayer({
|
|
@@ -85,6 +140,7 @@ export default class OlDrawing {
|
|
|
85
140
|
altitudeMode: 'clampToGround'
|
|
86
141
|
}
|
|
87
142
|
});
|
|
143
|
+
this.defaultStyle = new Modify({ source: this.drawingSource }).getOverlay().getStyleFunction();
|
|
88
144
|
this.map.subscribe('extendedState.drawing.activeTool', (_oldTool, newTool) => newTool === null ? this.removeDrawInteraction() : this.addDrawInteraction(newTool));
|
|
89
145
|
// Could be useful logic if we decide that the mobile UI should rely use a dedicated button
|
|
90
146
|
// to remove a selected vetex from a polygon/line (rather than using a longpress context menu)
|
|
@@ -127,13 +183,7 @@ export default class OlDrawing {
|
|
|
127
183
|
this.map.olMap.addInteraction(this.modify);
|
|
128
184
|
// Update the modified geometries in the state
|
|
129
185
|
this.modify.on('modifyend', (e) => {
|
|
130
|
-
e.features.forEach((olFeature) =>
|
|
131
|
-
const idx = this.drawingState.features.findIndex((f) => f.id === olFeature.getId());
|
|
132
|
-
const drawingFeature = this.drawingState.features[idx];
|
|
133
|
-
if (idx > -1) {
|
|
134
|
-
this.drawingState.features[idx].geojson = DrawingFeature.geojsonFromOlFeature(olFeature, drawingFeature.type);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
186
|
+
e.features.forEach((olFeature) => this.updateGeometryInState(olFeature)); //NOSONAR(typescript:S7728) Collection<?> is a custom Implementation with custom forEach
|
|
137
187
|
});
|
|
138
188
|
}
|
|
139
189
|
addSnapInteraction() {
|
|
@@ -142,6 +192,110 @@ export default class OlDrawing {
|
|
|
142
192
|
this.snap = new Snap({ source: this.drawingSource, pixelTolerance: this.map.pixelTolerance });
|
|
143
193
|
this.map.olMap.addInteraction(this.snap);
|
|
144
194
|
}
|
|
195
|
+
addTranslateInteraction() {
|
|
196
|
+
this.removeTranslateInteraction();
|
|
197
|
+
this.translate = new Translate({
|
|
198
|
+
condition: (event) => {
|
|
199
|
+
return this.translate != null && primaryAction(event);
|
|
200
|
+
},
|
|
201
|
+
layers: [this.drawingLayer]
|
|
202
|
+
});
|
|
203
|
+
this.map.olMap.addInteraction(this.translate);
|
|
204
|
+
}
|
|
205
|
+
removeTranslateInteraction() {
|
|
206
|
+
if (this.translate) {
|
|
207
|
+
this.map.olMap.removeInteraction(this.translate);
|
|
208
|
+
this.translate = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
addRotateAndScaleInteraction() {
|
|
212
|
+
this.removeRotateAndScaleInteraction();
|
|
213
|
+
const defaultStyle = this.defaultStyle;
|
|
214
|
+
const getCoordinates = (feature) => {
|
|
215
|
+
const geometry = feature.getGeometry();
|
|
216
|
+
if (geometry instanceof Point) {
|
|
217
|
+
return geometry.getCoordinates();
|
|
218
|
+
}
|
|
219
|
+
else if (geometry instanceof LineString) {
|
|
220
|
+
return geometry.getCoordinates()[0];
|
|
221
|
+
}
|
|
222
|
+
else if (geometry instanceof Polygon) {
|
|
223
|
+
return geometry.getCoordinates()[0][0];
|
|
224
|
+
}
|
|
225
|
+
return [];
|
|
226
|
+
};
|
|
227
|
+
this.rotateAndScale = new Modify({
|
|
228
|
+
features: this.modifiableFeatures,
|
|
229
|
+
condition: (event) => {
|
|
230
|
+
return this.rotateAndScale != null && primaryAction(event);
|
|
231
|
+
},
|
|
232
|
+
deleteCondition: never,
|
|
233
|
+
insertVertexCondition: never,
|
|
234
|
+
style: function (feature, resolution) {
|
|
235
|
+
feature.get('features').forEach(function (modifyFeature) {
|
|
236
|
+
const modifyGeometry = modifyFeature.get('modifyGeometry');
|
|
237
|
+
if (modifyGeometry) {
|
|
238
|
+
const point = getCoordinates(feature);
|
|
239
|
+
let modifyPoint = modifyGeometry.point;
|
|
240
|
+
if (!modifyPoint) {
|
|
241
|
+
// save the initial geometry and vertex position
|
|
242
|
+
modifyPoint = point;
|
|
243
|
+
modifyGeometry.point = modifyPoint;
|
|
244
|
+
modifyGeometry.geometry0 = modifyGeometry.geometry;
|
|
245
|
+
// get anchor and minimum radius of vertices to be used
|
|
246
|
+
const result = calculateCenter(modifyGeometry.geometry0);
|
|
247
|
+
modifyGeometry.center = result.center;
|
|
248
|
+
modifyGeometry.minRadius = result.minRadius;
|
|
249
|
+
}
|
|
250
|
+
const center = modifyGeometry.center;
|
|
251
|
+
const minRadius = modifyGeometry.minRadius;
|
|
252
|
+
let dx, dy;
|
|
253
|
+
dx = modifyPoint[0] - center[0];
|
|
254
|
+
dy = modifyPoint[1] - center[1];
|
|
255
|
+
const initialRadius = Math.hypot(dx, dy);
|
|
256
|
+
if (initialRadius > minRadius) {
|
|
257
|
+
const initialAngle = Math.atan2(dy, dx);
|
|
258
|
+
dx = point[0] - center[0];
|
|
259
|
+
dy = point[1] - center[1];
|
|
260
|
+
const currentRadius = Math.hypot(dx, dy);
|
|
261
|
+
if (currentRadius > 0) {
|
|
262
|
+
const currentAngle = Math.atan2(dy, dx);
|
|
263
|
+
const geometry = modifyGeometry.geometry0.clone();
|
|
264
|
+
geometry.scale(currentRadius / initialRadius, undefined, center);
|
|
265
|
+
geometry.rotate(currentAngle - initialAngle, center);
|
|
266
|
+
modifyGeometry.geometry = geometry;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return defaultStyle(feature, resolution);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
this.rotateAndScale.on('modifystart', function (event) {
|
|
275
|
+
// prettier-ignore
|
|
276
|
+
event.features.forEach(function (feature) {
|
|
277
|
+
feature.set('modifyGeometry', { geometry: feature.getGeometry().clone() }, true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
this.rotateAndScale.on('modifyend', (event) => {
|
|
281
|
+
// prettier-ignore
|
|
282
|
+
event.features.forEach((olFeature) => {
|
|
283
|
+
const modifyGeometry = olFeature.get('modifyGeometry');
|
|
284
|
+
if (modifyGeometry) {
|
|
285
|
+
olFeature.setGeometry(modifyGeometry.geometry);
|
|
286
|
+
olFeature.unset('modifyGeometry', true);
|
|
287
|
+
this.updateGeometryInState(olFeature);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
this.map.olMap.addInteraction(this.rotateAndScale);
|
|
292
|
+
}
|
|
293
|
+
removeRotateAndScaleInteraction() {
|
|
294
|
+
if (this.rotateAndScale) {
|
|
295
|
+
this.map.olMap.removeInteraction(this.rotateAndScale);
|
|
296
|
+
this.rotateAndScale = null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
145
299
|
/**
|
|
146
300
|
* Adds a context menu to the map with a single entry 'remove vertex'. The menu is configured to open
|
|
147
301
|
* when the user does an alternate click ( = context event) on or near a vertex of a modifiable feature.
|
|
@@ -150,7 +304,7 @@ export default class OlDrawing {
|
|
|
150
304
|
this.removeEditContextMenu();
|
|
151
305
|
const menuEntries = [
|
|
152
306
|
{
|
|
153
|
-
entry: 'Remove
|
|
307
|
+
entry: 'Remove Vertex',
|
|
154
308
|
callback: (_evt, mapCoordinate) => {
|
|
155
309
|
const successful = this.removeLastInteractedVertex();
|
|
156
310
|
if (!successful) {
|
|
@@ -161,7 +315,43 @@ export default class OlDrawing {
|
|
|
161
315
|
type: 'warning'
|
|
162
316
|
});
|
|
163
317
|
}
|
|
164
|
-
}
|
|
318
|
+
},
|
|
319
|
+
prepare: this.prepareMenuEntriesForVertex
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
entry: 'Move Shape',
|
|
323
|
+
callback: (_evt, _mapCoordinate) => {
|
|
324
|
+
if (this.lastClosestFeature) {
|
|
325
|
+
this.removeModifyInteraction();
|
|
326
|
+
this.addTranslateInteraction();
|
|
327
|
+
this.map.olMap.on('singleclick', () => {
|
|
328
|
+
this.removeTranslateInteraction();
|
|
329
|
+
this.addModifyInteraction();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
prepare: this.prepareMenuEntriesForShape
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
entry: 'Rotate/Scale Shape',
|
|
337
|
+
callback: (_evt, _mapCoordinate) => {
|
|
338
|
+
if (this.lastClosestFeature) {
|
|
339
|
+
this.removeModifyInteraction();
|
|
340
|
+
this.addRotateAndScaleInteraction();
|
|
341
|
+
this.map.olMap.on('singleclick', () => {
|
|
342
|
+
this.removeRotateAndScaleInteraction();
|
|
343
|
+
this.addModifyInteraction();
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
prepare: this.prepareMenuEntriesForShape
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
entry: 'Remove Shape',
|
|
351
|
+
callback: (_evt, _mapCoordinate) => {
|
|
352
|
+
this.removeLastInteractedShape();
|
|
353
|
+
},
|
|
354
|
+
prepare: this.prepareMenuEntriesForShape
|
|
165
355
|
}
|
|
166
356
|
];
|
|
167
357
|
const conditionToOpen = (_evt, mapCoordinate) => {
|
|
@@ -169,7 +359,7 @@ export default class OlDrawing {
|
|
|
169
359
|
return false;
|
|
170
360
|
}
|
|
171
361
|
// Only proceed if there is an editable vertex under the mouse pointer
|
|
172
|
-
return this.hasEditableVertexAtCoordinate(mapCoordinate);
|
|
362
|
+
return this.hasEditableShapeAtCoordinate(mapCoordinate) || this.hasEditableVertexAtCoordinate(mapCoordinate);
|
|
173
363
|
};
|
|
174
364
|
this.editContextMenu = new ContextMenu(this.context, menuEntries, true, conditionToOpen);
|
|
175
365
|
}
|
|
@@ -190,6 +380,16 @@ export default class OlDrawing {
|
|
|
190
380
|
this.removeSnapInteraction();
|
|
191
381
|
}
|
|
192
382
|
}
|
|
383
|
+
prepareMenuEntriesForVertex = (_e, mapCoordinate) => {
|
|
384
|
+
return this.hasEditableVertexAtCoordinate(mapCoordinate)
|
|
385
|
+
? EntryInteractionType.ENABLED
|
|
386
|
+
: EntryInteractionType.NOT_SHOWN;
|
|
387
|
+
};
|
|
388
|
+
prepareMenuEntriesForShape = (_e, mapCoordinate) => {
|
|
389
|
+
return this.hasEditableShapeAtCoordinate(mapCoordinate)
|
|
390
|
+
? EntryInteractionType.ENABLED
|
|
391
|
+
: EntryInteractionType.NOT_SHOWN;
|
|
392
|
+
};
|
|
193
393
|
/**
|
|
194
394
|
Check if there is a vertex of a selected (=editable) feature within the pixel tolerance of the clicked coordinates.
|
|
195
395
|
*/
|
|
@@ -203,13 +403,23 @@ export default class OlDrawing {
|
|
|
203
403
|
const clickAsPixel = this.map.olMap.getPixelFromCoordinate(coordinate);
|
|
204
404
|
const dx = vertexAsPixel[0] - clickAsPixel[0];
|
|
205
405
|
const dy = vertexAsPixel[1] - clickAsPixel[1];
|
|
206
|
-
const distanceToVertex = Math.
|
|
406
|
+
const distanceToVertex = Math.hypot(dx, dy);
|
|
207
407
|
if (distanceToVertex < this.map.pixelTolerance) {
|
|
208
408
|
return true;
|
|
209
409
|
}
|
|
210
410
|
}
|
|
211
411
|
return false;
|
|
212
412
|
}
|
|
413
|
+
hasEditableShapeAtCoordinate(coordinate) {
|
|
414
|
+
const filter = (olFeature) => this.modifiableFeatures.getArray().some((editFeature) => olFeature == editFeature);
|
|
415
|
+
// Use filter to only query currently selected features
|
|
416
|
+
const closestElements = this.getClosestVertexAndFeature(coordinate, filter);
|
|
417
|
+
if (closestElements) {
|
|
418
|
+
const closestFeature = closestElements[1];
|
|
419
|
+
return closestFeature.getGeometry()?.containsXY(coordinate[0], coordinate[1]) ?? false;
|
|
420
|
+
}
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
213
423
|
/**
|
|
214
424
|
Returns the closest vertex and feature to a coordinate from the drawing source.
|
|
215
425
|
The feature source can be pre-filtered via an optional filter function.
|
|
@@ -218,10 +428,12 @@ export default class OlDrawing {
|
|
|
218
428
|
const olFeature = this.drawingSource.getClosestFeatureToCoordinate(coordinate, filter);
|
|
219
429
|
const geometry = olFeature?.getGeometry();
|
|
220
430
|
if (geometry && olFeature) {
|
|
431
|
+
this.lastClosestFeature = olFeature;
|
|
221
432
|
const vertices = extractVerticesFromGeometry(geometry);
|
|
222
433
|
const closestVertex = vertices.getClosestPoint(coordinate);
|
|
223
434
|
return [closestVertex, olFeature];
|
|
224
435
|
}
|
|
436
|
+
this.lastClosestFeature = null;
|
|
225
437
|
return undefined;
|
|
226
438
|
}
|
|
227
439
|
/**
|
|
@@ -231,6 +443,14 @@ export default class OlDrawing {
|
|
|
231
443
|
removeLastInteractedVertex() {
|
|
232
444
|
return this.modify?.removePoint();
|
|
233
445
|
}
|
|
446
|
+
removeLastInteractedShape() {
|
|
447
|
+
if (this.lastClosestFeature) {
|
|
448
|
+
this.deleteHandler(this.lastClosestFeature.getId());
|
|
449
|
+
this.lastClosestFeature = null;
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
234
454
|
/**
|
|
235
455
|
* Adds features to the drawing source if they are missing and updates their style each time a property changes.
|
|
236
456
|
* Adding them to the source is only necessary if the feature originates from a deserialized state and not
|
|
@@ -239,7 +459,7 @@ export default class OlDrawing {
|
|
|
239
459
|
* @param {DrawingFeature[]} dFeatures - An array of `DrawingFeature` objects to be added.
|
|
240
460
|
*/
|
|
241
461
|
addFeatures(dFeatures) {
|
|
242
|
-
|
|
462
|
+
for (const df of dFeatures) {
|
|
243
463
|
let olFeature = this.getOlFeatureFromDrawingSource(df.id);
|
|
244
464
|
if (olFeature === null) {
|
|
245
465
|
olFeature = this.createOlFeature(df);
|
|
@@ -247,7 +467,7 @@ export default class OlDrawing {
|
|
|
247
467
|
}
|
|
248
468
|
df.onChange = (df) => olFeature.setStyle((f) => this.getStyle(df, f));
|
|
249
469
|
df.onChange(df);
|
|
250
|
-
}
|
|
470
|
+
}
|
|
251
471
|
}
|
|
252
472
|
/**
|
|
253
473
|
* Deletes the provided features from the drawing source.
|
|
@@ -255,12 +475,12 @@ export default class OlDrawing {
|
|
|
255
475
|
* @param {DrawingFeature[]} dFeatures - The list of features to be deleted.
|
|
256
476
|
*/
|
|
257
477
|
deleteFeatures(dFeatures) {
|
|
258
|
-
|
|
478
|
+
for (const df of dFeatures) {
|
|
259
479
|
const toRemove = this.getOlFeatureFromDrawingSource(df.id);
|
|
260
480
|
if (toRemove !== null) {
|
|
261
481
|
this.drawingSource.removeFeature(toRemove);
|
|
262
482
|
}
|
|
263
|
-
}
|
|
483
|
+
}
|
|
264
484
|
this.updateModifiableFeatures();
|
|
265
485
|
}
|
|
266
486
|
/**
|
|
@@ -269,12 +489,12 @@ export default class OlDrawing {
|
|
|
269
489
|
updateModifiableFeatures() {
|
|
270
490
|
const selectedDrawingFeatures = this.drawingState.features.filter((f) => f.selected);
|
|
271
491
|
this.modifiableFeatures.clear();
|
|
272
|
-
|
|
492
|
+
for (const df of selectedDrawingFeatures) {
|
|
273
493
|
const olFeature = this.getOlFeatureFromDrawingSource(df.id);
|
|
274
494
|
if (olFeature) {
|
|
275
495
|
this.modifiableFeatures.push(olFeature);
|
|
276
496
|
}
|
|
277
|
-
}
|
|
497
|
+
}
|
|
278
498
|
// Only activate interaction if there are features to modify
|
|
279
499
|
if (this.modifiableFeatures.getLength() > 0) {
|
|
280
500
|
this.addEditInteractions();
|
|
@@ -397,7 +617,7 @@ export default class OlDrawing {
|
|
|
397
617
|
geometryFunction: geomFunction,
|
|
398
618
|
// Default condition for ol drawing is noModifierKeys(e)
|
|
399
619
|
// canExecute: If another tool is exclusively drawing, this interaction will be prevented from reacting
|
|
400
|
-
condition: (e) =>
|
|
620
|
+
condition: (e) => primaryAction(e) && this.canExecute('map.draw'),
|
|
401
621
|
style: (f) => this.getStyle(new DrawingFeature(tool, this.drawingState, this.config.drawing), f)
|
|
402
622
|
});
|
|
403
623
|
this.draw.on('drawend', () => {
|
|
@@ -417,11 +637,13 @@ export default class OlDrawing {
|
|
|
417
637
|
}
|
|
418
638
|
// TODO Move as much parameters as possible into DrawingFeature
|
|
419
639
|
getStyle(dFeature, olFeature) {
|
|
420
|
-
const
|
|
640
|
+
const modifyGeometry = olFeature.get('modifyGeometry');
|
|
641
|
+
const geometry = modifyGeometry ? modifyGeometry.geometry : olFeature.getGeometry();
|
|
421
642
|
const measureFont = 'Bold ' + dFeature.measureFontSize + 'px/1 ' + dFeature.font;
|
|
422
643
|
const nameFont = 'Bold ' + dFeature.nameFontSize + 'px/1 ' + dFeature.font;
|
|
423
644
|
const measureColor = 'rgba(0, 0, 0, 0.4)';
|
|
424
645
|
const defaultStyle = new Style({
|
|
646
|
+
geometry: geometry,
|
|
425
647
|
// need to explicitly mention lineCap: round, so that MapfishPrint does not use butted as default
|
|
426
648
|
stroke: new Stroke({
|
|
427
649
|
color: dFeature.strokeColor,
|
|
@@ -576,7 +798,7 @@ export default class OlDrawing {
|
|
|
576
798
|
addLabel(getHalfPoint(segment), dFeature.getLengthText(getDistance(segment, this.state.projection)));
|
|
577
799
|
addLabel(square.getInteriorPoint(), dFeature.getAreaText(getArea(square, this.state.projection)));
|
|
578
800
|
}
|
|
579
|
-
if (dFeature.selected) {
|
|
801
|
+
if (dFeature.selected && this.rotateAndScale == null) {
|
|
580
802
|
const vertexStyle = dFeature.getVertexStyle();
|
|
581
803
|
// Add a node style to every vertex of the geometry
|
|
582
804
|
vertexStyle.setGeometry(function (f) {
|
|
@@ -587,6 +809,35 @@ export default class OlDrawing {
|
|
|
587
809
|
});
|
|
588
810
|
styles.push(vertexStyle);
|
|
589
811
|
}
|
|
812
|
+
// Draw Point/Circle for Center of Geometry if rotating/scaling
|
|
813
|
+
if (this.rotateAndScale != null) {
|
|
814
|
+
const result = calculateCenter(geometry);
|
|
815
|
+
const center = result.center;
|
|
816
|
+
if (center) {
|
|
817
|
+
styles.push(new Style({
|
|
818
|
+
geometry: new Point(center),
|
|
819
|
+
image: new CircleStyle({
|
|
820
|
+
radius: 4,
|
|
821
|
+
fill: new Fill({
|
|
822
|
+
color: '#ff3333'
|
|
823
|
+
})
|
|
824
|
+
})
|
|
825
|
+
}));
|
|
826
|
+
const coordinates = result.coordinates;
|
|
827
|
+
if (coordinates) {
|
|
828
|
+
const minRadius = result.minRadius;
|
|
829
|
+
const sqDistances = result.sqDistances;
|
|
830
|
+
const rsq = minRadius * minRadius;
|
|
831
|
+
const points = coordinates.filter(function (_c, index) {
|
|
832
|
+
return sqDistances[index] > rsq;
|
|
833
|
+
});
|
|
834
|
+
const vertexStyle = dFeature.getVertexStyle();
|
|
835
|
+
// Add a node style to every vertex of the geometry
|
|
836
|
+
vertexStyle.setGeometry(new MultiPoint(points));
|
|
837
|
+
styles.push(vertexStyle);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
590
841
|
return styles;
|
|
591
842
|
}
|
|
592
843
|
ensurePolygonIsProperlyClosed(polygon) {
|
|
@@ -45,7 +45,7 @@ export default class MapComponent extends GirafeHTMLElement {
|
|
|
45
45
|
return uHtml `<style>
|
|
46
46
|
*{font-family:Arial,sans-serif}.hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height)/1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad2);background-color:var(--text-color-grad2);color:var(--bkg-color)}.gg-button:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3;border:none}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height)/1.2);min-height:calc(var(--app-standard-height)/1.2);max-height:calc(var(--app-standard-height)/1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height)/2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height)/2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height)/2);min-height:calc(var(--app-standard-height)/2);max-height:calc(var(--app-standard-height)/2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height)/3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height)/3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover,.gg-select:hover,.gg-input:hover,.gg-textarea:hover,.gg-icon-button:hover{background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;border-bottom:1px solid gray;justify-content:left;align-items:end;gap:1rem;height:2rem;margin:.5rem auto 1rem;display:flex}.gg-tab{cursor:pointer;color:var(--text-color);background:0 0;border:none;padding:.5rem}.gg-tab.active{border-bottom:solid 2px var(--text-color-grad2);font-weight:600}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height)/2);height:calc(var(--app-standard-height)/2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}
|
|
47
47
|
</style><style>
|
|
48
|
-
#container,#ol-map{background-color:var(--bkg-color);width:100%;height:100%;position:relative}#ol-map.darkmap canvas{filter:invert()hue-rotate(180deg)}.hidden{display:none}.center{text-align:center;margin:10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.loading span{color:var(--text-color);margin-top:1rem;font-weight:600;display:block}.loading span.quote{font-style:italic;font-weight:300}#cs-map{background-color:var(--bkg-color);border-left:3px solid #444;height:100%;display:none;position:absolute;overflow:hidden}.ol-control{background-color:#0000;border:none;box-shadow:0 1px 4px #0000004d;right:2rem!important;left:unset!important}.ol-zoom{top:2rem!important}.ol-rotate{top:9.3rem!important}.ol-location{top:7rem!important}.img-location{width:55%;height:auto}.ol-zoom-in,.ol-zoom-out,.btn-location,.ol-rotate-reset{cursor:pointer;background-color:#fff;width:2rem!important;height:2rem!important;font-size:1.2rem!important}.ol-scale-line{bottom:.5rem!important;right:1rem!important;left:unset!important}.ol-popup{background-color:var(--bkg-color);color:var(--text-color);box-shadow:0 1px 4px var(--bx-shdw);border:1px solid var(--text-color-grad1);border-radius:10px;min-width:140px;padding:15px;position:absolute;bottom:12px;left:-50px}.ol-popup:after,.ol-popup:before{content:" ";pointer-events:none;border:solid #0000;width:0;height:0;position:absolute;top:100%}.ol-popup:after{border-top-color:var(--bkg-color);border-width:10px;margin-left:-10px;left:48px}.ol-popup:before{border-top-color:var(--text-color-grad1);border-width:11px;margin-left:-11px;left:48px}.ol-popup-closer{background-color:var(--bkg-color);color:var(--text-color);cursor:pointer;text-decoration:none;position:absolute;top:2px;right:8px}.ol-popup-closer:after{content:"✖"}#swiper{width:99%;height:0;display:none;position:absolute;top:50%;left:.5%}input[type=range]{-webkit-appearance:none;width:100%}input[type=range]::-webkit-slider-runnable-track{height:0}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:6px;height:0;margin-top:-1000px;padding-top:1000px;padding-bottom:1000px}input[type=range]::-moz-range-thumb{background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:3px;height:0;padding-top:1000px;padding-bottom:1000px}.close-swiper{background-color:var(--ol-background-color);width:2rem;height:2rem;color:var(--ol-subtle-foreground-color);cursor:pointer;border-width:1px;border-radius:4px;display:none;position:absolute;top:0;transform:translate(-3px)}.close-swiper:hover,.close-swiper:focus{outline:1px solid var(--ol-subtle-foreground-color);color:var(--ol-foreground-color)}.ol-viewport .tooltip{color:#fff;opacity:.7;white-space:nowrap;background:#00000080;border-radius:0;padding:.32rem .62rem;position:relative}@media screen and (hover:none){.ol-zoom,.ol-rotate,.ol-location{display:none!important}}.contextmenu{background-color:var(--bkg-color);color:var(--text-color);border:1px solid #ccc;box-shadow:1px 3px 4px #0006;& .hidden{display:none}& .menu-entry{cursor:pointer;padding:10px}}
|
|
48
|
+
#container,#ol-map{background-color:var(--bkg-color);width:100%;height:100%;position:relative}#ol-map.darkmap canvas{filter:invert()hue-rotate(180deg)}.hidden{display:none}.center{text-align:center;margin:10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.loading span{color:var(--text-color);margin-top:1rem;font-weight:600;display:block}.loading span.quote{font-style:italic;font-weight:300}#cs-map{background-color:var(--bkg-color);border-left:3px solid #444;height:100%;display:none;position:absolute;overflow:hidden}.ol-control{background-color:#0000;border:none;box-shadow:0 1px 4px #0000004d;right:2rem!important;left:unset!important}.ol-zoom{top:2rem!important}.ol-rotate{top:9.3rem!important}.ol-location{top:7rem!important}.img-location{width:55%;height:auto}.ol-zoom-in,.ol-zoom-out,.btn-location,.ol-rotate-reset{cursor:pointer;background-color:#fff;width:2rem!important;height:2rem!important;font-size:1.2rem!important}.ol-scale-line{bottom:.5rem!important;right:1rem!important;left:unset!important}.ol-popup{background-color:var(--bkg-color);color:var(--text-color);box-shadow:0 1px 4px var(--bx-shdw);border:1px solid var(--text-color-grad1);border-radius:10px;min-width:140px;padding:15px;position:absolute;bottom:12px;left:-50px}.ol-popup:after,.ol-popup:before{content:" ";pointer-events:none;border:solid #0000;width:0;height:0;position:absolute;top:100%}.ol-popup:after{border-top-color:var(--bkg-color);border-width:10px;margin-left:-10px;left:48px}.ol-popup:before{border-top-color:var(--text-color-grad1);border-width:11px;margin-left:-11px;left:48px}.ol-popup-closer{background-color:var(--bkg-color);color:var(--text-color);cursor:pointer;text-decoration:none;position:absolute;top:2px;right:8px}.ol-popup-closer:after{content:"✖"}#swiper{width:99%;height:0;display:none;position:absolute;top:50%;left:.5%}input[type=range]{-webkit-appearance:none;width:100%}input[type=range]::-webkit-slider-runnable-track{height:0}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:6px;height:0;margin-top:-1000px;padding-top:1000px;padding-bottom:1000px}input[type=range]::-moz-range-thumb{background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:3px;height:0;padding-top:1000px;padding-bottom:1000px}.close-swiper{background-color:var(--ol-background-color);width:2rem;height:2rem;color:var(--ol-subtle-foreground-color);cursor:pointer;border-width:1px;border-radius:4px;display:none;position:absolute;top:0;transform:translate(-3px)}.close-swiper:hover,.close-swiper:focus{outline:1px solid var(--ol-subtle-foreground-color);color:var(--ol-foreground-color)}.ol-viewport .tooltip{color:#fff;opacity:.7;white-space:nowrap;background:#00000080;border-radius:0;padding:.32rem .62rem;position:relative}@media screen and (hover:none){.ol-zoom,.ol-rotate,.ol-location{display:none!important}}.contextmenu{background-color:var(--bkg-color);color:var(--text-color);border:1px solid #ccc;box-shadow:1px 3px 4px #0006;& .hidden{display:none}& .menu-entry{cursor:pointer;padding:10px}& .menu-entry:hover{background-color:var(--bkg-color-grad1)}& .menu-entry[aria-disabled=true]{pointer-events:none;color:var(--text-color-grad1)}}
|
|
49
49
|
</style>
|
|
50
50
|
<link rel="stylesheet" href="lib/ol/ol.css"><div id="container"><div id="ol-map"></div><div class="ol-location ol-unselectable ol-control"><button onclick="${() => this.locateUser()}" class="btn-location"><img class="img-location" alt="location-icon" src="icons/adjust.svg"></button></div><div id="cs-map"><div class="${this.loading ? 'loading center' : 'loading hidden'}"><img alt="loading-icon" src="icons/loading.svg" class="gg-spin"> <span i18n="Loading cesium...">Loading cesium...</span> <span class="quote" i18n="cesium-loading-quote">Please be patient, like a giraffe reaching for the tastiest leaves.</span></div></div><input id="swiper" type="range" min="0" max="1000" step="1"> <button tip="Hide Swiper" id="close-swiper" class="close-swiper">×</button></div>`;
|
|
51
51
|
};
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { Coordinate } from 'ol/coordinate';
|
|
2
2
|
import IGirafeContext from '../../../tools/context/icontext';
|
|
3
|
+
export declare enum EntryInteractionType {
|
|
4
|
+
ENABLED = 0,
|
|
5
|
+
DISABLED = 1,
|
|
6
|
+
NOT_SHOWN = 2
|
|
7
|
+
}
|
|
3
8
|
export type MenuEntry = {
|
|
4
9
|
entry: string;
|
|
5
10
|
callback: (e: MouseEvent, mapCoordinate: Coordinate) => void;
|
|
11
|
+
prepare?: (e: MouseEvent, mapCoordinate: Coordinate) => EntryInteractionType;
|
|
6
12
|
};
|
|
7
13
|
/**
|
|
8
14
|
* Show a simple context menu when clicking on the map
|
|
@@ -16,6 +22,8 @@ export declare class ContextMenu {
|
|
|
16
22
|
private readonly openEventListener;
|
|
17
23
|
private readonly closeEventListener;
|
|
18
24
|
private readonly context;
|
|
25
|
+
private readonly menuEntries;
|
|
26
|
+
private menuEntryDivs;
|
|
19
27
|
private get map();
|
|
20
28
|
constructor(context: IGirafeContext, menuEntries?: MenuEntry[], isExclusive?: boolean, openCondition?: (evt: PointerEvent | MouseEvent, mapCoordinate: Coordinate) => boolean);
|
|
21
29
|
get visible(): boolean;
|
|
@@ -24,7 +32,8 @@ export declare class ContextMenu {
|
|
|
24
32
|
private enable;
|
|
25
33
|
private disable;
|
|
26
34
|
private handleContextmenuEvent;
|
|
27
|
-
updateMenuEntries(
|
|
35
|
+
updateMenuEntries(): void;
|
|
36
|
+
prepareMenuEntries(evt: PointerEvent | MouseEvent, mapCoordinate: Coordinate): void;
|
|
28
37
|
openMenu(mapCoordinates: Coordinate): void;
|
|
29
38
|
closeMenu(): void;
|
|
30
39
|
onClickMenuEntry(evt: PointerEvent | MouseEvent, callback: (e: PointerEvent | MouseEvent, mapCoordinate: Coordinate) => void): void;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Overlay } from 'ol';
|
|
2
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
export var EntryInteractionType;
|
|
4
|
+
(function (EntryInteractionType) {
|
|
5
|
+
EntryInteractionType[EntryInteractionType["ENABLED"] = 0] = "ENABLED";
|
|
6
|
+
EntryInteractionType[EntryInteractionType["DISABLED"] = 1] = "DISABLED";
|
|
7
|
+
EntryInteractionType[EntryInteractionType["NOT_SHOWN"] = 2] = "NOT_SHOWN";
|
|
8
|
+
})(EntryInteractionType || (EntryInteractionType = {}));
|
|
3
9
|
/**
|
|
4
10
|
* Show a simple context menu when clicking on the map
|
|
5
11
|
*/
|
|
@@ -12,17 +18,20 @@ export class ContextMenu {
|
|
|
12
18
|
openEventListener;
|
|
13
19
|
closeEventListener;
|
|
14
20
|
context;
|
|
21
|
+
menuEntries;
|
|
22
|
+
menuEntryDivs = {};
|
|
15
23
|
get map() {
|
|
16
24
|
return this.context.mapManager.getMap();
|
|
17
25
|
}
|
|
18
26
|
constructor(context, menuEntries = [], isExclusive = false, openCondition = () => true) {
|
|
19
27
|
this.name = `contextmenu-${uuidv4()}`;
|
|
20
28
|
this.context = context;
|
|
29
|
+
this.menuEntries = menuEntries;
|
|
21
30
|
this.isExclusive = isExclusive;
|
|
22
31
|
this.container = document.createElement('div');
|
|
23
32
|
this.container.classList.add('contextmenu');
|
|
24
33
|
this.container.classList.add('hidden');
|
|
25
|
-
this.updateMenuEntries(
|
|
34
|
+
this.updateMenuEntries();
|
|
26
35
|
this.overlay = new Overlay({
|
|
27
36
|
element: this.container,
|
|
28
37
|
position: undefined
|
|
@@ -84,21 +93,44 @@ export class ContextMenu {
|
|
|
84
93
|
evt.offsetY / window.devicePixelRatio
|
|
85
94
|
]);
|
|
86
95
|
if (openCondition(evt, mapCoordinate)) {
|
|
96
|
+
this.prepareMenuEntries(evt, mapCoordinate);
|
|
87
97
|
this.openMenu(mapCoordinate);
|
|
88
98
|
}
|
|
89
99
|
else {
|
|
90
100
|
evt.stopPropagation();
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
|
-
updateMenuEntries(
|
|
103
|
+
updateMenuEntries() {
|
|
94
104
|
this.container.replaceChildren('');
|
|
95
|
-
|
|
105
|
+
this.menuEntryDivs = {};
|
|
106
|
+
for (const entry of this.menuEntries) {
|
|
96
107
|
const div = document.createElement('div');
|
|
97
108
|
div.classList.add('menu-entry');
|
|
98
|
-
div.innerHTML = entry.entry;
|
|
109
|
+
div.innerHTML = this.context.i18nManager.getTranslation(entry.entry);
|
|
99
110
|
div.addEventListener('click', (evt) => this.onClickMenuEntry(evt, entry.callback));
|
|
100
111
|
this.container.appendChild(div);
|
|
101
|
-
|
|
112
|
+
this.menuEntryDivs[entry.entry] = div;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
prepareMenuEntries(evt, mapCoordinate) {
|
|
116
|
+
for (const entry of this.menuEntries) {
|
|
117
|
+
const interactionType = entry.prepare?.(evt, mapCoordinate) ?? EntryInteractionType.ENABLED;
|
|
118
|
+
const menuEntryDiv = this.menuEntryDivs[entry.entry];
|
|
119
|
+
switch (interactionType) {
|
|
120
|
+
case EntryInteractionType.ENABLED:
|
|
121
|
+
menuEntryDiv.ariaDisabled = 'false';
|
|
122
|
+
menuEntryDiv.style.display = 'block';
|
|
123
|
+
break;
|
|
124
|
+
case EntryInteractionType.DISABLED:
|
|
125
|
+
menuEntryDiv.ariaDisabled = 'true';
|
|
126
|
+
menuEntryDiv.style.display = 'block';
|
|
127
|
+
break;
|
|
128
|
+
case EntryInteractionType.NOT_SHOWN:
|
|
129
|
+
menuEntryDiv.ariaDisabled = 'true';
|
|
130
|
+
menuEntryDiv.style.display = 'none';
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
102
134
|
}
|
|
103
135
|
openMenu(mapCoordinates) {
|
|
104
136
|
if (!this.container.parentNode) {
|
|
@@ -19,9 +19,9 @@ class ModalsComponent extends GirafeHTMLElement {
|
|
|
19
19
|
constructor() {
|
|
20
20
|
super('native-modals');
|
|
21
21
|
}
|
|
22
|
-
initBox(type, message, title, placeholder) {
|
|
22
|
+
initBox(type, message, messageHandler, title, placeholder) {
|
|
23
23
|
this.boxType = type;
|
|
24
|
-
this.boxMessage = this.context.i18nManager.getTranslation(message);
|
|
24
|
+
this.boxMessage = messageHandler(this.context.i18nManager.getTranslation(message));
|
|
25
25
|
if (title) {
|
|
26
26
|
this.boxTitle = this.context.i18nManager.getTranslation(title);
|
|
27
27
|
}
|
|
@@ -40,13 +40,13 @@ class ModalsComponent extends GirafeHTMLElement {
|
|
|
40
40
|
this.refreshRender();
|
|
41
41
|
}
|
|
42
42
|
async promptBox(message, title, placeholder) {
|
|
43
|
-
this.initBox('prompt', message, title, placeholder);
|
|
43
|
+
this.initBox('prompt', message, (message) => message, title, placeholder);
|
|
44
44
|
return new Promise((resolve) => {
|
|
45
45
|
this.resolvePrompt = resolve;
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
async alertConfirmBox(type, message, title) {
|
|
49
|
-
this.initBox(type, message, title);
|
|
48
|
+
async alertConfirmBox(type, message, title, messageHandler) {
|
|
49
|
+
this.initBox(type, message, messageHandler, title);
|
|
50
50
|
return new Promise((resolve) => {
|
|
51
51
|
this.resolveAlertConfirm = resolve;
|
|
52
52
|
});
|
|
@@ -75,8 +75,9 @@ class ModalsComponent extends GirafeHTMLElement {
|
|
|
75
75
|
}
|
|
76
76
|
connectedCallback() {
|
|
77
77
|
super.connectedCallback();
|
|
78
|
-
|
|
79
|
-
window.
|
|
78
|
+
const defaultMessageHandler = (translatedMessage) => translatedMessage;
|
|
79
|
+
window.gConfirm = (message, title, messageHandler) => this.alertConfirmBox('confirm', message, title, messageHandler ?? defaultMessageHandler);
|
|
80
|
+
window.gAlert = (message, title) => this.alertConfirmBox('alert', message, title ?? '', defaultMessageHandler);
|
|
80
81
|
window.gPrompt = (message, title, placeholder) => this.promptBox(message, title, placeholder);
|
|
81
82
|
this.render();
|
|
82
83
|
super.girafeTranslate();
|
package/main.tools.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ declare global {
|
|
|
11
11
|
CESIUM_BASE_URL: string;
|
|
12
12
|
Cesium: unknown;
|
|
13
13
|
cordova: unknown;
|
|
14
|
-
gConfirm(message: string, title?: string): Promise<boolean>;
|
|
14
|
+
gConfirm(message: string, title: string, messageHandler?: (translatedMessage: string) => string): Promise<boolean>;
|
|
15
15
|
gAlert(message: string, title?: string): Promise<boolean>;
|
|
16
16
|
gPrompt(message: string, title?: string, placeholder?: string): Promise<string | false>;
|
|
17
17
|
gOpenWindow(title: string, url: string, width?: string | number, height?: string | number, top?: string | number, left?: string | number): void;
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.2152254142", "build":"2152254142", "date":"11/11/2025"}
|