@vcmap/ui 6.1.0-rc.1 → 6.1.0-rc.3

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 (128) hide show
  1. package/config/base.config.json +6 -0
  2. package/config/clipping.config.json +384 -0
  3. package/config/cluster.config.json +106 -0
  4. package/config/concepts-show-case.config.json +4 -0
  5. package/config/projects.config.json +5 -2
  6. package/dist/assets/{cesium-11e5bbc6.js → cesium-87d5e72d.js} +438 -432
  7. package/dist/assets/cesium.js +1 -1
  8. package/dist/assets/{core-9d0cfec3.js → core-72f9f393.js} +4907 -4514
  9. package/dist/assets/core.js +1 -1
  10. package/dist/assets/{ol-0d0ebb27.js → ol-e468ba43.js} +23518 -22404
  11. package/dist/assets/ol.js +1 -1
  12. package/dist/assets/ui-73257b15.css +1 -0
  13. package/dist/assets/{ui-08446666.js → ui-73257b15.js} +13703 -12977
  14. package/dist/assets/ui.js +1 -1
  15. package/dist/assets/vue.js +1 -1
  16. package/dist/assets/{vuetify-67025c41.css → vuetify-2437380c.css} +2 -2
  17. package/dist/assets/{vuetify-67025c41.js → vuetify-2437380c.js} +8024 -7634
  18. package/dist/assets/vuetify.js +1 -1
  19. package/index.d.ts +40 -19
  20. package/index.js +36 -6
  21. package/lib/olLib.js +25 -3
  22. package/package.json +6 -6
  23. package/plugins/@vcmap-show-case/callback-tester/README.md +3 -0
  24. package/plugins/@vcmap-show-case/callback-tester/package.json +5 -0
  25. package/plugins/@vcmap-show-case/callback-tester/src/CallbackTester.vue +62 -0
  26. package/plugins/@vcmap-show-case/callback-tester/src/index.js +48 -0
  27. package/plugins/@vcmap-show-case/form-inputs-example/src/FormInputsExample.vue +1 -0
  28. package/src/actions/actionHelper.d.ts +1 -0
  29. package/src/actions/actionHelper.js +70 -19
  30. package/src/application/VcsApp.vue +83 -50
  31. package/src/application/VcsApp.vue.d.ts +28 -2
  32. package/src/application/VcsContainer.vue +5 -3
  33. package/src/application/VcsContainer.vue.d.ts +14 -0
  34. package/src/application/VcsNavbar.vue +10 -6
  35. package/src/application/VcsNavbar.vue.d.ts +2 -0
  36. package/src/application/VcsObliqueFooter.vue +9 -3
  37. package/src/application/VcsSplashScreen.vue +37 -0
  38. package/src/application/VcsSplashScreen.vue.d.ts +6 -0
  39. package/src/application/positionDisplayInteraction.js +1 -1
  40. package/src/callback/activateClippingPolygonCallback.d.ts +29 -0
  41. package/src/callback/activateClippingPolygonCallback.js +54 -0
  42. package/src/callback/closeSplashScreenCallback.d.ts +8 -0
  43. package/src/callback/closeSplashScreenCallback.js +33 -0
  44. package/src/callback/deactivateClippingPolygonCallback.d.ts +29 -0
  45. package/src/callback/deactivateClippingPolygonCallback.js +54 -0
  46. package/src/callback/openSplashScreenCallback.d.ts +8 -0
  47. package/src/callback/openSplashScreenCallback.js +35 -0
  48. package/src/callback/toggleNavbarButtonCallback.d.ts +36 -0
  49. package/src/callback/toggleNavbarButtonCallback.js +62 -0
  50. package/src/components/buttons/VcsActionButtonList.vue +6 -4
  51. package/src/components/buttons/VcsToolButton.vue +0 -1
  52. package/src/components/form-inputs-controls/VcsDatePicker.vue +7 -1
  53. package/src/components/form-inputs-controls/VcsDatePicker.vue.d.ts +9 -0
  54. package/src/components/form-inputs-controls/VcsSelect.vue +1 -1
  55. package/src/components/form-inputs-controls/VcsTextArea.vue +13 -8
  56. package/src/components/form-output/markdownHelper.d.ts +0 -25
  57. package/src/components/form-output/markdownHelper.js +1 -386
  58. package/src/components/import/VcsImportComponent.vue +2 -0
  59. package/src/components/lists/VcsGroupedList.vue +178 -0
  60. package/src/components/lists/VcsGroupedList.vue.d.ts +17 -0
  61. package/src/components/lists/VcsList.vue +144 -394
  62. package/src/components/lists/VcsList.vue.d.ts +38 -159
  63. package/src/components/lists/VcsTreeNode.vue +18 -11
  64. package/src/components/lists/VcsTreeview.vue +27 -20
  65. package/src/components/lists/VcsTreeview.vue.d.ts +18 -1
  66. package/src/components/lists/listHelper.d.ts +87 -0
  67. package/src/components/lists/listHelper.js +348 -0
  68. package/src/components/section/VcsFormSection.vue +7 -2
  69. package/src/components/section/VcsFormSection.vue.d.ts +9 -0
  70. package/src/components/tables/VcsDataTable.vue +14 -3
  71. package/src/components/tables/VcsDataTable.vue.d.ts +9 -0
  72. package/src/components/vector-properties/VcsVectorPropertiesComponent.vue.d.ts +1 -1
  73. package/src/contentTree/LayerTree.vue +2 -1
  74. package/src/contentTree/LayerTree.vue.d.ts +2 -0
  75. package/src/contentTree/contentTreeCollection.d.ts +1 -0
  76. package/src/contentTree/contentTreeCollection.js +7 -3
  77. package/src/contentTree/contentTreeItem.js +4 -2
  78. package/src/contentTree/groupContentTreeItem.js +5 -3
  79. package/src/featureInfo/ClusterFeatureComponent.vue +58 -0
  80. package/src/featureInfo/ClusterFeatureComponent.vue.d.ts +6 -0
  81. package/src/featureInfo/abstractFeatureInfoView.js +1 -2
  82. package/src/featureInfo/featureInfo.d.ts +87 -1
  83. package/src/featureInfo/featureInfo.js +342 -34
  84. package/src/featureInfo/featureInfoInteraction.js +18 -3
  85. package/src/featureInfo/iframeFeatureInfoView.js +1 -1
  86. package/src/featureInfo/markdownBalloonFeatureInfoView.js +2 -4
  87. package/src/featureInfo/markdownFeatureInfoView.js +1 -1
  88. package/src/i18n/de.d.ts +17 -4
  89. package/src/i18n/de.js +7 -0
  90. package/src/i18n/en.d.ts +17 -4
  91. package/src/i18n/en.js +7 -0
  92. package/src/legend/VcsLegend.vue +1 -1
  93. package/src/legend/legendHelper.d.ts +1 -1
  94. package/src/legend/legendHelper.js +52 -9
  95. package/src/localStorage.d.ts +21 -0
  96. package/src/localStorage.js +51 -0
  97. package/src/manager/collectionManager/CollectionComponent.vue +1 -1
  98. package/src/manager/collectionManager/CollectionComponentContent.vue +2 -3
  99. package/src/manager/collectionManager/CollectionComponentList.vue +2 -3
  100. package/src/manager/collectionManager/CollectionComponentStandalone.vue +1 -1
  101. package/src/manager/navbarManager.js +9 -4
  102. package/src/manager/toolbox/ToolboxManagerComponent.vue +14 -12
  103. package/src/manager/toolbox/ToolboxManagerComponent.vue.d.ts +13 -2
  104. package/src/manager/toolbox/toolboxManager.d.ts +5 -0
  105. package/src/manager/toolbox/toolboxManager.js +7 -1
  106. package/src/manager/window/WindowComponent.vue +10 -0
  107. package/src/manager/window/WindowComponent.vue.d.ts +1 -0
  108. package/src/manager/window/WindowManager.vue +14 -4
  109. package/src/manager/window/WindowManager.vue.d.ts +1 -0
  110. package/src/manager/window/windowHelper.d.ts +7 -3
  111. package/src/manager/window/windowHelper.js +30 -10
  112. package/src/navigation/MapNavigation.vue +5 -5
  113. package/src/navigation/MapNavigation.vue.d.ts +1 -1
  114. package/src/navigation/overviewMap.d.ts +7 -0
  115. package/src/navigation/overviewMap.js +18 -4
  116. package/src/pluginHelper.d.ts +7 -0
  117. package/src/pluginHelper.js +18 -4
  118. package/src/search/ResultItem.vue.d.ts +1 -1
  119. package/src/search/markText.d.ts +1 -1
  120. package/src/search/markText.js +4 -4
  121. package/src/search/search.js +1 -1
  122. package/src/state.d.ts +4 -2
  123. package/src/state.js +54 -31
  124. package/src/uiConfig.d.ts +36 -0
  125. package/src/uiConfig.js +17 -1
  126. package/src/vcsUiApp.js +7 -11
  127. package/dist/assets/ui-08446666.css +0 -1
  128. /package/dist/assets/{vue-2f81c7f8.js → vue-ff37ea23.js} +0 -0
@@ -1,3 +1,10 @@
1
+ /**
2
+ *
3
+ * @param {import("ol/style/Style.js").default?} style
4
+ * @param {import("@vcmap-cesium/engine").Color} fillColor
5
+ * @returns {import("ol/style/Style.js").default}
6
+ */
7
+ export function getHighlightStyleFromStyle(style: import("ol/style/Style.js").default | null, fillColor: import("@vcmap-cesium/engine").Color): import("ol/style/Style.js").default;
1
8
  /**
2
9
  * @param {FeatureType} feature
3
10
  * @param {import("@vcmap/core").Layer} layer
@@ -5,6 +12,14 @@
5
12
  * @returns {import("ol/style/Style.js").default|import("@vcmap/core").VectorStyleItem}
6
13
  */
7
14
  export function getHighlightStyle(feature: FeatureType, layer: import("@vcmap/core").Layer, defaultFillColor: string): import("ol/style/Style.js").default | import("@vcmap/core").VectorStyleItem;
15
+ /**
16
+ * @param {import("ol").Feature} clusterFeature
17
+ * @param {import("@vcmap/core").VectorClusterGroup} clusterGroup
18
+ * @param {import("ol/style/Style.js").default} clusterStyle
19
+ * @param {string} defaultFillColor
20
+ * @returns {import("ol/style/Style.js").default}
21
+ */
22
+ export function getClusterHighlightStyle(clusterFeature: import("ol").Feature, clusterGroup: import("@vcmap/core").VectorClusterGroup, clusterStyle: import("ol/style/Style.js").default, defaultFillColor: string): import("ol/style/Style.js").default;
8
23
  /**
9
24
  * @param {import("../vcsUiApp.js").default} app
10
25
  * @returns {FeatureInfoSession}
@@ -69,7 +84,12 @@ declare class FeatureInfo extends Collection<AbstractFeatureInfoView> {
69
84
  */
70
85
  private _windowId;
71
86
  /**
72
- * @type {VcsEvent<FeatureType>}
87
+ * @type {string|null}
88
+ * @private
89
+ */
90
+ private _clusterWindowId;
91
+ /**
92
+ * @type {VcsEvent<FeatureType|null>}
73
93
  * @private
74
94
  */
75
95
  private _featureChanged;
@@ -83,6 +103,21 @@ declare class FeatureInfo extends Collection<AbstractFeatureInfoView> {
83
103
  * @private
84
104
  */
85
105
  private _selectedFeatureId;
106
+ /**
107
+ * @type {VcsEvent<import("ol").Feature|null>}
108
+ * @private
109
+ */
110
+ private _clusterFeatureChanged;
111
+ /**
112
+ * @type {import("ol").Feature|null}
113
+ * @private
114
+ */
115
+ private _selectedClusterFeature;
116
+ /**
117
+ * @type {string|null}
118
+ * @private
119
+ */
120
+ private _selectedClusterFeatureId;
86
121
  /**
87
122
  * @type {Array<function():void>}
88
123
  * @private
@@ -96,6 +131,8 @@ declare class FeatureInfo extends Collection<AbstractFeatureInfoView> {
96
131
  */
97
132
  private _scratchLayer;
98
133
  /**
134
+ * Emitted whenever a feature is selected or cleared.
135
+ * Does not reflect cluster feature changes!
99
136
  * @type {VcsEvent<null|FeatureType>}
100
137
  */
101
138
  get featureChanged(): VcsEvent<FeatureType | null>;
@@ -107,11 +144,29 @@ declare class FeatureInfo extends Collection<AbstractFeatureInfoView> {
107
144
  * @type {null|string}
108
145
  */
109
146
  get selectedFeatureId(): string | null;
147
+ /**
148
+ * Emitted whenever a cluster feature is selected or cleared.
149
+ * @type {VcsEvent<null|import("ol").Feature>}
150
+ */
151
+ get clusterFeatureChanged(): VcsEvent<Feature<import("ol/geom.js").Geometry> | null>;
152
+ /**
153
+ * @type {null|import("ol").Feature}
154
+ */
155
+ get selectedClusterFeature(): Feature<import("ol/geom.js").Geometry> | null;
156
+ /**
157
+ * @type {null|string}
158
+ */
159
+ get selectedClusterFeatureId(): string | null;
110
160
  /**
111
161
  * The window id of the current features FeatureInfoView window
112
162
  * @type {string|null}
113
163
  */
114
164
  get windowId(): string | null;
165
+ /**
166
+ * The window id of the current cluster feature window
167
+ * @type {string|null}
168
+ */
169
+ get clusterWindowId(): string | null;
115
170
  /**
116
171
  * @private
117
172
  */
@@ -136,11 +191,42 @@ declare class FeatureInfo extends Collection<AbstractFeatureInfoView> {
136
191
  * @returns {Promise<void>}
137
192
  */
138
193
  selectFeature(feature: FeatureType, position?: import("ol/coordinate.js").Coordinate | undefined, windowPosition?: import("ol/coordinate.js").Coordinate | undefined, featureInfoView?: AbstractFeatureInfoView | undefined): Promise<void>;
194
+ /**
195
+ * Selecting a cluster feature opens a window listing the features belonging to the cluster feature.
196
+ * To be listed the feature must meet the following criteria: a) the feature must be part of a layer, b) said layer must be managed in
197
+ * the same VcsApp as provided to the FeatureInfo on construction. if not providing a feature info view, then c) said layer must have a featureInfo property set on
198
+ * its properties bag and d) said featureInfo property must provide the name of a FeatureInfoView present on this FeatureInfos
199
+ * collection.
200
+ * The cluster feature will be cloned, highlighted and added on an internal scratch layer to ensure availability until deselection.
201
+ * The original cluster feature will be hidden until deselection.
202
+ * @param {import("ol").Feature} clusterFeature
203
+ * @returns {Promise<void>}
204
+ */
205
+ selectClusterFeature(clusterFeature: import("ol").Feature): Promise<void>;
139
206
  /**
140
207
  * Clears the current feature. remove window, highlighting and provided feature.
141
208
  * @private
142
209
  */
143
210
  private _clearInternal;
211
+ /**
212
+ * Clears the current cluster feature. remove window, highlighting and provided cluster feature.
213
+ * @private
214
+ */
215
+ private _clearClusterInternal;
216
+ /**
217
+ * Deselecting feature clears highlighting and closes FeatureInfoView. fires feature changed with null
218
+ */
219
+ clearFeature(): void;
220
+ /**
221
+ * Deselecting and removing cluster feature. Closing cluster window and fires cluster feature changed with null
222
+ */
223
+ clearCluster(): void;
224
+ /**
225
+ * Clears selection by deselecting current feature and cluster and closing all related windows.
226
+ * Fires feature changed and cluster feature changed events with null.
227
+ */
228
+ clearSelection(): void;
144
229
  }
145
230
  import { VcsEvent } from '@vcmap/core';
146
231
  import { Collection } from '@vcmap/core';
232
+ import { Feature } from 'ol';
@@ -12,6 +12,11 @@ import {
12
12
  VectorStyleItem,
13
13
  markVolatile,
14
14
  maxZIndex,
15
+ vectorClusterGroupName,
16
+ hidden,
17
+ isProvidedClusterFeature,
18
+ alreadyTransformedToImage,
19
+ ObliqueMap,
15
20
  } from '@vcmap/core';
16
21
  import { getLogger as getLoggerByName } from '@vcsuite/logger';
17
22
  import {
@@ -24,6 +29,7 @@ import { Feature } from 'ol';
24
29
  import { check, maybe, oneOf } from '@vcsuite/check';
25
30
 
26
31
  import { reactive } from 'vue';
32
+ import { WindowSlot } from '../manager/window/windowManager.js';
27
33
  import { vcsAppSymbol } from '../pluginHelper.js';
28
34
  import FeatureInfoInteraction from './featureInfoInteraction.js';
29
35
  import AbstractFeatureInfoView from './abstractFeatureInfoView.js';
@@ -36,6 +42,7 @@ import { getDefaultPrimaryColor } from '../vuePlugins/vuetify.js';
36
42
  import { ToolboxType } from '../manager/toolbox/toolboxManager.js';
37
43
  import MarkdownBalloonFeatureInfoView from './markdownBalloonFeatureInfoView.js';
38
44
  import IframeWmsFeatureInfoView from './iframeWmsFeatureInfoView.js';
45
+ import ClusterFeatureComponent from './ClusterFeatureComponent.vue';
39
46
 
40
47
  /** @typedef {import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature|import("@vcmap-cesium/engine").Entity} FeatureType */
41
48
 
@@ -64,6 +71,43 @@ export const featureInfoClassRegistry = new ClassRegistry();
64
71
  */
65
72
  export const featureInfoViewSymbol = Symbol('featureInfoView');
66
73
 
74
+ /**
75
+ *
76
+ * @param {import("ol/style/Style.js").default?} style
77
+ * @param {import("@vcmap-cesium/engine").Color} fillColor
78
+ * @returns {import("ol/style/Style.js").default}
79
+ */
80
+ export function getHighlightStyleFromStyle(style, fillColor) {
81
+ const highlightStyle =
82
+ style?.clone?.() ??
83
+ new VectorStyleItem(getDefaultVectorStyleItemOptions()).style;
84
+ if (highlightStyle.getText()) {
85
+ if (highlightStyle.getText().getFill()) {
86
+ highlightStyle.getText().getFill().setColor(fillColor.toCssColorString());
87
+ }
88
+ highlightStyle
89
+ .getText()
90
+ .setScale((highlightStyle.getText().getScale() ?? 1) * 2);
91
+ }
92
+ if (highlightStyle.getImage()) {
93
+ highlightStyle
94
+ .getImage()
95
+ .setScale(highlightStyle.getImage().getScale() * 2);
96
+ }
97
+ if (highlightStyle.getStroke()) {
98
+ highlightStyle.getStroke().setColor(fillColor.toCssColorString());
99
+ highlightStyle
100
+ .getStroke()
101
+ .setWidth(highlightStyle.getStroke().getWidth() * 2);
102
+ }
103
+ if (highlightStyle.getFill()) {
104
+ const color = fillColor.toBytes();
105
+ color[3] /= 255;
106
+ highlightStyle.getFill().setColor(color);
107
+ }
108
+ return highlightStyle;
109
+ }
110
+
67
111
  /**
68
112
  * @param {FeatureType} feature
69
113
  * @param {import("@vcmap/core").Layer} layer
@@ -71,7 +115,7 @@ export const featureInfoViewSymbol = Symbol('featureInfoView');
71
115
  * @returns {import("ol/style/Style.js").default|import("@vcmap/core").VectorStyleItem}
72
116
  */
73
117
  export function getHighlightStyle(feature, layer, defaultFillColor) {
74
- if (layer && layer.highlightStyle) {
118
+ if (layer?.highlightStyle) {
75
119
  return layer.highlightStyle;
76
120
  }
77
121
 
@@ -81,32 +125,32 @@ export function getHighlightStyle(feature, layer, defaultFillColor) {
81
125
  if (typeof style === 'function') {
82
126
  style = style(feature, 1);
83
127
  }
84
- style =
85
- style?.clone?.() ??
86
- new VectorStyleItem(getDefaultVectorStyleItemOptions()).style;
87
- if (style.getText()) {
88
- if (style.getText().getFill()) {
89
- style.getText().getFill().setColor(fillColor.toCssColorString());
90
- }
91
- style.getText().setScale((style.getText().getScale() ?? 1) * 2);
92
- }
93
- if (style.getImage()) {
94
- style.getImage().setScale(style.getImage().getScale() * 2);
95
- }
96
- if (style.getStroke()) {
97
- style.getStroke().setColor(fillColor.toCssColorString());
98
- style.getStroke().setWidth(style.getStroke().getWidth() * 2);
99
- }
100
- if (style.getFill()) {
101
- const color = fillColor.toBytes();
102
- color[3] /= 255;
103
- style.getFill().setColor(color);
104
- }
105
- return style;
128
+ return getHighlightStyleFromStyle(style, fillColor);
106
129
  }
107
130
  return fromCesiumColor(fillColor);
108
131
  }
109
132
 
133
+ /**
134
+ * @param {import("ol").Feature} clusterFeature
135
+ * @param {import("@vcmap/core").VectorClusterGroup} clusterGroup
136
+ * @param {import("ol/style/Style.js").default} clusterStyle
137
+ * @param {string} defaultFillColor
138
+ * @returns {import("ol/style/Style.js").default}
139
+ */
140
+ export function getClusterHighlightStyle(
141
+ clusterFeature,
142
+ clusterGroup,
143
+ clusterStyle,
144
+ defaultFillColor,
145
+ ) {
146
+ if (clusterGroup?.highlightStyle) {
147
+ return clusterGroup.getHighlightStyleForFeature(clusterFeature);
148
+ }
149
+
150
+ const fillColor = Color.fromCssColorString(defaultFillColor).withAlpha(0.8);
151
+ return getHighlightStyleFromStyle(clusterStyle, fillColor);
152
+ }
153
+
110
154
  /**
111
155
  * @param {import("../vcsUiApp.js").default} app
112
156
  * @returns {FeatureInfoSession}
@@ -158,7 +202,7 @@ function setupFeatureInfoTool(app) {
158
202
  session.stopped.addEventListener(() => {
159
203
  action.active = false;
160
204
  session = null;
161
- app.featureInfo.clear();
205
+ app.featureInfo.clearSelection();
162
206
  action.title = 'featureInfo.activateToolTitle';
163
207
  });
164
208
  this.active = true;
@@ -266,7 +310,12 @@ class FeatureInfo extends Collection {
266
310
  */
267
311
  this._windowId = null;
268
312
  /**
269
- * @type {VcsEvent<FeatureType>}
313
+ * @type {string|null}
314
+ * @private
315
+ */
316
+ this._clusterWindowId = null;
317
+ /**
318
+ * @type {VcsEvent<FeatureType|null>}
270
319
  * @private
271
320
  */
272
321
  this._featureChanged = new VcsEvent();
@@ -280,6 +329,21 @@ class FeatureInfo extends Collection {
280
329
  * @private
281
330
  */
282
331
  this._selectedFeatureId = null;
332
+ /**
333
+ * @type {VcsEvent<import("ol").Feature|null>}
334
+ * @private
335
+ */
336
+ this._clusterFeatureChanged = new VcsEvent();
337
+ /**
338
+ * @type {import("ol").Feature|null}
339
+ * @private
340
+ */
341
+ this._selectedClusterFeature = null;
342
+ /**
343
+ * @type {string|null}
344
+ * @private
345
+ */
346
+ this._selectedClusterFeatureId = null;
283
347
  /**
284
348
  * @type {Array<function():void>}
285
349
  * @private
@@ -305,18 +369,39 @@ class FeatureInfo extends Collection {
305
369
  ) {
306
370
  this._app.windowManager.remove(this._windowId);
307
371
  }
372
+
373
+ if (
374
+ this._clusterWindowId &&
375
+ this._app.windowManager.has(this._clusterWindowId)
376
+ ) {
377
+ const { props } = this._app.windowManager.get(this._clusterWindowId);
378
+ if (props.items.some((item) => item.group === layer.name)) {
379
+ props.items = props.items.filter(
380
+ (item) => item.group !== layer.name,
381
+ );
382
+ props.groups = props.groups.filter(
383
+ (group) => group.name !== layer.name,
384
+ );
385
+ if (props.items.length === 0) {
386
+ this._app.windowManager.remove(this._clusterWindowId);
387
+ }
388
+ }
389
+ }
308
390
  }),
309
391
  this._app.windowManager.removed.addEventListener(({ id }) => {
310
392
  if (id === this._windowId) {
311
- this.clear();
393
+ this.clearFeature();
394
+ }
395
+ if (id === this._clusterWindowId) {
396
+ this.clearCluster();
312
397
  }
313
398
  }),
314
399
  this._app.moduleAdded.addEventListener(() => {
315
- this.clear();
400
+ this.clearSelection();
316
401
  this._destroyFeatureInfoTool();
317
402
  this._destroyFeatureInfoTool = setupFeatureInfoTool(this._app);
318
403
  }),
319
- this._app.moduleRemoved.addEventListener(() => this.clear()),
404
+ this._app.moduleRemoved.addEventListener(() => this.clearSelection()),
320
405
  ];
321
406
  /**
322
407
  * A vector layer to render provided features on
@@ -332,6 +417,8 @@ class FeatureInfo extends Collection {
332
417
  }
333
418
 
334
419
  /**
420
+ * Emitted whenever a feature is selected or cleared.
421
+ * Does not reflect cluster feature changes!
335
422
  * @type {VcsEvent<null|FeatureType>}
336
423
  */
337
424
  get featureChanged() {
@@ -352,6 +439,28 @@ class FeatureInfo extends Collection {
352
439
  return this._selectedFeatureId;
353
440
  }
354
441
 
442
+ /**
443
+ * Emitted whenever a cluster feature is selected or cleared.
444
+ * @type {VcsEvent<null|import("ol").Feature>}
445
+ */
446
+ get clusterFeatureChanged() {
447
+ return this._clusterFeatureChanged;
448
+ }
449
+
450
+ /**
451
+ * @type {null|import("ol").Feature}
452
+ */
453
+ get selectedClusterFeature() {
454
+ return this._selectedClusterFeature;
455
+ }
456
+
457
+ /**
458
+ * @type {null|string}
459
+ */
460
+ get selectedClusterFeatureId() {
461
+ return this._selectedClusterFeatureId;
462
+ }
463
+
355
464
  /**
356
465
  * The window id of the current features FeatureInfoView window
357
466
  * @type {string|null}
@@ -360,6 +469,14 @@ class FeatureInfo extends Collection {
360
469
  return this._windowId;
361
470
  }
362
471
 
472
+ /**
473
+ * The window id of the current cluster feature window
474
+ * @type {string|null}
475
+ */
476
+ get clusterWindowId() {
477
+ return this._clusterWindowId;
478
+ }
479
+
363
480
  /**
364
481
  * @private
365
482
  */
@@ -426,10 +543,19 @@ class FeatureInfo extends Collection {
426
543
 
427
544
  if (usedFeatureInfoView && layer) {
428
545
  this._clearInternal();
546
+ if (
547
+ this._selectedClusterFeature &&
548
+ !this._selectedClusterFeature.get('features').includes(feature)
549
+ ) {
550
+ this.clearCluster();
551
+ }
429
552
  if (feature[isProvidedFeature]) {
430
553
  this._ensureScratchLayer();
431
- this._scratchLayer.addFeatures([feature]);
432
- const featureId = feature.getId(); // make sure to grab ID after adding it to the layer
554
+ // we need to clone the feature to avoid changing vcsLayerNameSymbol on the original feature
555
+ const clonedFeature = feature.clone();
556
+ clonedFeature.setId(feature.getId());
557
+ this._scratchLayer.addFeatures([clonedFeature]);
558
+ const featureId = clonedFeature.getId(); // make sure to grab ID after adding it to the layer
433
559
  this._scratchLayer.featureVisibility.highlight({
434
560
  [featureId]: getHighlightStyle(
435
561
  feature,
@@ -440,6 +566,29 @@ class FeatureInfo extends Collection {
440
566
  });
441
567
  this._clearHighlightingCb = () =>
442
568
  this._scratchLayer.featureVisibility.unHighlight([featureId]);
569
+ } else if (layer.vectorClusterGroup) {
570
+ this._ensureScratchLayer();
571
+ const clone = feature.clone();
572
+ const featureId = feature.getId();
573
+ this._scratchLayer.vectorProperties.setValuesForFeatures(
574
+ layer.vectorProperties.getValuesForFeatures([clone]),
575
+ [clone],
576
+ );
577
+ const eyeOffset = clone.get('olcs_eyeOffset') ?? [0, 0, 0];
578
+ eyeOffset[2] -= 10;
579
+ clone.set('olcs_eyeOffset', eyeOffset);
580
+ clone.setId(featureId);
581
+ this._scratchLayer.addFeatures([clone]);
582
+ this._scratchLayer.featureVisibility.highlight({
583
+ [featureId]: getHighlightStyle(
584
+ feature,
585
+ layer,
586
+ this._app.uiConfig.config.primaryColor ??
587
+ getDefaultPrimaryColor(this._app),
588
+ ),
589
+ });
590
+ this._clearHighlightingCb = () =>
591
+ this._scratchLayer.featureVisibility.unHighlight([clone]);
443
592
  } else if (layer.featureVisibility) {
444
593
  const featureId = feature.getId();
445
594
  layer.featureVisibility.highlight({
@@ -470,10 +619,117 @@ class FeatureInfo extends Collection {
470
619
  this._selectedFeatureId = feature.getId();
471
620
  this._featureChanged.raiseEvent(this._selectedFeature);
472
621
  } else {
473
- this.clear();
622
+ this.clearSelection();
474
623
  }
475
624
  }
476
625
 
626
+ /**
627
+ * Selecting a cluster feature opens a window listing the features belonging to the cluster feature.
628
+ * To be listed the feature must meet the following criteria: a) the feature must be part of a layer, b) said layer must be managed in
629
+ * the same VcsApp as provided to the FeatureInfo on construction. if not providing a feature info view, then c) said layer must have a featureInfo property set on
630
+ * its properties bag and d) said featureInfo property must provide the name of a FeatureInfoView present on this FeatureInfos
631
+ * collection.
632
+ * The cluster feature will be cloned, highlighted and added on an internal scratch layer to ensure availability until deselection.
633
+ * The original cluster feature will be hidden until deselection.
634
+ * @param {import("ol").Feature} clusterFeature
635
+ * @returns {Promise<void>}
636
+ */
637
+ async selectClusterFeature(clusterFeature) {
638
+ this.clearFeature();
639
+ this._clearClusterInternal();
640
+ const id = `cluster-at-${clusterFeature.getGeometry().getCoordinates().join('-')}`;
641
+
642
+ this._ensureScratchLayer();
643
+ const feature = clusterFeature.clone();
644
+ feature.setId(id);
645
+
646
+ clusterFeature[hidden] = true;
647
+ clusterFeature.changed();
648
+
649
+ const fillColor =
650
+ this._app.uiConfig.config.primaryColor ??
651
+ getDefaultPrimaryColor(this._app);
652
+
653
+ if (clusterFeature[vectorClusterGroupName]) {
654
+ const clusterGroup = this._app.vectorClusterGroups.getByKey(
655
+ clusterFeature[vectorClusterGroupName],
656
+ );
657
+ this._scratchLayer.vectorProperties.setValuesForFeatures(
658
+ clusterGroup.vectorProperties.getValuesForFeatures([feature]),
659
+ [feature],
660
+ );
661
+ const clusterStyle = clusterGroup.style.createStyleFunction((layerName) =>
662
+ this._app.layers.getByKey(layerName),
663
+ )(clusterFeature, 1);
664
+ const highlightStyle = getClusterHighlightStyle(
665
+ clusterFeature,
666
+ clusterGroup,
667
+ clusterStyle,
668
+ fillColor,
669
+ );
670
+ feature.setStyle(highlightStyle);
671
+ } else if (clusterFeature[isProvidedClusterFeature]) {
672
+ feature.setStyle(
673
+ fromCesiumColor(Color.fromCssColorString(fillColor)).style,
674
+ );
675
+ }
676
+
677
+ if (this._app.maps.activeMap instanceof ObliqueMap) {
678
+ feature.getGeometry()[alreadyTransformedToImage] = true;
679
+ }
680
+ this._scratchLayer.addFeatures([feature]);
681
+
682
+ const features = clusterFeature.get('features');
683
+ const groups = {};
684
+ const items = features.map((f) => {
685
+ const listItem = reactive({
686
+ name: f.getId(),
687
+ title: f.getAttributes()?.title || f.getAttributes()?.name || f.getId(),
688
+ disabled: !this._getFeatureInfoViewForFeature(f),
689
+ selectionChanged: (value) => {
690
+ if (value) {
691
+ this.selectFeature(f);
692
+ } else {
693
+ this.clearFeature();
694
+ }
695
+ },
696
+ });
697
+ const layerName = f[vcsLayerName];
698
+ if (layerName) {
699
+ if (!groups[layerName]) {
700
+ const title = this._app.layers.getByKey(layerName)?.properties?.title;
701
+ groups[layerName] = {
702
+ name: layerName,
703
+ title: title || layerName,
704
+ };
705
+ }
706
+ listItem.group = layerName;
707
+ }
708
+ return listItem;
709
+ });
710
+
711
+ this._clusterWindowId = id;
712
+ this._app.windowManager.add(
713
+ {
714
+ id,
715
+ component: ClusterFeatureComponent,
716
+ props: reactive({
717
+ items,
718
+ groups: Object.values(groups),
719
+ }),
720
+ state: {
721
+ headerTitle: 'featureInfo.cluster.headerTitle',
722
+ },
723
+ slot: WindowSlot.DYNAMIC_LEFT,
724
+ },
725
+ vcsAppSymbol,
726
+ );
727
+
728
+ this._selectedClusterFeature = clusterFeature;
729
+ this._selectedClusterFeatureId = id;
730
+ this._clusterFeatureChanged.raiseEvent(this._selectedClusterFeature);
731
+ }
732
+
477
733
  /**
478
734
  * Clears the current feature. remove window, highlighting and provided feature.
479
735
  * @private
@@ -487,16 +743,35 @@ class FeatureInfo extends Collection {
487
743
  this._app.windowManager.remove(this._windowId);
488
744
  this._windowId = null;
489
745
  }
746
+ if (this._scratchLayer && this._selectedFeatureId) {
747
+ this._scratchLayer.removeFeaturesById([this._selectedFeatureId]);
748
+ }
749
+ }
490
750
 
491
- if (this._scratchLayer) {
492
- this._scratchLayer.removeAllFeatures();
751
+ /**
752
+ * Clears the current cluster feature. remove window, highlighting and provided cluster feature.
753
+ * @private
754
+ */
755
+ _clearClusterInternal() {
756
+ if (this._clusterWindowId) {
757
+ this._app.windowManager.remove(this._clusterWindowId);
758
+ this._clusterWindowId = null;
759
+ }
760
+
761
+ if (this._selectedClusterFeature) {
762
+ this._selectedClusterFeature[hidden] = false;
763
+ this._selectedClusterFeature.changed();
764
+
765
+ if (this._scratchLayer) {
766
+ this._scratchLayer.removeFeaturesById([this._selectedClusterFeatureId]);
767
+ }
493
768
  }
494
769
  }
495
770
 
496
771
  /**
497
772
  * Deselecting feature clears highlighting and closes FeatureInfoView. fires feature changed with null
498
773
  */
499
- clear() {
774
+ clearFeature() {
500
775
  this._clearInternal();
501
776
  if (this._selectedFeature) {
502
777
  this._selectedFeature = null;
@@ -505,12 +780,45 @@ class FeatureInfo extends Collection {
505
780
  }
506
781
  }
507
782
 
783
+ /**
784
+ * Deselecting and removing cluster feature. Closing cluster window and fires cluster feature changed with null
785
+ */
786
+ clearCluster() {
787
+ this._clearClusterInternal();
788
+ if (this._selectedClusterFeature) {
789
+ this._selectedClusterFeature[hidden] = false;
790
+ this._selectedClusterFeature = null;
791
+ this._clusterFeatureChanged.raiseEvent(this._selectedClusterFeature);
792
+ }
793
+ }
794
+
795
+ /**
796
+ * @deprecated
797
+ */
798
+ clear() {
799
+ getLogger().deprecate(
800
+ 'clear',
801
+ 'Use clearSelection instead. Clear will clear the FeatureInfo collection removing all registered FeatureInfoViews in feature.',
802
+ );
803
+ this.clearSelection();
804
+ }
805
+
806
+ /**
807
+ * Clears selection by deselecting current feature and cluster and closing all related windows.
808
+ * Fires feature changed and cluster feature changed events with null.
809
+ */
810
+ clearSelection() {
811
+ this.clearFeature();
812
+ this.clearCluster();
813
+ }
814
+
508
815
  /**
509
816
  * Destroys the feature info and all its events & listeners
510
817
  */
511
818
  destroy() {
512
819
  super.destroy();
513
820
  this._clearInternal();
821
+ this._clearClusterInternal();
514
822
  this._featureChanged.destroy();
515
823
  this._destroyFeatureInfoTool();
516
824
  if (this._scratchLayer) {