@geogirafe/lib-geoportal 1.1.0-dev.2456794124 → 1.1.0-dev.2463717290
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/components/drawing/olDrawing.d.ts +7 -0
- package/components/drawing/olDrawing.js +132 -25
- package/components/drawing/shapeConstraints.d.ts +11 -0
- package/components/drawing/shapeConstraints.js +64 -0
- package/package.json +1 -1
- package/templates/public/about.json +1 -1
- package/tools/configuration/girafeconfig.js +1 -1
|
@@ -33,6 +33,7 @@ export default class OlDrawing {
|
|
|
33
33
|
lastClosestFeature: Feature | null;
|
|
34
34
|
translate: Translate | null;
|
|
35
35
|
transform: Transform | null;
|
|
36
|
+
private readonly modifyFeatureChangeListeners;
|
|
36
37
|
defaultStyle: StyleFunction | undefined;
|
|
37
38
|
private readonly updateGeometryInState;
|
|
38
39
|
private get state();
|
|
@@ -114,6 +115,12 @@ export default class OlDrawing {
|
|
|
114
115
|
getStyle(dFeature: DrawingFeature, olFeature: Feature<Geometry>): Style[];
|
|
115
116
|
private removeDrawInteraction;
|
|
116
117
|
private removeModifyInteraction;
|
|
118
|
+
private getDrawingShapeType;
|
|
119
|
+
private isSquareOrRectangle;
|
|
120
|
+
private shouldAllowVertexInsertionAtEvent;
|
|
121
|
+
private getHoveredModifiableFeatureAtPixel;
|
|
122
|
+
private applyScaleRotateOnModifyStyle;
|
|
123
|
+
private clearModifyFeatureChangeListeners;
|
|
117
124
|
private removeSnapInteraction;
|
|
118
125
|
private removeEditContextMenu;
|
|
119
126
|
registerInteractions(): void;
|
|
@@ -10,11 +10,12 @@ import VectorLayer from 'ol/layer/Vector.js';
|
|
|
10
10
|
import GeoJSON from 'ol/format/GeoJSON.js';
|
|
11
11
|
import { getPointResolution } from 'ol/proj.js';
|
|
12
12
|
import { always, never, primaryAction } from 'ol/events/condition.js';
|
|
13
|
-
import { ensurePolygonIsProperlyClosed, getAreaOfPolygon, getDistance, getHalfPoint, getLabelStyle, getRadiusDataForCircle } from '../../tools/utils/olutils.js';
|
|
13
|
+
import { ensurePolygonIsProperlyClosed, getAreaOfPolygon, getDistance, getHalfPoint, getLabelStyle, getRadiusDataForCircle, unByKeyAll } from '../../tools/utils/olutils.js';
|
|
14
14
|
import { ContextMenu, EntryInteractionType } from '../map/tools/contextmenu.js';
|
|
15
15
|
import { formatCoordinates } from '../../tools/geometrytools.js';
|
|
16
16
|
import { v4 as uuidv4 } from 'uuid';
|
|
17
17
|
import { isAlternateMouseClick, isPrimaryPointerAction } from '../../tools/state/userinteractionevent.js';
|
|
18
|
+
import { calculateCenterAndMinRadius, createScaledAndRotatedGeometry, getGeometryForRendering, shouldAllowVertexInsertionForShape } from './shapeConstraints.js';
|
|
18
19
|
import Transform from 'ol-ext/interaction/Transform.js';
|
|
19
20
|
function getLineStroke(strokeType, lineWidth) {
|
|
20
21
|
switch (strokeType) {
|
|
@@ -68,6 +69,7 @@ export default class OlDrawing {
|
|
|
68
69
|
lastClosestFeature = null;
|
|
69
70
|
translate = null;
|
|
70
71
|
transform = null;
|
|
72
|
+
modifyFeatureChangeListeners = [];
|
|
71
73
|
defaultStyle;
|
|
72
74
|
updateGeometryInState = (olFeature) => {
|
|
73
75
|
const idx = this.drawingState.features.findIndex((f) => f.id === olFeature.getId());
|
|
@@ -127,6 +129,7 @@ export default class OlDrawing {
|
|
|
127
129
|
}
|
|
128
130
|
addModifyInteraction() {
|
|
129
131
|
this.removeModifyInteraction();
|
|
132
|
+
const vertexStyle = new DrawingFeature(DrawingShape.Point, this.drawingState, this.config.drawing).getVertexStyle(true);
|
|
130
133
|
this.modify = new Modify({
|
|
131
134
|
features: this.modifiableFeatures,
|
|
132
135
|
// Feature editing is triggered by: 1) primary action = click or touch, 2) alternate mouse click = remove vertex
|
|
@@ -135,34 +138,64 @@ export default class OlDrawing {
|
|
|
135
138
|
isAlternateMouseClick(e)) &&
|
|
136
139
|
this.canExecute('map.modify'),
|
|
137
140
|
deleteCondition: never,
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
// For square/rectangle, we disable vertex insertion only on the hovered shape.
|
|
142
|
+
insertVertexCondition: (e) => primaryAction(e) && this.shouldAllowVertexInsertionAtEvent(e),
|
|
143
|
+
style: (feature) => {
|
|
144
|
+
this.applyScaleRotateOnModifyStyle(feature);
|
|
145
|
+
return vertexStyle;
|
|
146
|
+
},
|
|
140
147
|
snapToPointer: true,
|
|
141
148
|
pixelTolerance: this.map.pixelTolerance
|
|
142
149
|
});
|
|
143
150
|
this.map.olMap.addInteraction(this.modify);
|
|
144
151
|
this.modify.on('modifystart', (e) => {
|
|
152
|
+
this.clearModifyFeatureChangeListeners();
|
|
145
153
|
e.features.forEach((olFeature) => {
|
|
146
|
-
olFeature.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
const geometry = olFeature.getGeometry();
|
|
155
|
+
if (!geometry) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const shapeType = this.drawingState.features.find((f) => f.id === olFeature.getId())?.type;
|
|
159
|
+
if (shapeType === DrawingShape.Square || shapeType === DrawingShape.Rectangle) {
|
|
160
|
+
olFeature.set('modifyGeometry', { geometry: geometry.clone() }, true);
|
|
161
|
+
}
|
|
162
|
+
else if (shapeType === DrawingShape.Disk) {
|
|
163
|
+
const listener = olFeature.on('change', (e) => {
|
|
164
|
+
const geometry = e.target.getGeometry();
|
|
165
|
+
if (!geometry) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (geometry.getType() === 'Circle') {
|
|
169
|
+
const circle = geometry;
|
|
170
|
+
const newRadius = circle.getRadius();
|
|
171
|
+
const properties = circle.getProperties();
|
|
172
|
+
const { pointer } = properties;
|
|
173
|
+
const oldRadiusLine = new LineString([circle.getCenter(), pointer]);
|
|
174
|
+
oldRadiusLine.scale(newRadius / oldRadiusLine.getLength());
|
|
175
|
+
circle.setProperties({
|
|
176
|
+
...properties,
|
|
177
|
+
pointer: oldRadiusLine.getLastCoordinate()
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
this.modifyFeatureChangeListeners.push(listener);
|
|
182
|
+
}
|
|
161
183
|
});
|
|
162
184
|
});
|
|
163
185
|
// Update the modified geometries in the state
|
|
164
186
|
this.modify.on('modifyend', (e) => {
|
|
165
|
-
|
|
187
|
+
this.clearModifyFeatureChangeListeners();
|
|
188
|
+
e.features.forEach((olFeature) => {
|
|
189
|
+
const shapeType = this.drawingState.features.find((f) => f.id === olFeature.getId())?.type;
|
|
190
|
+
if (shapeType === DrawingShape.Square || shapeType === DrawingShape.Rectangle) {
|
|
191
|
+
const modifyGeometry = olFeature.get('modifyGeometry');
|
|
192
|
+
if (modifyGeometry?.geometry) {
|
|
193
|
+
olFeature.setGeometry(modifyGeometry.geometry);
|
|
194
|
+
}
|
|
195
|
+
olFeature.unset('modifyGeometry', true);
|
|
196
|
+
}
|
|
197
|
+
this.updateGeometryInState(olFeature); //NOSONAR(typescript:S7728) Collection<?> is a custom Implementation with custom forEach
|
|
198
|
+
});
|
|
166
199
|
});
|
|
167
200
|
}
|
|
168
201
|
addSnapInteraction() {
|
|
@@ -302,9 +335,13 @@ export default class OlDrawing {
|
|
|
302
335
|
}
|
|
303
336
|
}
|
|
304
337
|
prepareMenuEntriesForVertex = (_e, mapCoordinate) => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
338
|
+
if (!this.hasEditableVertexAtCoordinate(mapCoordinate)) {
|
|
339
|
+
return EntryInteractionType.NOT_SHOWN;
|
|
340
|
+
}
|
|
341
|
+
if (this.isSquareOrRectangle(this.getDrawingShapeType(this.lastClosestFeature))) {
|
|
342
|
+
return EntryInteractionType.NOT_SHOWN;
|
|
343
|
+
}
|
|
344
|
+
return EntryInteractionType.ENABLED;
|
|
308
345
|
};
|
|
309
346
|
prepareMenuEntriesForShape = (_e, mapCoordinate) => {
|
|
310
347
|
return this.hasEditableShapeAtCoordinate(mapCoordinate)
|
|
@@ -621,8 +658,10 @@ export default class OlDrawing {
|
|
|
621
658
|
}
|
|
622
659
|
// TODO Move as much parameters as possible into DrawingFeature
|
|
623
660
|
getStyle(dFeature, olFeature) {
|
|
624
|
-
const
|
|
625
|
-
|
|
661
|
+
const geometry = getGeometryForRendering(olFeature);
|
|
662
|
+
if (!geometry) {
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
626
665
|
const measureFont = 'Bold ' + dFeature.measureFontSize + 'px/1 ' + dFeature.font;
|
|
627
666
|
const nameFont = 'Bold ' + dFeature.nameFontSize + 'px/1 ' + dFeature.font;
|
|
628
667
|
const measureColor = 'rgba(0, 0, 0, 0.4)';
|
|
@@ -782,7 +821,7 @@ export default class OlDrawing {
|
|
|
782
821
|
const vertexStyle = dFeature.getVertexStyle();
|
|
783
822
|
// Add a node style to every vertex of the geometry
|
|
784
823
|
vertexStyle.setGeometry(function (f) {
|
|
785
|
-
const geom = f
|
|
824
|
+
const geom = getGeometryForRendering(f);
|
|
786
825
|
if (geom && geom instanceof Geometry) {
|
|
787
826
|
return extractVerticesFromGeometry(geom);
|
|
788
827
|
}
|
|
@@ -800,11 +839,79 @@ export default class OlDrawing {
|
|
|
800
839
|
this.context.userInteractionManager.unregisterListener('map.select', this.toolName);
|
|
801
840
|
}
|
|
802
841
|
removeModifyInteraction() {
|
|
842
|
+
this.clearModifyFeatureChangeListeners();
|
|
803
843
|
if (this.modify) {
|
|
804
844
|
this.map.olMap.removeInteraction(this.modify);
|
|
805
845
|
this.modify = null;
|
|
806
846
|
}
|
|
807
847
|
}
|
|
848
|
+
getDrawingShapeType(olFeature) {
|
|
849
|
+
if (!olFeature) {
|
|
850
|
+
return undefined;
|
|
851
|
+
}
|
|
852
|
+
return this.drawingState.features.find((f) => f.id === olFeature.getId())?.type;
|
|
853
|
+
}
|
|
854
|
+
isSquareOrRectangle(shapeType) {
|
|
855
|
+
return shapeType === DrawingShape.Square || shapeType === DrawingShape.Rectangle;
|
|
856
|
+
}
|
|
857
|
+
shouldAllowVertexInsertionAtEvent(e) {
|
|
858
|
+
const hoveredFeature = this.getHoveredModifiableFeatureAtPixel(e.pixel);
|
|
859
|
+
return shouldAllowVertexInsertionForShape(this.getDrawingShapeType(hoveredFeature));
|
|
860
|
+
}
|
|
861
|
+
getHoveredModifiableFeatureAtPixel(pixel) {
|
|
862
|
+
const modifiableFeatureSet = new Set(this.modifiableFeatures.getArray());
|
|
863
|
+
const hoveredFeature = this.map.olMap.forEachFeatureAtPixel(pixel, (feature, layer) => {
|
|
864
|
+
if (!(feature instanceof Feature) || layer !== this.drawingLayer || !modifiableFeatureSet.has(feature)) {
|
|
865
|
+
return undefined;
|
|
866
|
+
}
|
|
867
|
+
return feature;
|
|
868
|
+
}, {
|
|
869
|
+
hitTolerance: this.map.pixelTolerance
|
|
870
|
+
});
|
|
871
|
+
return hoveredFeature instanceof Feature ? hoveredFeature : null;
|
|
872
|
+
}
|
|
873
|
+
applyScaleRotateOnModifyStyle(modifyPointFeature) {
|
|
874
|
+
const modifyPointGeometry = modifyPointFeature.getGeometry();
|
|
875
|
+
if (!(modifyPointGeometry instanceof Point)) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const currentPoint = modifyPointGeometry.getCoordinates();
|
|
879
|
+
const features = modifyPointFeature.get('features');
|
|
880
|
+
if (!features) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
features.forEach((olFeature) => {
|
|
884
|
+
const shapeType = this.getDrawingShapeType(olFeature);
|
|
885
|
+
if (!this.isSquareOrRectangle(shapeType)) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const modifyGeometry = olFeature.get('modifyGeometry');
|
|
889
|
+
if (!modifyGeometry?.geometry) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (!modifyGeometry.point ||
|
|
893
|
+
!modifyGeometry.geometry0 ||
|
|
894
|
+
!modifyGeometry.center ||
|
|
895
|
+
modifyGeometry.minRadius === undefined) {
|
|
896
|
+
modifyGeometry.point = [...currentPoint];
|
|
897
|
+
modifyGeometry.geometry0 = modifyGeometry.geometry;
|
|
898
|
+
const geometryData = calculateCenterAndMinRadius(modifyGeometry.geometry0);
|
|
899
|
+
modifyGeometry.center = geometryData.center;
|
|
900
|
+
modifyGeometry.minRadius = geometryData.minRadius;
|
|
901
|
+
}
|
|
902
|
+
if (!modifyGeometry.geometry0 || !modifyGeometry.center || modifyGeometry.minRadius === undefined) {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
modifyGeometry.geometry = createScaledAndRotatedGeometry(modifyGeometry.geometry0, modifyGeometry.center, modifyGeometry.minRadius, modifyGeometry.point, currentPoint);
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
clearModifyFeatureChangeListeners() {
|
|
909
|
+
if (this.modifyFeatureChangeListeners.length === 0) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
unByKeyAll(this.modifyFeatureChangeListeners);
|
|
913
|
+
this.modifyFeatureChangeListeners.length = 0;
|
|
914
|
+
}
|
|
808
915
|
removeSnapInteraction() {
|
|
809
916
|
if (this.snap) {
|
|
810
917
|
this.map.olMap.removeInteraction(this.snap);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Coordinate } from 'ol/coordinate.js';
|
|
2
|
+
import Geometry from 'ol/geom/Geometry.js';
|
|
3
|
+
import Feature from 'ol/Feature.js';
|
|
4
|
+
import { DrawingShape } from './drawingFeature.js';
|
|
5
|
+
export declare function calculateCenterAndMinRadius(geometry: Geometry): {
|
|
6
|
+
center: Coordinate;
|
|
7
|
+
minRadius: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function createScaledAndRotatedGeometry(geometry0: Geometry, center: Coordinate, minRadius: number, initialPoint: Coordinate, currentPoint: Coordinate): Geometry;
|
|
10
|
+
export declare function getGeometryForRendering(feature: Feature<Geometry>): Geometry | undefined;
|
|
11
|
+
export declare function shouldAllowVertexInsertionForShape(shapeType: DrawingShape | undefined): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { getCenter, getHeight, getWidth } from 'ol/extent.js';
|
|
3
|
+
import LineString from 'ol/geom/LineString.js';
|
|
4
|
+
import Polygon from 'ol/geom/Polygon.js';
|
|
5
|
+
import { DrawingShape } from './drawingFeature.js';
|
|
6
|
+
const EPSILON = 1e-12;
|
|
7
|
+
function sqDistance(a, b) {
|
|
8
|
+
const dx = a[0] - b[0];
|
|
9
|
+
const dy = a[1] - b[1];
|
|
10
|
+
return dx * dx + dy * dy;
|
|
11
|
+
}
|
|
12
|
+
export function calculateCenterAndMinRadius(geometry) {
|
|
13
|
+
let center;
|
|
14
|
+
let coordinates;
|
|
15
|
+
if (geometry instanceof Polygon) {
|
|
16
|
+
const ring = geometry.getCoordinates()[0];
|
|
17
|
+
const polygonCoords = ring.slice(0, -1);
|
|
18
|
+
const x = polygonCoords.reduce((sum, c) => sum + c[0], 0);
|
|
19
|
+
const y = polygonCoords.reduce((sum, c) => sum + c[1], 0);
|
|
20
|
+
center = [x / polygonCoords.length, y / polygonCoords.length];
|
|
21
|
+
coordinates = polygonCoords;
|
|
22
|
+
}
|
|
23
|
+
else if (geometry instanceof LineString) {
|
|
24
|
+
center = geometry.getCoordinateAt(0.5);
|
|
25
|
+
coordinates = geometry.getCoordinates();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
center = getCenter(geometry.getExtent());
|
|
29
|
+
}
|
|
30
|
+
if (coordinates && coordinates.length > 0) {
|
|
31
|
+
const maxSqDistance = Math.max(...coordinates.map((coordinate) => sqDistance(coordinate, center)));
|
|
32
|
+
return {
|
|
33
|
+
center,
|
|
34
|
+
minRadius: Math.sqrt(maxSqDistance) / 3
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
center,
|
|
39
|
+
minRadius: Math.max(getWidth(geometry.getExtent()), getHeight(geometry.getExtent())) / 3
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function createScaledAndRotatedGeometry(geometry0, center, minRadius, initialPoint, currentPoint) {
|
|
43
|
+
const initialRadius = Math.sqrt(sqDistance(initialPoint, center));
|
|
44
|
+
if (initialRadius <= minRadius) {
|
|
45
|
+
return geometry0.clone();
|
|
46
|
+
}
|
|
47
|
+
const currentRadius = Math.sqrt(sqDistance(currentPoint, center));
|
|
48
|
+
if (currentRadius <= EPSILON) {
|
|
49
|
+
return geometry0.clone();
|
|
50
|
+
}
|
|
51
|
+
const initialAngle = Math.atan2(initialPoint[1] - center[1], initialPoint[0] - center[0]);
|
|
52
|
+
const currentAngle = Math.atan2(currentPoint[1] - center[1], currentPoint[0] - center[0]);
|
|
53
|
+
const geometry = geometry0.clone();
|
|
54
|
+
geometry.scale(currentRadius / initialRadius, undefined, center);
|
|
55
|
+
geometry.rotate(currentAngle - initialAngle, center);
|
|
56
|
+
return geometry;
|
|
57
|
+
}
|
|
58
|
+
export function getGeometryForRendering(feature) {
|
|
59
|
+
const modifyGeometry = feature.get('modifyGeometry');
|
|
60
|
+
return modifyGeometry?.geometry ?? feature.getGeometry() ?? undefined;
|
|
61
|
+
}
|
|
62
|
+
export function shouldAllowVertexInsertionForShape(shapeType) {
|
|
63
|
+
return shapeType !== DrawingShape.Square && shapeType !== DrawingShape.Rectangle;
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.1.0-dev.
|
|
1
|
+
{"version":"1.1.0-dev.2463717290", "build":"2463717290", "date":"19/04/2026"}
|
|
@@ -248,7 +248,7 @@ class GirafeConfig {
|
|
|
248
248
|
extension: '.csv',
|
|
249
249
|
includeHeader: true,
|
|
250
250
|
quote: "'",
|
|
251
|
-
separator: ','
|
|
251
|
+
separator: ';' /* Switched to ';' as ',' wasn't working with MS Excel out of the Box (double-click on the file) while for example LibreOffice works with both*/
|
|
252
252
|
};
|
|
253
253
|
return {
|
|
254
254
|
...defaultConfig,
|