@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
@@ -0,0 +1,143 @@
1
+ import AbstractInteraction from '../../../interaction/abstractInteraction.js';
2
+ import { EventType, ModificationKeyType } from '../../../interaction/interactionType.js';
3
+ import SelectMultiFeatureInteraction from './selectMultiFeatureInteraction.js';
4
+ import SelectSingleFeatureInteraction from './selectSingleFeatureInteraction.js';
5
+ import { cursorMap } from './editGeometryMouseOverInteraction.js';
6
+ import { vcsLayerName } from '../../../layer/layerSymbols.js';
7
+ import { mouseOverSymbol } from '../editorSymbols.js';
8
+
9
+ /**
10
+ * Enumeration of editor selection modes.
11
+ * @enum {string}
12
+ * @property {string} SINGLE
13
+ * @property {string} MULTI
14
+ */
15
+ export const SelectionMode = {
16
+ SINGLE: 'single',
17
+ MULTI: 'multi',
18
+ };
19
+
20
+ /**
21
+ * A class to handle mouse over effects on features for select sessions.
22
+ * @class
23
+ * @extends {AbstractInteraction}
24
+ */
25
+ class SelectFeatureMouseOverInteraction extends AbstractInteraction {
26
+ /**
27
+ * @param {string} layerName Name of the layer for which the mouse over effect should accur.
28
+ * @param {SelectSingleFeatureInteraction | SelectMultiFeatureInteraction} selectFeatureInteraction The select feature interaction that handles the feature selection. Supports both, single and multi selection.
29
+ */
30
+ constructor(layerName, selectFeatureInteraction) {
31
+ let modkeys;
32
+ let selectionMode;
33
+ if (selectFeatureInteraction instanceof SelectSingleFeatureInteraction) {
34
+ modkeys = ModificationKeyType.NONE;
35
+ selectionMode = SelectionMode.SINGLE;
36
+ } else if (selectFeatureInteraction instanceof SelectMultiFeatureInteraction) {
37
+ modkeys = ModificationKeyType.NONE | ModificationKeyType.CTRL;
38
+ selectionMode = SelectionMode.MULTI;
39
+ } else {
40
+ throw new Error('This interaction is not supported');
41
+ }
42
+
43
+ super(EventType.MOVE, modkeys);
44
+ /** @type {SelectSingleFeatureInteraction | SelectMultiFeatureInteraction} */
45
+ this._selectFeatureInteraction = selectFeatureInteraction;
46
+ /** @type {SelectionMode} */
47
+ this.selectionMode = selectionMode;
48
+ /**
49
+ * The feature that is currently hovered and belongs to the layer with the layerName.
50
+ * @type {import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature|null}
51
+ * @private
52
+ */
53
+ this._currentFeature = null;
54
+ /**
55
+ * The layer name to react to
56
+ * @type {string}
57
+ */
58
+ this.layerName = layerName;
59
+ /**
60
+ * @type {CSSStyleDeclaration}
61
+ * @private
62
+ */
63
+ this.cursorStyle = document.body.style;
64
+ }
65
+
66
+ /**
67
+ * @inheritDoc
68
+ * @param {InteractionEvent} event
69
+ * @returns {Promise<InteractionEvent>}
70
+ */
71
+ async pipe(event) {
72
+ if (
73
+ event.feature &&
74
+ (event.feature[vcsLayerName] === this.layerName)
75
+ ) {
76
+ this._currentFeature = /** @type {import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature} */
77
+ (event.feature);
78
+ } else {
79
+ this._currentFeature = null;
80
+ }
81
+ this._evaluate(event.key);
82
+ return event;
83
+ }
84
+
85
+ /**
86
+ * @inheritDoc
87
+ * @param {ModificationKeyType} modifier
88
+ */
89
+ modifierChanged(modifier) {
90
+ this._evaluate(modifier);
91
+ }
92
+
93
+ /**
94
+ * @inheritDoc
95
+ * @param {(boolean|number)=} active
96
+ */
97
+ setActive(active) {
98
+ super.setActive(active);
99
+ this.reset();
100
+ }
101
+
102
+ /**
103
+ * Reset the cursorStyle to auto
104
+ */
105
+ reset() {
106
+ if (this.cursorStyle && this.cursorStyle.cursor) {
107
+ this.cursorStyle.cursor = cursorMap.auto;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * @param {ModificationKeyType} modifier
113
+ * @private
114
+ */
115
+ _evaluate(modifier) {
116
+ if (this._currentFeature) {
117
+ const isCtrlPressed = this.selectionMode === SelectionMode.MULTI && (modifier & ModificationKeyType.CTRL);
118
+ const isSelected = this._selectFeatureInteraction.hasFeatureId(this._currentFeature.getId());
119
+
120
+ if (isCtrlPressed) {
121
+ this.cursorStyle.cursor = isSelected ? cursorMap.removeFromSelection : cursorMap.addToSelection;
122
+ } else {
123
+ this.cursorStyle.cursor = cursorMap.select;
124
+ }
125
+
126
+ this.cursorStyle[mouseOverSymbol] = this.id;
127
+ } else if (this.cursorStyle?.[mouseOverSymbol] === this.id) {
128
+ this.cursorStyle.cursor = cursorMap.auto;
129
+ delete this.cursorStyle[mouseOverSymbol];
130
+ }
131
+ }
132
+
133
+ /**
134
+ * @inheritDoc
135
+ */
136
+ destroy() {
137
+ this.reset();
138
+ this.cursorStyle = null;
139
+ super.destroy();
140
+ }
141
+ }
142
+
143
+ export default SelectFeatureMouseOverInteraction;
@@ -12,6 +12,7 @@ import { isTiledFeature } from '../../../layer/featureStoreLayer.js';
12
12
  * FeatureStore features will be converted to their dynamic state on selection.
13
13
  * @class
14
14
  * @extends {AbstractInteraction}
15
+ * @implements {SelectFeatureInteraction}
15
16
  */
16
17
  class SelectMultiFeatureInteraction extends AbstractInteraction {
17
18
  /**
@@ -47,16 +48,17 @@ class SelectMultiFeatureInteraction extends AbstractInteraction {
47
48
  /**
48
49
  * @returns {Array<import("ol").Feature>}
49
50
  */
50
- get selectedFeatures() {
51
+ get selected() {
51
52
  return [...this._selectedFeatures.values()];
52
53
  }
53
54
 
54
55
  /**
55
- * @param {string} featureId
56
+ * Checks if a feature with a spicific id is selected.
57
+ * @param {string | number} id
56
58
  * @returns {boolean}
57
59
  */
58
- hasFeatureId(featureId) {
59
- return this._selectedFeatures.has(featureId);
60
+ hasFeatureId(id) {
61
+ return this._selectedFeatures.has(id);
60
62
  }
61
63
 
62
64
  /**
@@ -72,9 +74,12 @@ class SelectMultiFeatureInteraction extends AbstractInteraction {
72
74
  if (event.key & ModificationKeyType.CTRL) {
73
75
  event.stopPropagation = true;
74
76
  await this._modifySelectionSet(event.feature);
75
- } else if (!this._selectedFeatures.has(event.feature.getId())) {
77
+ } else if (
78
+ !this._selectedFeatures.has(event.feature.getId()) ||
79
+ (this._selectedFeatures.has(event.feature.getId()) && this._selectedFeatures.size > 1)
80
+ ) {
76
81
  event.stopPropagation = true;
77
- await this.setSelectionSet([event.feature]);
82
+ await this.setSelected([event.feature]);
78
83
  }
79
84
  } else if (!(event.key & ModificationKeyType.CTRL)) {
80
85
  this.clear();
@@ -83,12 +88,13 @@ class SelectMultiFeatureInteraction extends AbstractInteraction {
83
88
  }
84
89
 
85
90
  /**
86
- * @param {Array<import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature|import("@vcmap-cesium/engine").Entity>} features
91
+ * @param {Array<import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature|import("@vcmap-cesium/engine").Entity> | import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature|import("@vcmap-cesium/engine").Entity} features
87
92
  * @returns {Promise<void>}
88
93
  */
89
- async setSelectionSet(features) {
94
+ async setSelected(features) {
90
95
  this._selectedFeatures.clear();
91
- const olFeatures = await Promise.all(features.map((f) => {
96
+ const featureArray = Array.isArray(features) ? features : [features];
97
+ const olFeatures = await Promise.all(featureArray.map((f) => {
92
98
  if (f[isTiledFeature]) {
93
99
  return /** @type {import("@vcmap/core").FeatureStoreLayer} */ (this._layer)
94
100
  .switchStaticFeatureToDynamic(f.getId());
@@ -99,7 +105,7 @@ class SelectMultiFeatureInteraction extends AbstractInteraction {
99
105
  this._selectedFeatures.set(f.getId(), f);
100
106
  });
101
107
 
102
- this._featuresChanged.raiseEvent(this.selectedFeatures);
108
+ this._featuresChanged.raiseEvent(this.selected);
103
109
  }
104
110
 
105
111
  /**
@@ -120,7 +126,7 @@ class SelectMultiFeatureInteraction extends AbstractInteraction {
120
126
  this._selectedFeatures.set(id, /** @type {import("ol").Feature} */ (olFeature));
121
127
  }
122
128
 
123
- this._featuresChanged.raiseEvent(this.selectedFeatures);
129
+ this._featuresChanged.raiseEvent(this.selected);
124
130
  }
125
131
 
126
132
  /**
@@ -9,6 +9,7 @@ import { isTiledFeature } from '../../../layer/featureStoreLayer.js';
9
9
  * Static FeatureStore features will be converted into their dynamic form
10
10
  * @class
11
11
  * @extends {AbstractInteraction}
12
+ * @implements {SelectFeatureInteraction}
12
13
  */
13
14
  class SelectSingleFeatureInteraction extends AbstractInteraction {
14
15
  /**
@@ -35,10 +36,10 @@ class SelectSingleFeatureInteraction extends AbstractInteraction {
35
36
  }
36
37
 
37
38
  /**
38
- * @returns {import("ol").Feature|null}
39
+ * @returns {Array<import("ol").Feature>}
39
40
  */
40
- get selectedFeature() {
41
- return this._selectedFeature;
41
+ get selected() {
42
+ return this._selectedFeature ? [this._selectedFeature] : [];
42
43
  }
43
44
 
44
45
  /**
@@ -53,7 +54,7 @@ class SelectSingleFeatureInteraction extends AbstractInteraction {
53
54
  ) {
54
55
  if (!(this._selectedFeature && event.feature.getId() === this._selectedFeature.getId())) {
55
56
  event.stopPropagation = true;
56
- await this.selectFeature(
57
+ await this.setSelected(
57
58
  /** @type {import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature} */
58
59
  (event.feature),
59
60
  );
@@ -67,20 +68,29 @@ class SelectSingleFeatureInteraction extends AbstractInteraction {
67
68
  /**
68
69
  * Selects the given feature. if passed in a tiled feature store feature, it will be converted. Do not pass in uneditable features (feature which do not
69
70
  * belong to the layer for which this interaction was created)
70
- * @param {import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature} feature
71
+ * @param {Array<import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature|import("@vcmap-cesium/engine").Entity> | import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature|import("@vcmap-cesium/engine").Entity} feature
71
72
  * @returns {Promise<void>}
72
73
  */
73
- async selectFeature(feature) {
74
- let olFeature = feature;
74
+ async setSelected(feature) {
75
+ let olFeature = Array.isArray(feature) ? feature[0] : feature;
75
76
  if (feature[isTiledFeature]) {
76
77
  olFeature = await /** @type {import("@vcmap/core").FeatureStoreLayer} */ (this._layer)
77
- .switchStaticFeatureToDynamic(feature.getId());
78
+ .switchStaticFeatureToDynamic(olFeature.getId());
78
79
  }
79
80
 
80
81
  this._selectedFeature = /** @type {import("ol").Feature} */ (olFeature);
81
82
  this.featureChanged.raiseEvent(this._selectedFeature);
82
83
  }
83
84
 
85
+ /**
86
+ * Checks if a feature with a spicific id is selected.
87
+ * @param {string | number} id
88
+ * @returns {boolean}
89
+ */
90
+ hasFeatureId(id) {
91
+ return this._selectedFeature?.getId() === id;
92
+ }
93
+
84
94
  /**
85
95
  * Clears the current selection, if there is one.
86
96
  */
@@ -90,6 +100,15 @@ class SelectSingleFeatureInteraction extends AbstractInteraction {
90
100
  this.featureChanged.raiseEvent(null);
91
101
  }
92
102
  }
103
+
104
+ /**
105
+ * @inheritDoc
106
+ */
107
+ destroy() {
108
+ this._selectedFeature = null;
109
+ this.featureChanged.destroy();
110
+ super.destroy();
111
+ }
93
112
  }
94
113
 
95
114
  export default SelectSingleFeatureInteraction;
@@ -1,7 +1,6 @@
1
1
  import AbstractInteraction from '../../../interaction/abstractInteraction.js';
2
2
  import { EventType } from '../../../interaction/interactionType.js';
3
3
  import { vertexSymbol } from '../editorSymbols.js';
4
- import { emptyStyle } from '../../../style/styleHelpers.js';
5
4
  import VcsEvent from '../../../vcsEvent.js';
6
5
 
7
6
  /**
@@ -36,7 +35,7 @@ class TranslateVertexInteraction extends AbstractInteraction {
36
35
  this.vertexChanged.raiseEvent(this._vertex);
37
36
 
38
37
  if (event.type & EventType.DRAGEND) {
39
- this._vertex.setStyle(null);
38
+ this._vertex.unset('olcs_allowPicking');
40
39
  this._vertex = null;
41
40
  }
42
41
  event.stopPropagation = true;
@@ -46,7 +45,7 @@ class TranslateVertexInteraction extends AbstractInteraction {
46
45
  event.feature[vertexSymbol]
47
46
  ) {
48
47
  this._vertex = /** @type {Vertex} */ (event.feature);
49
- this._vertex.setStyle(emptyStyle);
48
+ this._vertex.set('olcs_allowPicking', false);
50
49
  event.stopPropagation = true;
51
50
  }
52
51
  return event;
@@ -0,0 +1,287 @@
1
+ import { check } from '@vcsuite/check';
2
+ import { Circle, Fill, Style, Stroke } from 'ol/style.js';
3
+ import { createSync } from '../../layer/vectorSymbols.js';
4
+ import { SessionType, setupInteractionChain } from './editorSessionHelpers.js';
5
+ import SelectSingleFeatureInteraction from './interactions/selectSingleFeatureInteraction.js';
6
+ import SelectMultiFeatureInteraction from './interactions/selectMultiFeatureInteraction.js';
7
+ import SelectFeatureMouseOverInteraction, { SelectionMode } from './interactions/selectFeatureMouseOverInteraction.js';
8
+ import VcsEvent from '../../vcsEvent.js';
9
+ import ObliqueMap from '../../map/obliqueMap.js';
10
+ import { vcsLayerName } from '../../layer/layerSymbols.js';
11
+
12
+ /**
13
+ * @typedef {Object} SelectionHighlightManager
14
+ * @property {Array<import("ol").Feature>} highlightedFeatures Currently highlighted features.
15
+ * @property {function(Array<import("ol").Feature>):void} update Sets the new features to be highlighted. All currently highlighted features that are not part of the new features are unhighlighted. Features that are not on the highlight layer are ignored.
16
+ * @property {function():void} destroy Stops all listeners, unhighlights all features.
17
+ */
18
+
19
+ /**
20
+ * Creates the selection highlight manager. By calling update features can be highlighted.
21
+ * @param {import("@vcmap/core").VectorLayer} layer The layer of the features to be highlighted.
22
+ * @param {import("ol/style").Style} highlightStyle Highlight style for the highlighted features.
23
+ * @returns {SelectionHighlightManager} Object that manages highlighting and allowPicking property of features. Has own state for selected features.
24
+ */
25
+ function createHighlightManager(layer, highlightStyle) {
26
+ const currentFeaturesMap = new Map();
27
+
28
+ /**
29
+ * Sets the new features to be highlighted. All currently highlighted features that are not part of the new features are unhighlighted.
30
+ * @param {Array<import("ol").Feature>} newFeatures Features to be highlighted.
31
+ */
32
+ const update = (newFeatures) => {
33
+ const newIds = new Set(newFeatures.map((f) => {
34
+ f[createSync] = true;
35
+ return f.getId();
36
+ }));
37
+ const idsToHighlight = [];
38
+ newIds.forEach((id) => {
39
+ if (!currentFeaturesMap.has(id)) {
40
+ idsToHighlight.push(id);
41
+ }
42
+ });
43
+
44
+ const idsToUnHighlight = [];
45
+ currentFeaturesMap.forEach((feature, id) => {
46
+ if (!newIds.has(id)) {
47
+ idsToUnHighlight.push(id);
48
+ }
49
+ });
50
+
51
+ layer.featureVisibility.unHighlight(idsToUnHighlight);
52
+ layer.featureVisibility.highlight(Object.fromEntries(idsToHighlight.map(id => [id, highlightStyle])));
53
+ layer.getFeaturesById(idsToUnHighlight).forEach(feature => delete feature[createSync]);
54
+
55
+ currentFeaturesMap.clear();
56
+ newFeatures.forEach(feature => currentFeaturesMap.set(feature.getId(), feature));
57
+ };
58
+
59
+ return {
60
+ get highlightedFeatures() {
61
+ return [...currentFeaturesMap.values()];
62
+ },
63
+ update(newFeatures) {
64
+ const featuresOnHighlightLayer = newFeatures.filter(feature => feature[vcsLayerName] === layer.name);
65
+ update(featuresOnHighlightLayer);
66
+ },
67
+ destroy() {
68
+ if (currentFeaturesMap.size > 0) {
69
+ const idsToUnHighlight = [...currentFeaturesMap.keys()];
70
+ layer.featureVisibility.unHighlight(idsToUnHighlight);
71
+ layer.getFeaturesById(idsToUnHighlight).forEach(feature => delete feature[createSync]);
72
+ currentFeaturesMap.clear();
73
+ }
74
+ },
75
+ };
76
+ }
77
+
78
+ /**
79
+ * @returns {import("ol/style").Style}
80
+ */
81
+ export function getDefaultHighlightStyle() {
82
+ const fill = new Fill({ color: 'rgba(76,175,80,0.2)' });
83
+ const stroke = new Stroke({ color: '#4CAF50', width: 2 });
84
+
85
+ return new Style({
86
+ fill,
87
+ stroke,
88
+ image: new Circle({
89
+ fill,
90
+ stroke,
91
+ radius: 14,
92
+ }),
93
+ });
94
+ }
95
+
96
+ /**
97
+ * @typedef {EditorSession} SelectFeaturesSession
98
+ * @property {Array<import("ol").Feature>} currentFeatures
99
+ * @property {import("ol").Feature} firstFeature
100
+ * @property {VcsEvent<Array<import("ol").Feature>>} featuresChanged
101
+ * @property {SelectionMode} mode
102
+ * @property {function(SelectionMode):void} setMode Sets the selection mode. Raises modeChanged event, throws error when invalid selection mode input.
103
+ * @property {VcsEvent<SelectionMode>} modeChanged
104
+ * @property {function(Array<import("ol").Feature>): Promise<void>} setCurrentFeatures Promise that resolves when features are set.
105
+ * @property {function(): void} clearSelection Deselects all features.
106
+ */
107
+
108
+ /**
109
+ * @param {import("@vcmap/core").VcsApp} app
110
+ * @param {import("@vcmap/core").VectorLayer} layer
111
+ * @param {string=} [interactionId] id for registering mutliple exclusive interaction.
112
+ * @param {SelectionMode=} [initialMode=SelectionMode.MULTI]
113
+ * @param {import("ol/style").Style=} [highlightStyle]
114
+ * @returns {SelectFeaturesSession}
115
+ */
116
+ function startSelectFeaturesSession(
117
+ app,
118
+ layer,
119
+ interactionId,
120
+ initialMode = SelectionMode.MULTI,
121
+ highlightStyle = getDefaultHighlightStyle(),
122
+ ) {
123
+ /** @type {VcsEvent<void>} */
124
+ const stopped = new VcsEvent();
125
+ /** @type {VcsEvent<SelectionMode>} */
126
+ const modeChanged = new VcsEvent();
127
+ /** @type {VcsEvent<Array<import("ol").Feature>>} */
128
+ const featuresChanged = new VcsEvent();
129
+ /** @type {SelectSingleFeatureInteraction | SelectMultiFeatureInteraction | null} */
130
+ let currentSelectInteraction = null;
131
+ /** @type {SelectFeatureMouseOverInteraction} */
132
+ let mouseOverInteraction = null;
133
+ /** @type {SelectionMode} */
134
+ let currentSelectionMode = null;
135
+ /** @type {ObliqueMap|null} */
136
+ let obliqueMap = null;
137
+
138
+ const {
139
+ interactionChain,
140
+ removed: interactionRemoved,
141
+ destroy: destroyInteractionChain,
142
+ } = setupInteractionChain(app.maps.eventHandler, interactionId);
143
+
144
+ const highlightManager = createHighlightManager(
145
+ layer, highlightStyle,
146
+ );
147
+
148
+ let interactionListeners = [];
149
+ function destroyCurrentSelectInteraction() {
150
+ if (currentSelectInteraction) {
151
+ if (currentSelectInteraction) {
152
+ interactionChain.removeInteraction(currentSelectInteraction);
153
+ currentSelectInteraction.destroy();
154
+ currentSelectInteraction = null;
155
+ }
156
+ if (mouseOverInteraction) {
157
+ interactionChain.removeInteraction(mouseOverInteraction);
158
+ mouseOverInteraction.destroy();
159
+ mouseOverInteraction = null;
160
+ }
161
+ }
162
+ interactionListeners.forEach((cb) => { cb(); });
163
+ interactionListeners = [];
164
+ }
165
+
166
+ /**
167
+ * Destroys current selection interaction and creates new one. Sets all highlighted features as selected. If previous mode was multi and new mode is single, only first feature is selected and highlighted.
168
+ * @param {SelectionMode} newMode Either single or multi selection mode.
169
+ * @returns {void}
170
+ */
171
+ function createSelectInteraction(newMode) {
172
+ if (newMode === currentSelectionMode) {
173
+ return;
174
+ }
175
+
176
+ destroyCurrentSelectInteraction();
177
+ if (newMode === SelectionMode.SINGLE) {
178
+ currentSelectInteraction = new SelectSingleFeatureInteraction(layer);
179
+
180
+ interactionListeners.push(
181
+ currentSelectInteraction.featureChanged.addEventListener((feature) => {
182
+ const featureArray = feature ? [feature] : [];
183
+ highlightManager.update(featureArray);
184
+ featuresChanged.raiseEvent(featureArray);
185
+ if (obliqueMap) {
186
+ obliqueMap.switchEnabled = !feature;
187
+ }
188
+ }),
189
+ );
190
+ } else if (newMode === SelectionMode.MULTI) {
191
+ currentSelectInteraction = new SelectMultiFeatureInteraction(layer);
192
+ interactionListeners.push(
193
+ currentSelectInteraction.featuresChanged.addEventListener((features) => {
194
+ highlightManager.update(features);
195
+ featuresChanged.raiseEvent(features);
196
+ if (obliqueMap) {
197
+ obliqueMap.switchEnabled = features.length === 0;
198
+ }
199
+ }),
200
+ );
201
+ }
202
+ const { highlightedFeatures } = highlightManager;
203
+ if (highlightedFeatures) {
204
+ // if single select interaction, only one features is set. This triggers the event listener which ensures that only this feature is highlighted.
205
+ currentSelectInteraction.setSelected(highlightedFeatures);
206
+ }
207
+ currentSelectionMode = newMode;
208
+ interactionChain.addInteraction(currentSelectInteraction);
209
+ mouseOverInteraction = new SelectFeatureMouseOverInteraction(
210
+ layer.name,
211
+ currentSelectInteraction,
212
+ );
213
+ interactionChain.addInteraction(mouseOverInteraction);
214
+ }
215
+
216
+ createSelectInteraction(initialMode);
217
+
218
+ /**
219
+ * @type {function():void}
220
+ */
221
+ let obliqueImageChangedListener = () => {};
222
+ const mapChanged = (map) => {
223
+ obliqueImageChangedListener();
224
+ if (map instanceof ObliqueMap) {
225
+ currentSelectInteraction.clear();
226
+ obliqueImageChangedListener = map.imageChanged.addEventListener(() => {
227
+ currentSelectInteraction.clear();
228
+ });
229
+ obliqueMap = map;
230
+ } else {
231
+ if (obliqueMap) {
232
+ currentSelectInteraction.clear();
233
+ }
234
+ obliqueMap = null;
235
+ obliqueImageChangedListener = () => {};
236
+ }
237
+ };
238
+ const mapChangedListener = app.maps.mapActivated.addEventListener(mapChanged);
239
+ mapChanged(app.maps.activeMap);
240
+
241
+ const stop = () => {
242
+ destroyCurrentSelectInteraction();
243
+ destroyInteractionChain();
244
+ highlightManager.destroy();
245
+ mapChangedListener();
246
+ obliqueImageChangedListener();
247
+ if (obliqueMap) {
248
+ obliqueMap.switchEnabled = true;
249
+ }
250
+ stopped.raiseEvent();
251
+ stopped.destroy();
252
+ modeChanged.destroy();
253
+ featuresChanged.destroy();
254
+ };
255
+ interactionRemoved.addEventListener(stop);
256
+
257
+ return {
258
+ type: SessionType.SELECT,
259
+ stopped,
260
+ stop,
261
+ setCurrentFeatures(features) {
262
+ return currentSelectInteraction.setSelected(features);
263
+ },
264
+ get currentFeatures() {
265
+ return currentSelectInteraction.selected;
266
+ },
267
+ get firstFeature() {
268
+ return currentSelectInteraction.selected[0] || null;
269
+ },
270
+ get mode() {
271
+ return currentSelectionMode;
272
+ },
273
+ setMode(newMode) {
274
+ check(newMode, Object.values(SelectionMode));
275
+
276
+ createSelectInteraction(newMode);
277
+ modeChanged.raiseEvent(currentSelectionMode);
278
+ },
279
+ modeChanged,
280
+ featuresChanged,
281
+ clearSelection() {
282
+ currentSelectInteraction.clear();
283
+ },
284
+ };
285
+ }
286
+
287
+ export default startSelectFeaturesSession;
@@ -83,7 +83,6 @@ function getCenterFromFeatures2D(features) {
83
83
  * In most scenarios, this function must not be called directly and the startEditFeatureSession used instead.
84
84
  * @param {import("@vcmap/core").VcsMap} map
85
85
  * @param {import("@vcmap/core").VectorLayer} layer
86
- * @param {import("@vcmap/core").SelectMultiFeatureInteraction} selectMultiFeatureInteraction
87
86
  * @param {import("@vcmap/core").VectorLayer} scratchLayer
88
87
  * @param {import("@vcmap/core").TransformationMode} mode
89
88
  * @returns {TransformationHandler}
@@ -91,7 +90,6 @@ function getCenterFromFeatures2D(features) {
91
90
  export default function createTransformationHandler(
92
91
  map,
93
92
  layer,
94
- selectMultiFeatureInteraction,
95
93
  scratchLayer,
96
94
  mode,
97
95
  ) {
@@ -106,12 +104,11 @@ export default function createTransformationHandler(
106
104
  let cesiumMap = null;
107
105
 
108
106
  let cancelAsyncSetting = () => {};
109
- const handleFeaturesChanged = async () => {
107
+ const setFeatures = async (features) => {
110
108
  cancelAsyncSetting();
111
- const { selectedFeatures } = selectMultiFeatureInteraction;
112
- const show = selectedFeatures.length > 0;
109
+ const show = features.length > 0;
113
110
  if (show) {
114
- const { center: newCenter, someClamped, someNoTerrain } = getCenterFromFeatures(selectedFeatures);
111
+ const { center: newCenter, someClamped, someNoTerrain } = getCenterFromFeatures(features);
115
112
  center = newCenter;
116
113
  if (!cesiumMap || !someNoTerrain) { // only set center sync, if updating will not change it too drastically (to avoid jumps)
117
114
  handlerFeatures.show = true;
@@ -140,8 +137,6 @@ export default function createTransformationHandler(
140
137
  handlerFeatures = create2DHandlers(map, scratchLayer, mode);
141
138
  getCenterFromFeatures = getCenterFromFeatures2D;
142
139
  }
143
- handleFeaturesChanged();
144
- const featuresChangedListener = selectMultiFeatureInteraction.featuresChanged.addEventListener(handleFeaturesChanged);
145
140
 
146
141
  return {
147
142
  get showing() { return handlerFeatures.show; },
@@ -158,9 +153,9 @@ export default function createTransformationHandler(
158
153
  center[2] += dz;
159
154
  handlerFeatures.setCenter(center);
160
155
  },
156
+ setFeatures,
161
157
  destroy() {
162
158
  cancelAsyncSetting();
163
- featuresChangedListener();
164
159
  handlerFeatures.destroy();
165
160
  scratchLayer.removeAllFeatures();
166
161
  },
@@ -19,6 +19,7 @@ import { Color } from '@vcmap-cesium/engine';
19
19
  * @property {import("ol/coordinate").Coordinate} center - readonly current center of the handler. this is a copy, not a reference
20
20
  * @property {AXIS_AND_PLANES} showAxis - highlight the given axis
21
21
  * @property {boolean} showing - readonly value indicating whether the handlers are showing (proxy for: features are selected)
22
+ * @property {function(Array<import("ol").Feature>):void} setFeatures - Sets transformation handle features. The handler is located in the center of the features.
22
23
  * @property {function():void} destroy - destroy the handler and any resources created by it
23
24
  */
24
25
 
@@ -71,7 +71,7 @@ export function getStylesArray(style, feature, resolution = 1) {
71
71
 
72
72
  /**
73
73
  * function to convert a feature to an array of Cesium.Primitives given a style and default properties. the resulting primitives
74
- * must be added to the contexts collections here
74
+ * must be added to the modules collections here
75
75
  * @param {import("ol").Feature<import("ol/geom/Geometry").default>} feature
76
76
  * @param {import("ol/style/Style").StyleLike} style
77
77
  * @param {import("@vcmap/core").VectorProperties} vectorProperties