@vcmap/core 5.0.0-rc.27 → 5.0.0-rc.28

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 (37) hide show
  1. package/index.d.ts +273 -125
  2. package/index.js +6 -4
  3. package/package.json +1 -1
  4. package/src/category/category.js +16 -16
  5. package/src/category/categoryCollection.js +13 -13
  6. package/src/interaction/eventHandler.js +4 -4
  7. package/src/layer/featureVisibility.js +3 -4
  8. package/src/layer/globalHider.js +1 -1
  9. package/src/layer/layer.js +2 -1
  10. package/src/layer/vectorLayer.js +7 -0
  11. package/src/oblique/helpers.js +7 -9
  12. package/src/ol/feature.js +28 -0
  13. package/src/overrideClassRegistry.js +17 -17
  14. package/src/style/declarativeStyleItem.js +2 -3
  15. package/src/util/editor/editFeaturesSession.js +150 -166
  16. package/src/util/editor/editGeometrySession.js +69 -47
  17. package/src/util/editor/editorHelpers.js +3 -1
  18. package/src/util/editor/editorSessionHelpers.js +11 -3
  19. package/src/util/editor/editorSymbols.js +5 -0
  20. package/src/util/editor/interactions/editFeaturesMouseOverInteraction.js +15 -49
  21. package/src/util/editor/interactions/editGeometryMouseOverInteraction.js +16 -33
  22. package/src/util/editor/interactions/ensureHandlerSelectionInteraction.js +5 -5
  23. package/src/util/editor/interactions/selectFeatureMouseOverInteraction.js +143 -0
  24. package/src/util/editor/interactions/selectMultiFeatureInteraction.js +17 -11
  25. package/src/util/editor/interactions/selectSingleFeatureInteraction.js +27 -8
  26. package/src/util/editor/interactions/translateVertexInteraction.js +2 -3
  27. package/src/util/editor/selectFeaturesSession.js +287 -0
  28. package/src/util/editor/transformation/transformationHandler.js +4 -9
  29. package/src/util/editor/transformation/transformationTypes.js +1 -0
  30. package/src/util/featureconverter/convert.js +1 -1
  31. package/src/util/indexedCollection.js +19 -3
  32. package/src/util/layerCollection.js +4 -2
  33. package/src/util/overrideCollection.js +20 -20
  34. package/src/vcsApp.js +107 -85
  35. package/src/vcsModule.js +129 -0
  36. package/src/{vcsAppContextHelpers.js → vcsModuleHelpers.js} +5 -5
  37. package/src/context.js +0 -89
@@ -1,197 +1,193 @@
1
- import { Circle, Fill, Style, Stroke } from 'ol/style.js';
1
+ import { extend as extendExtent, createEmpty as createEmptyExtent, getCenter, isEmpty } from 'ol/extent.js';
2
2
  import { getLogger } from '@vcsuite/logger';
3
3
 
4
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';
5
+ import { setupInteractionChain, SessionType, setupScratchLayer } from './editorSessionHelpers.js';
8
6
  import createTransformationHandler from './transformation/transformationHandler.js';
9
7
  import { TransformationMode } from './transformation/transformationTypes.js';
10
8
  import MapInteractionController from './interactions/mapInteractionController.js';
11
9
  import TranslateInteraction from './transformation/translateInteraction.js';
12
10
  import RotateInteraction from './transformation/rotateInteraction.js';
13
11
  import ScaleInteraction from './transformation/scaleInteraction.js';
14
- import { createSync, obliqueGeometry } from '../../layer/vectorSymbols.js';
12
+ import { obliqueGeometry } from '../../layer/vectorSymbols.js';
15
13
  import ExtrudeInteraction from './transformation/extrudeInteraction.js';
16
- import { ModificationKeyType } from '../../interaction/interactionType.js';
17
14
  import ObliqueMap from '../../map/obliqueMap.js';
18
15
  import { ensureFeatureAbsolute } from './editorHelpers.js';
19
16
  import CesiumMap from '../../map/cesiumMap.js';
20
17
  import EnsureHandlerSelectionInteraction from './interactions/ensureHandlerSelectionInteraction.js';
18
+ import EditFeaturesMouseOverInteraction from './interactions/editFeaturesMouseOverInteraction.js';
19
+ import { ModificationKeyType } from '../../interaction/interactionType.js';
21
20
 
22
21
  /**
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
22
+ * Saves the original allowPicking settings and sets them to false if CTRL is not pressed.
23
+ * @param {import("ol").Feature} feature
24
+ * @param {Map<string|number, boolean|undefined>} allowPickingMap A map containing the original allowPicking settings for the features. Id is key, setting is value.
25
+ * @param {ModificationKeyType} currentModificationKey The modification key that is currently pressed.
28
26
  */
27
+ function setAllowPicking(feature, allowPickingMap, currentModificationKey) {
28
+ if (!allowPickingMap.has(feature.getId())) {
29
+ allowPickingMap.set(feature.getId(), feature.get('olcs_allowPicking'));
30
+ }
31
+ if (currentModificationKey !== ModificationKeyType.CTRL) {
32
+ feature.set('olcs_allowPicking', false);
33
+ }
34
+ }
29
35
 
30
36
  /**
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
37
+ * Restores the original allowPicking settings for the feature.
38
+ * @param {import("ol").Feature} feature
39
+ * @param {Map<string|number, boolean|undefined>} allowPickingMap A map containing the original allowPicking settings for the features. Id is key, setting is value.
38
40
  */
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
- };
41
+ function clearAllowPicking(feature, allowPickingMap) {
42
+ const allowPicking = allowPickingMap.get(feature.getId());
43
+ if (allowPicking != null) {
44
+ feature.set('olcs_allowPicking', allowPicking);
45
+ } else {
46
+ feature.unset('olcs_allowPicking');
47
+ }
100
48
  }
101
49
 
102
50
  /**
103
- * @returns {import("ol/style").Style}
51
+ * @typedef {EditorSession} EditFeaturesSession
52
+ * @property {TransformationMode} mode - read only access to the current mode
53
+ * @property {function(number):void} rotate - Function for rotating features. Takes angle in radians as parameter.
54
+ * @property {function(number, number, number):void} translate - Function for translating features. Takes Δx, Δy, Δz as parameters.
55
+ * @property {function(number, number):void} scale - Function for scaling features. Takes sx, sy as parameters.
56
+ * @property {function(TransformationMode):void} setMode
57
+ * @property {import("@vcmap/core").VcsEvent<TransformationMode>} modeChanged
58
+ * @property {function(Array<import("ol").Feature>):void} setFeatures - Sets the features for the edit session.
59
+ * @property {Array<import("ol").Feature>} features - Gets the features of the edit session.
104
60
  */
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
61
 
120
62
  /**
121
63
  * Creates an editor session to select, translate, rotate & scale the feature on a given layer
122
64
  * @param {import("@vcmap/core").VcsApp} app
123
65
  * @param {import("@vcmap/core").VectorLayer} layer
66
+ * @param {string} [interactionId] id for registering mutliple exclusive interaction. Needed to run a selection session at the same time as a edit features session.
124
67
  * @param {TransformationMode=} [initialMode=TransformationMode.TRANSLATE]
125
- * @param {import("ol/style").Style=} [highlightStyle]
126
68
  * @returns {EditFeaturesSession}
127
69
  */
128
70
  function startEditFeaturesSession(
129
71
  app,
130
72
  layer,
73
+ interactionId,
131
74
  initialMode = TransformationMode.TRANSLATE,
132
- highlightStyle = getDefaultHighlightStyle(),
75
+
133
76
  ) {
134
- const scratchLayer = setupScratchLayer(app.layers);
135
- /** @type {VcsEvent<void>} */
77
+ /**
78
+ * @type {VcsEvent<void>}
79
+ */
136
80
  const stopped = new VcsEvent();
81
+
82
+ /**
83
+ * The features that are set for the edit session.
84
+ * @type {Array<import("ol").Feature>}
85
+ */
86
+ const currentFeatures = [];
87
+
88
+ // The allow picking prop needs to be set false for the selected features to make sure that the transformation handler can always be selected.
89
+ /**
90
+ * @type {Map<string|number, boolean|undefined>}
91
+ */
92
+ const allowPickingMap = new Map();
93
+ let modificationKey;
94
+ /** Callback to remove the modifier changed listener. */
95
+ const modifierChangedListener = app.maps.eventHandler.modifierChanged.addEventListener((mod) => { // CTRL is used to modify the current selection set, we must allow picking again so you can deselect a feature
96
+ modificationKey = mod;
97
+ const allowPicking = modificationKey === ModificationKeyType.CTRL;
98
+ currentFeatures.forEach((feature) => { feature.set('olcs_allowPicking', allowPicking); });
99
+ });
100
+
101
+ const scratchLayer = setupScratchLayer(app.layers);
102
+
137
103
  const {
138
104
  interactionChain,
139
105
  removed: interactionRemoved,
140
106
  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));
107
+ } = setupInteractionChain(app.maps.eventHandler, interactionId);
108
+
109
+ const mouseOverInteraction = new EditFeaturesMouseOverInteraction();
110
+ interactionChain.addInteraction(mouseOverInteraction);
111
+
112
+ interactionChain.addInteraction(new EnsureHandlerSelectionInteraction(currentFeatures));
151
113
 
152
114
  const mapInteractionController = new MapInteractionController();
153
115
  interactionChain.addInteraction(mapInteractionController);
154
116
 
155
- const mouseOverInteraction = new EditFeaturesMouseOverInteraction(
156
- layer.name,
157
- selectFeatureInteraction,
158
- );
159
- interactionChain.addInteraction(mouseOverInteraction);
160
-
161
117
  let mode = initialMode;
162
118
  let destroyTransformation = () => {};
119
+ let transformationHandler;
120
+ const translate = (dx, dy, dz) => {
121
+ transformationHandler?.translate?.(dx, dy, dz);
122
+ currentFeatures.forEach((f) => {
123
+ const geometry = f[obliqueGeometry] ?? f.getGeometry(); // XXX wont work in oblqiue
124
+ geometry.applyTransform((input, output) => {
125
+ const inputLength = input.length;
126
+ for (let i = 0; i < inputLength; i += 3) {
127
+ output[i] = input[i] + dx;
128
+ output[i + 1] = input[i + 1] + dy;
129
+ output[i + 2] = input[i + 2] + dz;
130
+ }
131
+ return output;
132
+ });
133
+ });
134
+ };
135
+
136
+ const rotate = (angle) => {
137
+ let center = transformationHandler?.center;
138
+ if (!center) {
139
+ const extent = createEmptyExtent();
140
+ currentFeatures.forEach((f) => {
141
+ extendExtent(extent, f.getGeometry().getExtent()); // XXX wont work in oblqiue
142
+ });
143
+ if (!isEmpty(extent)) {
144
+ center = getCenter(extent);
145
+ }
146
+ }
147
+ currentFeatures.forEach((f) => {
148
+ const geometry = f[obliqueGeometry] ?? f.getGeometry(); // XXX wont work in oblqiue
149
+ geometry.rotate(angle, center);
150
+ });
151
+ };
152
+
153
+ const scale = (sx, sy) => {
154
+ let center = transformationHandler?.center;
155
+ if (!center) { // XXX copy paste
156
+ const extent = createEmptyExtent();
157
+ currentFeatures.forEach((f) => {
158
+ extendExtent(extent, f.getGeometry().getExtent());
159
+ });
160
+ if (!isEmpty(extent)) {
161
+ center = getCenter(extent);
162
+ }
163
+ }
164
+ currentFeatures.forEach((f) => {
165
+ const geometry = f[obliqueGeometry] ?? f.getGeometry();
166
+ geometry.scale(sx, sy, center);
167
+ });
168
+ };
169
+
163
170
  const createTransformations = () => {
164
171
  destroyTransformation();
165
- const transformationHandler = createTransformationHandler(
172
+
173
+ transformationHandler = createTransformationHandler(
166
174
  app.maps.activeMap,
167
175
  layer,
168
- selectFeatureInteraction,
169
176
  scratchLayer,
170
177
  mode,
171
178
  );
179
+ transformationHandler.setFeatures(currentFeatures);
172
180
 
173
181
  let interaction;
174
182
  if (mode === TransformationMode.TRANSLATE) {
175
183
  interaction = new TranslateInteraction(transformationHandler);
176
184
  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
- });
185
+ translate(dx, dy, dz);
190
186
  });
191
187
  } else if (mode === TransformationMode.EXTRUDE) {
192
188
  interaction = new ExtrudeInteraction(transformationHandler);
193
189
  interaction.extruded.addEventListener((dz) => {
194
- selectFeatureInteraction.selectedFeatures.forEach((f) => {
190
+ currentFeatures.forEach((f) => {
195
191
  ensureFeatureAbsolute(f, layer, app.maps.activeMap);
196
192
  let extrudedHeight = f.get('olcs_extrudedHeight') ?? 0;
197
193
  extrudedHeight += dz;
@@ -201,20 +197,12 @@ function startEditFeaturesSession(
201
197
  } else if (mode === TransformationMode.ROTATE) {
202
198
  interaction = new RotateInteraction(transformationHandler);
203
199
  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
- });
200
+ rotate(angle);
209
201
  });
210
202
  } else if (mode === TransformationMode.SCALE) {
211
203
  interaction = new ScaleInteraction(transformationHandler);
212
204
  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
- });
205
+ scale(sx, sy);
218
206
  });
219
207
  } else {
220
208
  throw new Error(`Unknown transformation mode ${mode}`);
@@ -225,7 +213,8 @@ function startEditFeaturesSession(
225
213
  destroyTransformation = () => {
226
214
  interactionChain.removeInteraction(interaction);
227
215
  interaction.destroy();
228
- transformationHandler.destroy();
216
+ transformationHandler?.destroy();
217
+ transformationHandler = null;
229
218
  };
230
219
  };
231
220
 
@@ -244,10 +233,6 @@ function startEditFeaturesSession(
244
233
  }
245
234
  }
246
235
  };
247
- /**
248
- * @type {ObliqueMap|null}
249
- */
250
- let obliqueMap = null;
251
236
  /**
252
237
  * @type {function():void}
253
238
  */
@@ -255,17 +240,10 @@ function startEditFeaturesSession(
255
240
  const mapChanged = (map) => {
256
241
  obliqueImageChangedListener();
257
242
  if (map instanceof ObliqueMap) {
258
- selectFeatureInteraction.clear();
259
243
  obliqueImageChangedListener = map.imageChanged.addEventListener(() => {
260
- selectFeatureInteraction.clear();
261
244
  createTransformations();
262
245
  });
263
- obliqueMap = map;
264
246
  } else {
265
- if (obliqueMap) {
266
- selectFeatureInteraction.clear();
267
- }
268
- obliqueMap = null;
269
247
  obliqueImageChangedListener = () => {};
270
248
  }
271
249
  if (mode === TransformationMode.EXTRUDE && !(map instanceof CesiumMap)) {
@@ -277,31 +255,24 @@ function startEditFeaturesSession(
277
255
  const mapChangedListener = app.maps.mapActivated.addEventListener(mapChanged);
278
256
  mapChanged(app.maps.activeMap);
279
257
 
280
- selectFeatureInteraction.featuresChanged.addEventListener((selectedFeatures) => {
281
- if (obliqueMap) {
282
- obliqueMap.switchEnabled = selectedFeatures.length === 0;
283
- }
284
- });
285
-
286
258
  const stop = () => {
259
+ destroyTransformation();
260
+ destroyInteractionChain();
287
261
  obliqueImageChangedListener();
288
- if (obliqueMap) {
289
- obliqueMap.switchEnabled = true;
290
- }
291
262
  mapChangedListener();
292
- destroyTransformation();
293
- destroySelectionSet();
263
+ modifierChangedListener();
264
+ currentFeatures.forEach(feature => clearAllowPicking(feature, allowPickingMap));
265
+ allowPickingMap.clear();
294
266
  app.layers.remove(scratchLayer);
295
- destroyInteractionChain();
267
+ modeChanged.destroy();
296
268
  stopped.raiseEvent();
297
269
  stopped.destroy();
298
- modeChanged.destroy();
299
270
  };
271
+
300
272
  interactionRemoved.addEventListener(stop);
301
273
 
302
274
  return {
303
275
  type: SessionType.EDIT_FEATURES,
304
- featureSelection: selectFeatureInteraction,
305
276
  stopped,
306
277
  stop,
307
278
  get mode() {
@@ -309,6 +280,19 @@ function startEditFeaturesSession(
309
280
  },
310
281
  modeChanged,
311
282
  setMode,
283
+ rotate,
284
+ translate,
285
+ scale,
286
+ setFeatures(features) {
287
+ currentFeatures.forEach(feature => clearAllowPicking(feature, allowPickingMap));
288
+ currentFeatures.length = 0;
289
+ currentFeatures.push(...features);
290
+ currentFeatures.forEach(feature => setAllowPicking(feature, allowPickingMap, modificationKey));
291
+ transformationHandler?.setFeatures(features);
292
+ },
293
+ get features() {
294
+ return currentFeatures;
295
+ },
312
296
  };
313
297
  }
314
298