@vcmap/core 5.0.0-rc.22 → 5.0.0-rc.24

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.
Files changed (59) hide show
  1. package/index.d.ts +631 -98
  2. package/index.js +24 -9
  3. package/package.json +2 -2
  4. package/src/category/category.js +1 -1
  5. package/src/featureProvider/abstractFeatureProvider.js +1 -18
  6. package/src/featureProvider/wmsFeatureProvider.js +1 -1
  7. package/src/interaction/eventHandler.js +14 -0
  8. package/src/layer/cesium/clusterContext.js +12 -0
  9. package/src/layer/cesium/vectorCesiumImpl.js +2 -2
  10. package/src/layer/cesium/vectorContext.js +115 -7
  11. package/src/layer/cesiumTilesetLayer.js +0 -14
  12. package/src/layer/czmlLayer.js +1 -1
  13. package/src/layer/dataSourceLayer.js +1 -53
  14. package/src/layer/featureLayer.js +0 -44
  15. package/src/layer/featureStoreLayer.js +0 -15
  16. package/src/layer/layer.js +0 -11
  17. package/src/layer/vectorHelpers.js +0 -85
  18. package/src/layer/vectorLayer.js +0 -9
  19. package/src/layer/vectorProperties.js +150 -8
  20. package/src/layer/vectorTileLayer.js +0 -9
  21. package/src/layer/wmsHelpers.js +2 -0
  22. package/src/map/baseOLMap.js +12 -6
  23. package/src/map/cesiumMap.js +48 -38
  24. package/src/map/openlayersMap.js +6 -5
  25. package/src/map/vcsMap.js +22 -0
  26. package/src/style/arcStyle.js +316 -0
  27. package/src/style/arrowStyle.js +269 -0
  28. package/src/util/editor/createFeatureSession.js +3 -1
  29. package/src/util/editor/editFeaturesSession.js +315 -0
  30. package/src/util/editor/editGeometrySession.js +5 -1
  31. package/src/util/editor/editorHelpers.js +118 -14
  32. package/src/util/editor/editorSessionHelpers.js +12 -0
  33. package/src/util/editor/editorSymbols.js +6 -0
  34. package/src/util/editor/interactions/editFeaturesMouseOverInteraction.js +120 -0
  35. package/src/util/editor/interactions/editGeometryMouseOverInteraction.js +1 -3
  36. package/src/util/editor/interactions/ensureHandlerSelectionInteraction.js +48 -0
  37. package/src/util/editor/interactions/mapInteractionController.js +5 -2
  38. package/src/util/editor/interactions/selectMultiFeatureInteraction.js +146 -0
  39. package/src/util/editor/interactions/translateVertexInteraction.js +2 -2
  40. package/src/util/editor/transformation/create2DHandlers.js +294 -0
  41. package/src/util/editor/transformation/create3DHandlers.js +575 -0
  42. package/src/util/editor/transformation/extrudeInteraction.js +91 -0
  43. package/src/util/editor/transformation/rotateInteraction.js +188 -0
  44. package/src/util/editor/transformation/scaleInteraction.js +185 -0
  45. package/src/util/editor/transformation/transformationHandler.js +168 -0
  46. package/src/util/editor/transformation/transformationTypes.js +83 -0
  47. package/src/util/editor/transformation/translateInteraction.js +209 -0
  48. package/src/util/featureconverter/arcToCesium.js +87 -0
  49. package/src/util/featureconverter/convert.js +7 -1
  50. package/src/util/featureconverter/extent3D.js +64 -1
  51. package/src/util/featureconverter/lineStringToCesium.js +103 -2
  52. package/src/util/featureconverter/pointHelpers.js +341 -0
  53. package/src/util/featureconverter/pointToCesium.js +27 -76
  54. package/src/util/geometryHelpers.js +11 -8
  55. package/src/util/mapCollection.js +25 -0
  56. package/src/util/math.js +99 -2
  57. package/tests/unit/helpers/cesiumHelpers.js +17 -5
  58. package/tests/unit/helpers/helpers.js +13 -0
  59. package/src/featureProvider/featureProviderHelpers.js +0 -50
@@ -0,0 +1,269 @@
1
+ import { Cartesian3 } from '@vcmap/cesium';
2
+ import {
3
+ Circle,
4
+ Fill,
5
+ Icon,
6
+ Stroke,
7
+ Style,
8
+ Image as OlImage,
9
+ } from 'ol/style.js';
10
+ import { parseEnumValue, parseNumber } from '@vcsuite/parsers';
11
+ import { getCartesianBearing } from '../util/math.js';
12
+ import { PrimitiveOptionsType } from '../layer/vectorProperties.js';
13
+ import { getStringColor, parseColor } from './styleHelpers.js';
14
+
15
+ /**
16
+ * @typedef {Object} ArrowStyleOptions
17
+ * @property {import("ol/color").Color|import("ol/colorlike").ColorLike} [color='#000000'] - color used to color in the line & the icon
18
+ * @property {number} [width=1] - passed to ol.Style.Stroke
19
+ * @property {number} [zIndex] - passed to ol.Style
20
+ * @property {import("ol/style/Icon").Options|import("ol/style").Image} [arrowIcon] - icon options to use. if none are provided, if is attempted to derive the arrow icon from the primitive options. if providing your own icon, the color will not be options.color by default
21
+ * @property {VectorPropertiesPrimitiveOptions} [primitiveOptions] - the default primitive options are a cylinder with a bottom radius of 1/3 its length and 0 top radius
22
+ * @property {ArrowEnd} [end=ArrowEnd.END] - end to place the arrow head at
23
+ */
24
+
25
+ /**
26
+ * @enum {string}
27
+ */
28
+ export const ArrowEnd = {
29
+ NONE: 'none',
30
+ BOTH: 'both',
31
+ START: 'start',
32
+ END: 'end',
33
+ };
34
+
35
+ /**
36
+ * @param {VectorPropertiesPrimitiveOptions} primitiveOptions
37
+ * @param {number} [twoDFactor=1.5]
38
+ * @returns {string}
39
+ * @private
40
+ */
41
+ export function getDefaultArrowIconSrc(primitiveOptions, twoDFactor = 1.5) {
42
+ let height = 13;
43
+ let width = 13;
44
+ let points = [[0, 13], [13, 13], [6, 0]];
45
+
46
+ if (primitiveOptions.type === PrimitiveOptionsType.SPHERE) {
47
+ const radius = Math.floor((primitiveOptions.geometryOptions?.radius ?? 1) * twoDFactor);
48
+ return `<svg height="${radius * 2}" width="${radius * 2}" xmlns="http://www.w3.org/2000/svg">
49
+ <circle cx="${radius}" cy="${radius}" r="${radius}" style="fill:white;" />
50
+ </svg>`;
51
+ }
52
+
53
+ if (
54
+ primitiveOptions.type === PrimitiveOptionsType.BOX &&
55
+ primitiveOptions.geometryOptions.minimum &&
56
+ primitiveOptions.geometryOptions.maximum
57
+ ) {
58
+ const min = Array.isArray(primitiveOptions.geometryOptions.minimum) ?
59
+ primitiveOptions.geometryOptions.minimum :
60
+ Cartesian3.pack(primitiveOptions.geometryOptions.minimum, []);
61
+
62
+ const max = Array.isArray(primitiveOptions.geometryOptions.maximum) ?
63
+ primitiveOptions.geometryOptions.maximum :
64
+ Cartesian3.pack(primitiveOptions.geometryOptions.maximum, []);
65
+
66
+ width = Math.floor((max[0] - min[0]) * twoDFactor);
67
+ height = Math.floor((max[1] - min[1]) * twoDFactor);
68
+
69
+ points = [
70
+ [0, 0],
71
+ [width, 0],
72
+ [width, height],
73
+ [0, height],
74
+ ];
75
+ } else if (
76
+ primitiveOptions.type === PrimitiveOptionsType.CYLINDER &&
77
+ primitiveOptions.geometryOptions.length
78
+ ) {
79
+ const topRadius = Math.floor(primitiveOptions.geometryOptions.topRadius * twoDFactor);
80
+ const bottomRadius = Math.floor(primitiveOptions.geometryOptions.bottomRadius * twoDFactor);
81
+ const maxRadius = Math.max(topRadius, bottomRadius);
82
+
83
+ height = Math.floor(primitiveOptions.geometryOptions.length * twoDFactor);
84
+ width = maxRadius * 2;
85
+
86
+ points = [
87
+ [(width / 2) - bottomRadius, height],
88
+ [(width / 2) + bottomRadius, height],
89
+ [(width / 2) + topRadius, 0],
90
+ [(width / 2) - topRadius, 0],
91
+ ];
92
+ if (bottomRadius === 0) {
93
+ points.splice(1, 1);
94
+ } else if (topRadius === 0) {
95
+ points.splice(2, 1);
96
+ }
97
+ }
98
+ return `<svg height="${height}" width="${width}" xmlns="http://www.w3.org/2000/svg"><polygon points="${points.map(p => p.join(',')).join(' ')}" style="fill:white;" /></svg>`;
99
+ }
100
+
101
+ /**
102
+ * @returns {VectorPropertiesPrimitiveOptions}
103
+ */
104
+ function getDefaultArrowPrimitive() {
105
+ return {
106
+ type: PrimitiveOptionsType.CYLINDER,
107
+ geometryOptions: {
108
+ length: 9,
109
+ bottomRadius: 3,
110
+ topRadius: 0,
111
+ },
112
+ offset: [0, 0, -4.3],
113
+ };
114
+ }
115
+
116
+ /**
117
+ * A style which renders arrow heads at the ends of a line string. This style cannot be applied to non-LineString geometries.
118
+ * When setting this on a layer with heterogeneous geometry types, use a style function.
119
+ * @class
120
+ * @extends {Style}
121
+ */
122
+ class ArrowStyle extends Style {
123
+ /**
124
+ * @param {ArrowStyleOptions} [options={}]
125
+ */
126
+ constructor(options = {}) {
127
+ const color = options.color ?? '#000000';
128
+ const styleOptions = {
129
+ stroke: new Stroke({
130
+ color,
131
+ width: parseNumber(options.width, 1),
132
+ }),
133
+ zIndex: options.zIndex,
134
+ };
135
+
136
+ const primitiveOptions = options.primitiveOptions ?? getDefaultArrowPrimitive();
137
+ const iconOptions = options.arrowIcon ?? {
138
+ src: `data:image/svg+xml,${encodeURIComponent(getDefaultArrowIconSrc(primitiveOptions))}`,
139
+ color: parseColor(color),
140
+ };
141
+ styleOptions.image = iconOptions instanceof OlImage ? iconOptions : new Icon(iconOptions);
142
+ super(styleOptions);
143
+
144
+ /**
145
+ * @type {VectorPropertiesPrimitiveOptions}
146
+ */
147
+ this.primitiveOptions = primitiveOptions;
148
+ /**
149
+ * @type {ArrowEnd}
150
+ */
151
+ this.end = parseEnumValue(options.end, ArrowEnd, ArrowEnd.END);
152
+ this.setRenderer(this._render.bind(this));
153
+ }
154
+
155
+ /**
156
+ * Same as getStroke().getWidth() / getStroke().setWidth()
157
+ * @type {number}
158
+ */
159
+ get width() {
160
+ return this.getStroke().getWidth();
161
+ }
162
+
163
+ /**
164
+ * @param {number} width
165
+ */
166
+ set width(width) {
167
+ this.getStroke().setWidth(width);
168
+ }
169
+
170
+ /**
171
+ * The color of the stroke and icon styles. Setting the color will not re-apply the icons color.
172
+ * @type {import("ol/color").Color|import("ol/colorlike").ColorLike}
173
+ */
174
+ get color() {
175
+ return this.getStroke().getColor();
176
+ }
177
+
178
+ /**
179
+ * @param {import("ol/color").Color|import("ol/colorlike").ColorLike} color
180
+ */
181
+ set color(color) {
182
+ this.getStroke().setColor(color);
183
+ }
184
+
185
+ /**
186
+ * @param {CanvasRenderingContext2D} ctx
187
+ * @param {import("ol/coordinate").Coordinate} imagePosition
188
+ * @param {number} rotation
189
+ * @param {number} pixelRatio
190
+ * @private
191
+ */
192
+ _drawArrow(ctx, imagePosition, rotation, pixelRatio) {
193
+ ctx.save();
194
+ let scale = this.getImage().getScale();
195
+ scale = Array.isArray(scale) ? scale : [scale, scale];
196
+ ctx.setTransform(scale[0], 0, 0, scale[1], imagePosition[0], imagePosition[1]);
197
+ ctx.rotate(Math.PI - rotation);
198
+ const image = this.getImage().getImage(pixelRatio);
199
+ ctx.translate(0, Math.floor(image.height / 2));
200
+ ctx.drawImage(image, -image.width / 2, -image.height / 2);
201
+ ctx.restore();
202
+ }
203
+
204
+ /**
205
+ * @param {Array<import("ol/coordinate").Coordinate>} geom
206
+ * @param {import("ol/render").State} e
207
+ * @private
208
+ */
209
+ _render(geom, e) {
210
+ if (e.geometry.getType() === 'LineString' && geom.length > 1) {
211
+ const ctx = e.context;
212
+ if (this.end !== ArrowEnd.NONE) {
213
+ if (this.end === ArrowEnd.START || this.end === ArrowEnd.BOTH) {
214
+ this._drawArrow(ctx, geom[0], getCartesianBearing(geom[1], geom[0]), e.pixelRatio);
215
+ }
216
+
217
+ if (this.end === ArrowEnd.END || this.end === ArrowEnd.BOTH) {
218
+ this._drawArrow(ctx, geom.at(-1), getCartesianBearing(geom.at(-2), geom.at(-1)), e.pixelRatio);
219
+ }
220
+ }
221
+ ctx.save();
222
+ ctx.lineJoin = 'round';
223
+ ctx.lineWidth = this.width;
224
+ ctx.strokeStyle = getStringColor(this.color);
225
+ ctx.beginPath();
226
+ ctx.moveTo(geom[0][0], geom[0][1]);
227
+ for (let i = 0; i < geom.length; i++) {
228
+ ctx.lineTo(geom[i][0], geom[i][1]);
229
+ }
230
+ ctx.stroke();
231
+ ctx.restore();
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Returns the style used to render primitives (a style with a circle image and the color of this style).
237
+ * @returns {import("ol/style").Style}
238
+ */
239
+ getOlcsStyle() {
240
+ return new Style({
241
+ image: new Circle({
242
+ radius: 2,
243
+ fill: new Fill({ color: this.color }),
244
+ }),
245
+ });
246
+ }
247
+
248
+ /**
249
+ * @returns {ArrowStyleOptions}
250
+ * @protected
251
+ */
252
+ _getCloneOptions() {
253
+ return {
254
+ color: this.color,
255
+ width: this.width,
256
+ arrowIcon: this.getImage().clone(),
257
+ zIndex: this.getZIndex(),
258
+ };
259
+ }
260
+
261
+ /**
262
+ * @returns {ArrowStyle}
263
+ */
264
+ clone() {
265
+ return new ArrowStyle(this._getCloneOptions());
266
+ }
267
+ }
268
+
269
+ export default ArrowStyle;
@@ -30,7 +30,7 @@ import ObliqueMap from '../../map/obliqueMap.js';
30
30
  * @param {GeometryType} geometryType
31
31
  * @returns {CreateFeatureSession}
32
32
  */
33
- export default function startCreateFeatureSession(app, layer, geometryType) {
33
+ function startCreateFeatureSession(app, layer, geometryType) {
34
34
  check(app, VcsApp);
35
35
  check(layer, VectorLayer);
36
36
  check(geometryType, Object.values(GeometryType));
@@ -180,3 +180,5 @@ export default function startCreateFeatureSession(app, layer, geometryType) {
180
180
  stop,
181
181
  };
182
182
  }
183
+
184
+ export default startCreateFeatureSession;
@@ -0,0 +1,315 @@
1
+ import { Circle, Fill, Style, Stroke } from 'ol/style.js';
2
+ import { getLogger } from '@vcsuite/logger';
3
+
4
+ import VcsEvent from '../../vcsEvent.js';
5
+ import { SessionType, setupInteractionChain, setupScratchLayer } from './editorSessionHelpers.js';
6
+ import SelectMultiFeatureInteraction from './interactions/selectMultiFeatureInteraction.js';
7
+ import EditFeaturesMouseOverInteraction from './interactions/editFeaturesMouseOverInteraction.js';
8
+ import createTransformationHandler from './transformation/transformationHandler.js';
9
+ import { TransformationMode } from './transformation/transformationTypes.js';
10
+ import MapInteractionController from './interactions/mapInteractionController.js';
11
+ import TranslateInteraction from './transformation/translateInteraction.js';
12
+ import RotateInteraction from './transformation/rotateInteraction.js';
13
+ import ScaleInteraction from './transformation/scaleInteraction.js';
14
+ import { createSync, obliqueGeometry } from '../../layer/vectorSymbols.js';
15
+ import ExtrudeInteraction from './transformation/extrudeInteraction.js';
16
+ import { ModificationKeyType } from '../../interaction/interactionType.js';
17
+ import ObliqueMap from '../../map/obliqueMap.js';
18
+ import { ensureFeatureAbsolute } from './editorHelpers.js';
19
+ import CesiumMap from '../../map/cesiumMap.js';
20
+ import EnsureHandlerSelectionInteraction from './interactions/ensureHandlerSelectionInteraction.js';
21
+
22
+ /**
23
+ * @typedef {EditorSession} EditFeaturesSession
24
+ * @property {SelectMultiFeatureInteraction} featureSelection
25
+ * @property {TransformationMode} mode - read only access to the current mode
26
+ * @property {function(TransformationMode):void} setMode
27
+ * @property {import("@vcmap/core").VcsEvent<TransformationMode>} modeChanged
28
+ */
29
+
30
+ /**
31
+ * Creates a selection set from the selected features. This maintains `createSync` symbol, highlight and allow picking on
32
+ * currently selected features and listens to changes until stopped.
33
+ * @param {import("@vcmap/core").VectorLayer} layer
34
+ * @param {SelectMultiFeatureInteraction} selectFeatureInteraction
35
+ * @param {import("ol/style").Style} highlightStyle
36
+ * @param {import("@vcmap/core").EventHandler} eventHandler
37
+ * @returns {function():void} un-highlight all and stop listening to changes
38
+ */
39
+ function createSelectionSet(layer, selectFeatureInteraction, highlightStyle, eventHandler) {
40
+ let currentIds = new Set();
41
+ /** @type {Map<string|number, boolean|undefined>} */
42
+ const allowPickingMap = new Map();
43
+ const modifierChangedListener = eventHandler.modifierChanged.addEventListener((mod) => { // CTRL is used to modify the current selection set, we must allow picking again so you can deselect a feature
44
+ const allowPicking = mod === ModificationKeyType.CTRL;
45
+ selectFeatureInteraction.selectedFeatures.forEach((f) => { f.set('olcs_allowPicking', allowPicking); });
46
+ });
47
+
48
+ const clearFeature = (f) => {
49
+ delete f[createSync];
50
+ const allowPicking = allowPickingMap.get(f.getId());
51
+ if (allowPicking != null) {
52
+ f.set('olcs_allowPicking', allowPicking);
53
+ } else {
54
+ f.unset('olcs_allowPicking');
55
+ }
56
+ };
57
+
58
+ const featureChangedListener = selectFeatureInteraction.featuresChanged.addEventListener((newFeatures) => {
59
+ const newIds = new Set(newFeatures.map((f) => {
60
+ const id = f.getId();
61
+ if (!allowPickingMap.has(id)) {
62
+ allowPickingMap.set(id, f.get('olcs_allowPicking'));
63
+ }
64
+ f[createSync] = true;
65
+ f.set('olcs_allowPicking', false);
66
+ return id;
67
+ }));
68
+ const idsToHighlight = [];
69
+ newIds.forEach((id) => {
70
+ if (!currentIds.has(id)) {
71
+ idsToHighlight.push(id);
72
+ }
73
+ });
74
+
75
+ const idsToUnHighlight = [];
76
+ currentIds.forEach((id) => {
77
+ if (!newIds.has(id)) {
78
+ idsToUnHighlight.push(id);
79
+ }
80
+ });
81
+
82
+ layer.featureVisibility.unHighlight(idsToUnHighlight);
83
+ layer.featureVisibility.highlight(Object.fromEntries(idsToHighlight.map(id => [id, highlightStyle])));
84
+ layer.getFeaturesById(idsToUnHighlight)
85
+ .forEach(clearFeature);
86
+
87
+ currentIds = newIds;
88
+ });
89
+
90
+ return () => {
91
+ modifierChangedListener();
92
+ featureChangedListener();
93
+ if (currentIds.size > 0) {
94
+ const idsToUnHighlight = [...currentIds];
95
+ layer.getFeaturesById(idsToUnHighlight)
96
+ .forEach(clearFeature);
97
+ layer.featureVisibility.unHighlight(idsToUnHighlight);
98
+ }
99
+ };
100
+ }
101
+
102
+ /**
103
+ * @returns {import("ol/style").Style}
104
+ */
105
+ function getDefaultHighlightStyle() {
106
+ const fill = new Fill({ color: 'rgba(76,175,80,0.2)' });
107
+ const stroke = new Stroke({ color: '#4CAF50', width: 2 });
108
+
109
+ return new Style({
110
+ fill,
111
+ stroke,
112
+ image: new Circle({
113
+ fill,
114
+ stroke,
115
+ radius: 14,
116
+ }),
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Creates an editor session to select, translate, rotate & scale the feature on a given layer
122
+ * @param {import("@vcmap/core").VcsApp} app
123
+ * @param {import("@vcmap/core").VectorLayer} layer
124
+ * @param {TransformationMode=} [initialMode=TransformationMode.TRANSLATE]
125
+ * @param {import("ol/style").Style=} [highlightStyle]
126
+ * @returns {EditFeaturesSession}
127
+ */
128
+ function startEditFeaturesSession(
129
+ app,
130
+ layer,
131
+ initialMode = TransformationMode.TRANSLATE,
132
+ highlightStyle = getDefaultHighlightStyle(),
133
+ ) {
134
+ const scratchLayer = setupScratchLayer(app.layers);
135
+ /** @type {VcsEvent<void>} */
136
+ const stopped = new VcsEvent();
137
+ const {
138
+ interactionChain,
139
+ removed: interactionRemoved,
140
+ destroy: destroyInteractionChain,
141
+ } = setupInteractionChain(app.maps.eventHandler);
142
+ const selectFeatureInteraction = new SelectMultiFeatureInteraction(layer);
143
+ const destroySelectionSet = createSelectionSet(
144
+ layer,
145
+ selectFeatureInteraction,
146
+ highlightStyle,
147
+ app.maps.eventHandler,
148
+ );
149
+ interactionChain.addInteraction(selectFeatureInteraction);
150
+ interactionChain.addInteraction(new EnsureHandlerSelectionInteraction(selectFeatureInteraction));
151
+
152
+ const mapInteractionController = new MapInteractionController();
153
+ interactionChain.addInteraction(mapInteractionController);
154
+
155
+ const mouseOverInteraction = new EditFeaturesMouseOverInteraction(
156
+ layer.name,
157
+ selectFeatureInteraction,
158
+ );
159
+ interactionChain.addInteraction(mouseOverInteraction);
160
+
161
+ let mode = initialMode;
162
+ let destroyTransformation = () => {};
163
+ const createTransformations = () => {
164
+ destroyTransformation();
165
+ const transformationHandler = createTransformationHandler(
166
+ app.maps.activeMap,
167
+ layer,
168
+ selectFeatureInteraction,
169
+ scratchLayer,
170
+ mode,
171
+ );
172
+
173
+ let interaction;
174
+ if (mode === TransformationMode.TRANSLATE) {
175
+ interaction = new TranslateInteraction(transformationHandler);
176
+ interaction.translated.addEventListener(([dx, dy, dz]) => {
177
+ transformationHandler.translate(dx, dy, dz);
178
+ selectFeatureInteraction.selectedFeatures.forEach((f) => {
179
+ const geometry = f[obliqueGeometry] ?? f.getGeometry();
180
+ geometry.applyTransform((input, output) => {
181
+ const inputLength = input.length;
182
+ for (let i = 0; i < inputLength; i += 3) {
183
+ output[i] = input[i] + dx;
184
+ output[i + 1] = input[i + 1] + dy;
185
+ output[i + 2] = input[i + 2] + dz;
186
+ }
187
+ return output;
188
+ });
189
+ });
190
+ });
191
+ } else if (mode === TransformationMode.EXTRUDE) {
192
+ interaction = new ExtrudeInteraction(transformationHandler);
193
+ interaction.extruded.addEventListener((dz) => {
194
+ selectFeatureInteraction.selectedFeatures.forEach((f) => {
195
+ ensureFeatureAbsolute(f, layer, app.maps.activeMap);
196
+ let extrudedHeight = f.get('olcs_extrudedHeight') ?? 0;
197
+ extrudedHeight += dz;
198
+ f.set('olcs_extrudedHeight', extrudedHeight);
199
+ });
200
+ });
201
+ } else if (mode === TransformationMode.ROTATE) {
202
+ interaction = new RotateInteraction(transformationHandler);
203
+ interaction.rotated.addEventListener(({ angle }) => {
204
+ const { center } = transformationHandler;
205
+ selectFeatureInteraction.selectedFeatures.forEach((f) => {
206
+ const geometry = f[obliqueGeometry] ?? f.getGeometry();
207
+ geometry.rotate(angle, center);
208
+ });
209
+ });
210
+ } else if (mode === TransformationMode.SCALE) {
211
+ interaction = new ScaleInteraction(transformationHandler);
212
+ interaction.scaled.addEventListener(([sx, sy]) => {
213
+ const { center } = transformationHandler;
214
+ selectFeatureInteraction.selectedFeatures.forEach((f) => {
215
+ const geometry = f[obliqueGeometry] ?? f.getGeometry();
216
+ geometry.scale(sx, sy, center);
217
+ });
218
+ });
219
+ } else {
220
+ throw new Error(`Unknown transformation mode ${mode}`);
221
+ }
222
+
223
+ interactionChain.addInteraction(interaction);
224
+
225
+ destroyTransformation = () => {
226
+ interactionChain.removeInteraction(interaction);
227
+ interaction.destroy();
228
+ transformationHandler.destroy();
229
+ };
230
+ };
231
+
232
+ /**
233
+ * @type {VcsEvent<TransformationMode>}
234
+ */
235
+ const modeChanged = new VcsEvent();
236
+ const setMode = (newMode) => {
237
+ if (newMode !== mode) {
238
+ if (newMode === TransformationMode.EXTRUDE && !(app.maps.activeMap instanceof CesiumMap)) {
239
+ getLogger('EditFeaturesSession').warning('Cannot set extrude mode if map is not a CesiumMap');
240
+ } else {
241
+ mode = newMode;
242
+ createTransformations();
243
+ modeChanged.raiseEvent(mode);
244
+ }
245
+ }
246
+ };
247
+ /**
248
+ * @type {ObliqueMap|null}
249
+ */
250
+ let obliqueMap = null;
251
+ /**
252
+ * @type {function():void}
253
+ */
254
+ let obliqueImageChangedListener = () => {};
255
+ const mapChanged = (map) => {
256
+ obliqueImageChangedListener();
257
+ if (map instanceof ObliqueMap) {
258
+ selectFeatureInteraction.clear();
259
+ obliqueImageChangedListener = map.imageChanged.addEventListener(() => {
260
+ selectFeatureInteraction.clear();
261
+ createTransformations();
262
+ });
263
+ obliqueMap = map;
264
+ } else {
265
+ if (obliqueMap) {
266
+ selectFeatureInteraction.clear();
267
+ }
268
+ obliqueMap = null;
269
+ obliqueImageChangedListener = () => {};
270
+ }
271
+ if (mode === TransformationMode.EXTRUDE && !(map instanceof CesiumMap)) {
272
+ setMode(TransformationMode.TRANSLATE);
273
+ } else {
274
+ createTransformations();
275
+ }
276
+ };
277
+ const mapChangedListener = app.maps.mapActivated.addEventListener(mapChanged);
278
+ mapChanged(app.maps.activeMap);
279
+
280
+ selectFeatureInteraction.featuresChanged.addEventListener((selectedFeatures) => {
281
+ if (obliqueMap) {
282
+ obliqueMap.switchEnabled = selectedFeatures.length === 0;
283
+ }
284
+ });
285
+
286
+ const stop = () => {
287
+ obliqueImageChangedListener();
288
+ if (obliqueMap) {
289
+ obliqueMap.switchEnabled = true;
290
+ }
291
+ mapChangedListener();
292
+ destroyTransformation();
293
+ destroySelectionSet();
294
+ app.layers.remove(scratchLayer);
295
+ destroyInteractionChain();
296
+ stopped.raiseEvent();
297
+ stopped.destroy();
298
+ modeChanged.destroy();
299
+ };
300
+ interactionRemoved.addEventListener(stop);
301
+
302
+ return {
303
+ type: SessionType.EDIT_FEATURES,
304
+ featureSelection: selectFeatureInteraction,
305
+ stopped,
306
+ stop,
307
+ get mode() {
308
+ return mode;
309
+ },
310
+ modeChanged,
311
+ setMode,
312
+ };
313
+ }
314
+
315
+ export default startEditFeaturesSession;
@@ -14,6 +14,7 @@ import geometryIsValid from './validateGeoemetry.js';
14
14
  import ObliqueMap from '../../map/obliqueMap.js';
15
15
  import { emptyStyle } from '../../style/styleHelpers.js';
16
16
  import MapInteractionController from './interactions/mapInteractionController.js';
17
+ import { originalStyle } from '../../layer/featureVisibility.js';
17
18
 
18
19
  /**
19
20
  * @typedef {EditorSession} EditGeometrySession
@@ -228,6 +229,7 @@ function createEditSimplePolygonInteraction(feature, scratchLayer) {
228
229
  function createEditPointInteraction(feature, scratchLayer) {
229
230
  const vertex = createVertex(feature.getGeometry().getCoordinates());
230
231
  const featureStyle = feature.getStyle();
232
+ feature[originalStyle] = featureStyle;
231
233
  feature.setStyle(emptyStyle);
232
234
  scratchLayer.addFeatures([vertex]);
233
235
  const translateVertex = new TranslateVertexInteraction();
@@ -255,7 +257,7 @@ function createEditPointInteraction(feature, scratchLayer) {
255
257
  * @param {import("@vcmap/core").VectorLayer} layer
256
258
  * @returns {EditGeometrySession}
257
259
  */
258
- export default function startEditGeometrySession(app, layer) {
260
+ function startEditGeometrySession(app, layer) {
259
261
  const {
260
262
  interactionChain,
261
263
  removed: interactionRemoved,
@@ -399,3 +401,5 @@ export default function startEditGeometrySession(app, layer) {
399
401
  stop,
400
402
  };
401
403
  }
404
+
405
+ export default startEditGeometrySession;