@vcmap/ui 6.2.1 → 6.2.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 (62) hide show
  1. package/build/postInstall.js +1 -1
  2. package/config/dev.config.json +1 -1
  3. package/dist/assets/{cesium-607748ca.js → cesium-dcdef786.js} +1513 -1313
  4. package/dist/assets/cesium.js +1 -1
  5. package/dist/assets/{core-ea8a27bd.js → core-f4ee6bea.js} +1554 -1551
  6. package/dist/assets/core-workers/panoramaImageWorker.js +1 -1
  7. package/dist/assets/core.js +1 -1
  8. package/dist/assets/{ol-8fe80c53.js → ol-f7a5160f.js} +1 -1
  9. package/dist/assets/ol.js +1 -1
  10. package/dist/assets/ui-d3a7bd39.css +1 -0
  11. package/dist/assets/{ui-ffa735dc.js → ui-d3a7bd39.js} +7725 -7494
  12. package/dist/assets/ui.js +1 -1
  13. package/dist/assets/vue.js +1 -1
  14. package/dist/assets/{vuetify-def19d80.js → vuetify-8e1623b2.js} +1 -1
  15. package/dist/assets/vuetify.js +1 -1
  16. package/index.d.ts +3 -1
  17. package/index.js +2 -0
  18. package/package.json +4 -3
  19. package/plugins/@vcmap-show-case/category-tester/src/CollectionComponentOptions.vue +41 -2
  20. package/plugins/@vcmap-show-case/category-tester/src/index.js +2 -0
  21. package/src/application/VcsSettings.vue +4 -0
  22. package/src/application/VcsSplashScreen.vue +9 -1
  23. package/src/components/flight/VcsFlightAnchorsComponent.vue +2 -2
  24. package/src/components/lists/VcsList.vue +10 -18
  25. package/src/components/lists/VcsTreeNode.vue +10 -4
  26. package/src/components/lists/VcsTreeNode.vue.d.ts +8 -0
  27. package/src/components/lists/VcsTreeview.vue +2 -6
  28. package/src/components/lists/VcsTreeview.vue.d.ts +0 -1
  29. package/src/components/lists/dragHelper.d.ts +9 -2
  30. package/src/components/lists/dragHelper.js +47 -21
  31. package/src/contentTree/LayerSwap.vue +24 -4
  32. package/src/contentTree/LayerSwap.vue.d.ts +18 -4
  33. package/src/contentTree/wmsGroupContentTreeItem.d.ts +22 -3
  34. package/src/contentTree/wmsGroupContentTreeItem.js +126 -64
  35. package/src/featureInfo/balloonFeatureInfoView.d.ts +15 -2
  36. package/src/featureInfo/balloonFeatureInfoView.js +8 -103
  37. package/src/featureInfo/balloonHelper.d.ts +13 -0
  38. package/src/featureInfo/balloonHelper.js +77 -0
  39. package/src/featureInfo/featureInfo.d.ts +2 -1
  40. package/src/featureInfo/featureInfo.js +11 -3
  41. package/src/featureInfo/featureInfoInteraction.js +4 -1
  42. package/src/featureInfo/iframeWmsFeatureInfoView.js +12 -2
  43. package/src/i18n/de.d.ts +3 -0
  44. package/src/i18n/de.js +3 -0
  45. package/src/i18n/en.d.ts +3 -0
  46. package/src/i18n/en.js +3 -0
  47. package/src/legend/legendHelper.js +5 -0
  48. package/src/manager/collectionManager/CollectionComponentContent.vue +69 -5
  49. package/src/manager/collectionManager/CollectionComponentContent.vue.d.ts +3 -0
  50. package/src/manager/collectionManager/CollectionComponentList.vue +2 -24
  51. package/src/manager/collectionManager/CollectionComponentList.vue.d.ts +0 -8
  52. package/src/manager/collectionManager/collectionComponentClass.d.ts +44 -1
  53. package/src/manager/collectionManager/collectionComponentClass.js +152 -2
  54. package/src/manager/panel/PanelComponent.vue +9 -1
  55. package/src/state.d.ts +14 -0
  56. package/src/state.js +60 -6
  57. package/src/vcsUiApp.d.ts +9 -36
  58. package/src/vcsUiApp.js +45 -11
  59. package/dist/assets/ui-ffa735dc.css +0 -1
  60. /package/dist/assets/core-workers/{panoramaImageWorker.js-a3ad230c.js → panoramaImageWorker.js-a6bb36f6.js} +0 -0
  61. /package/dist/assets/{vue-09a72820.js → vue-cbd2bd60.js} +0 -0
  62. /package/dist/assets/{vuetify-def19d80.css → vuetify-8e1623b2.css} +0 -0
@@ -1,6 +1,7 @@
1
1
  export default WMSGroupContentTreeItem;
2
2
  export type WMSGroupContentTreeItemOptions = import('./contentTreeItem.js').ContentTreeItemOptions & {
3
3
  layerName: string;
4
+ loadOnStartup?: boolean;
4
5
  showWhenNotSupported?: boolean;
5
6
  setWMSLayersExclusive?: boolean;
6
7
  hideStyleSelector?: boolean;
@@ -21,12 +22,13 @@ export type WMSEntry = {
21
22
  };
22
23
  /**
23
24
  * @typedef {import('./contentTreeItem.js').ContentTreeItemOptions &
24
- * { layerName: string, showWhenNotSupported?: boolean, setWMSLayersExclusive?:boolean, hideStyleSelector?:boolean, allowedWMSLayers?:string[]}} WMSGroupContentTreeItemOptions
25
- * @property {boolean} showWhenNotSupported - optional flag to show the item even if it is not supported by the activeMap.
25
+ * { layerName: string, loadOnStartup?: boolean, showWhenNotSupported?: boolean, setWMSLayersExclusive?:boolean, hideStyleSelector?:boolean, allowedWMSLayers?:string[]}} WMSGroupContentTreeItemOptions
26
26
  * @property {string} layerName - The name of the WMSLayer to show the children of.
27
+ * @property {boolean} [loadOnStartup=false] - optional flag to load the capabilities on startup, will be loaded on click otherwise. Leads to an inherent "initOpen" flag if not true.
28
+ * @property {boolean} [showWhenNotSupported=false] - optional flag to show the item even if it is not supported by the activeMap.
27
29
  * @property {boolean} [setWMSLayersExclusive=false] - Whether the WMSlayers are mutually exclusive.
28
30
  * @property {boolean} [hideStyleSelector=false] - Whether the layer style can be selected. Will add a StyleSelector action to compatible items if the Layer has more than one style.
29
- * @property {string[]} allowedWMSLayers - The list of layers to be shown, other available layers will not be shown.
31
+ * @property {string[]} [allowedWMSLayers] - The list of layers to be shown, other available layers will not be shown.
30
32
  */
31
33
  /**
32
34
  * @typedef {Object} WMSStyleEntry
@@ -61,6 +63,11 @@ declare class WMSGroupContentTreeItem extends VcsObjectContentTreeItem<import(".
61
63
  * @private
62
64
  */
63
65
  private _layerName;
66
+ /**
67
+ * @type {boolean}
68
+ * @private
69
+ */
70
+ private _loadOnStartup;
64
71
  /**
65
72
  * @type {boolean}
66
73
  * @private
@@ -115,6 +122,12 @@ declare class WMSGroupContentTreeItem extends VcsObjectContentTreeItem<import(".
115
122
  * @private
116
123
  */
117
124
  private _pauseStateChangedListener;
125
+ /**
126
+ * Flag to track if children have been loaded
127
+ * @type {boolean}
128
+ * @private
129
+ */
130
+ private _childrenLoaded;
118
131
  /**
119
132
  * readonly access, do not manipulate the entries directly.
120
133
  * @type {Array<WMSEntry>}
@@ -157,6 +170,12 @@ declare class WMSGroupContentTreeItem extends VcsObjectContentTreeItem<import(".
157
170
  * @private
158
171
  */
159
172
  private _setStateFromLayer;
173
+ /**
174
+ * Loads WMS entries and creates child items
175
+ * @returns {Promise<void>}
176
+ * @private
177
+ */
178
+ private _loadWMSChildren;
160
179
  /**
161
180
  * @returns {Promise<void>}
162
181
  * @private
@@ -112,12 +112,13 @@ function createWMSChildContentTreeItem(
112
112
 
113
113
  /**
114
114
  * @typedef {import('./contentTreeItem.js').ContentTreeItemOptions &
115
- * { layerName: string, showWhenNotSupported?: boolean, setWMSLayersExclusive?:boolean, hideStyleSelector?:boolean, allowedWMSLayers?:string[]}} WMSGroupContentTreeItemOptions
116
- * @property {boolean} showWhenNotSupported - optional flag to show the item even if it is not supported by the activeMap.
115
+ * { layerName: string, loadOnStartup?: boolean, showWhenNotSupported?: boolean, setWMSLayersExclusive?:boolean, hideStyleSelector?:boolean, allowedWMSLayers?:string[]}} WMSGroupContentTreeItemOptions
117
116
  * @property {string} layerName - The name of the WMSLayer to show the children of.
117
+ * @property {boolean} [loadOnStartup=false] - optional flag to load the capabilities on startup, will be loaded on click otherwise. Leads to an inherent "initOpen" flag if not true.
118
+ * @property {boolean} [showWhenNotSupported=false] - optional flag to show the item even if it is not supported by the activeMap.
118
119
  * @property {boolean} [setWMSLayersExclusive=false] - Whether the WMSlayers are mutually exclusive.
119
120
  * @property {boolean} [hideStyleSelector=false] - Whether the layer style can be selected. Will add a StyleSelector action to compatible items if the Layer has more than one style.
120
- * @property {string[]} allowedWMSLayers - The list of layers to be shown, other available layers will not be shown.
121
+ * @property {string[]} [allowedWMSLayers] - The list of layers to be shown, other available layers will not be shown.
121
122
  */
122
123
 
123
124
  /**
@@ -154,7 +155,7 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
154
155
  * @param {import("../vcsUiApp.js").default} app
155
156
  */
156
157
  constructor(options, app) {
157
- super(options, app);
158
+ super({ initOpen: !options.loadOnStartup, ...options }, app);
158
159
 
159
160
  /**
160
161
  * @type {string}
@@ -162,6 +163,12 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
162
163
  */
163
164
  this._layerName = options.layerName;
164
165
 
166
+ /**
167
+ * @type {boolean}
168
+ * @private
169
+ */
170
+ this._loadOnStartup = parseBoolean(options.loadOnStartup, false);
171
+
165
172
  /**
166
173
  * @type {boolean}
167
174
  * @private
@@ -180,7 +187,7 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
180
187
  false,
181
188
  );
182
189
  // if WMSLayers should be handled exclusive, the item should handle like a NodeContentTreeItem.
183
- this.clickable = !this._setWMSLayersExclusive;
190
+ this.clickable = !this._setWMSLayersExclusive || !this._loadOnStartup;
184
191
 
185
192
  /**
186
193
  * @type {boolean}
@@ -188,9 +195,10 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
188
195
  */
189
196
  this._hideStyleSelector = parseBoolean(options.hideStyleSelector, false);
190
197
 
191
- this.state = this._setWMSLayersExclusive
192
- ? StateActionState.NONE
193
- : StateActionState.INACTIVE;
198
+ this.state =
199
+ this._setWMSLayersExclusive || !this._loadOnStartup
200
+ ? StateActionState.NONE
201
+ : StateActionState.INACTIVE;
194
202
 
195
203
  /**
196
204
  * @type {boolean}
@@ -238,6 +246,13 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
238
246
  */
239
247
  this._pauseStateChangedListener = false;
240
248
 
249
+ /**
250
+ * Flag to track if children have been loaded
251
+ * @type {boolean}
252
+ * @private
253
+ */
254
+ this._childrenLoaded = false;
255
+
241
256
  this._setup();
242
257
  }
243
258
 
@@ -421,6 +436,10 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
421
436
  this._listeners.splice(0);
422
437
  this._availableWMSEntries = [];
423
438
  this._legendSet = false;
439
+ this._childrenLoaded = false;
440
+
441
+ // Reset clickable state based on initial configuration
442
+ this.clickable = !this._setWMSLayersExclusive || !this._loadOnStartup;
424
443
  }
425
444
 
426
445
  /**
@@ -463,6 +482,83 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
463
482
  }
464
483
  }
465
484
 
485
+ /**
486
+ * Loads WMS entries and creates child items
487
+ * @returns {Promise<void>}
488
+ * @private
489
+ */
490
+ async _loadWMSChildren() {
491
+ if (this._childrenLoaded || !this._layer) {
492
+ return;
493
+ }
494
+
495
+ this.state = StateActionState.LOADING;
496
+ this.clickable = !this._setWMSLayersExclusive;
497
+
498
+ try {
499
+ const availableWMSEntries = await getWMSEntries(
500
+ this._layer.url,
501
+ this._layer.parameters,
502
+ );
503
+ // check if the layer still exists, it can happen that the layer was removed while fetching the capabilities.
504
+ if (!this._layer) {
505
+ return;
506
+ }
507
+ this._availableWMSEntries = availableWMSEntries.filter((wmsEntry) => {
508
+ return this._allowedWMSLayers
509
+ ? this._allowedWMSLayers.includes(wmsEntry.name)
510
+ : true;
511
+ });
512
+ const childItems = this._availableWMSEntries.map((wmsEntry) =>
513
+ createWMSChildContentTreeItem(
514
+ this._app,
515
+ wmsEntry,
516
+ this.name,
517
+ this._hideStyleSelector,
518
+ ),
519
+ );
520
+ childItems.forEach((childItem) => {
521
+ this._app.contentTree.add(childItem);
522
+ childItem.disabled = this.disabled;
523
+ this._listeners.push(
524
+ childItem.clickedEvent.addEventListener(() => {
525
+ this._handleChildClickedEvent(childItem);
526
+ }),
527
+ childItem.styleSelected.addEventListener((style) => {
528
+ this._handleStyleSelectedEvent(childItem, style);
529
+ }),
530
+ );
531
+ });
532
+ this._childItems = childItems;
533
+ // check if we do have a legend already configured, if yes we set a flag, that we do not need to set the legend.
534
+ if (this._layer.properties.legend) {
535
+ this._legendSet = true;
536
+ }
537
+ // we need to get the Initial State from the Layer, to set the correct State to the children.
538
+ this._setStateFromLayer();
539
+ // the layer State maybe incomplete or does not fit to the wmsGroupContentTreeItem, so we need to set the State to the Layer.
540
+ this._setState();
541
+ this._setLegend();
542
+ this._childrenLoaded = true;
543
+
544
+ // Update clickable state after children are loaded
545
+ if (!this._setWMSLayersExclusive) {
546
+ this.clickable = true;
547
+ }
548
+ } catch (e) {
549
+ // if the layer is not there it has been removed while fetching the capabilities.
550
+ if (this._layer) {
551
+ this._layer.deactivate();
552
+ this.visible = false;
553
+ this._invalid = true;
554
+ }
555
+ getLogger(this.className).error(
556
+ `An error occured while fetching the ${this._layerName} capabilities:`,
557
+ e,
558
+ );
559
+ }
560
+ }
561
+
466
562
  /**
467
563
  * @returns {Promise<void>}
468
564
  * @private
@@ -489,7 +585,6 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
489
585
  if (this._showWhenNotSupported) {
490
586
  this.disabled = !isSupported;
491
587
  }
492
- this.state = StateActionState.LOADING;
493
588
  this.setPropertiesFromObject(this._layer);
494
589
 
495
590
  this._listeners.push(
@@ -517,67 +612,18 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
517
612
  }
518
613
  }),
519
614
  );
520
- try {
521
- const availableWMSEntries = await getWMSEntries(
522
- this._layer.url,
523
- this._layer.parameters,
524
- );
525
- // check if the layer still exists, it can happen that the layer was removed while fetching the capabilities.
526
- if (!this._layer) {
527
- return;
528
- }
529
- this._availableWMSEntries = availableWMSEntries.filter((wmsEntry) => {
530
- return this._allowedWMSLayers
531
- ? this._allowedWMSLayers.includes(wmsEntry.name)
532
- : true;
533
- });
534
- const childItems = this._availableWMSEntries.map((wmsEntry) => {
535
- return createWMSChildContentTreeItem(
536
- this._app,
537
- wmsEntry,
538
- this.name,
539
- this._hideStyleSelector,
540
- );
541
- });
542
- childItems.forEach((childItem) => {
543
- this._app.contentTree.add(childItem);
544
- childItem.disabled = this.disabled;
545
- this._listeners.push(
546
- childItem.clickedEvent.addEventListener(() => {
547
- this._handleChildClickedEvent(childItem);
548
- }),
549
- childItem.styleSelected.addEventListener((style) => {
550
- this._handleStyleSelectedEvent(childItem, style);
551
- }),
552
- );
553
- });
554
- this._childItems = childItems;
555
- // check if we do have a legend already configured, if yes we set a flag, that we do not need to set the legend.
556
- if (this._layer.properties.legend) {
557
- this._legendSet = true;
558
- }
559
- // we need to get the Initial State from the Layer, to set the correct State to the children.
560
- this._setStateFromLayer();
561
- // the layer State maybe incomplete or does not fit to the wmsGroupContentTreeItem, so we need to set the State to the Layer.
562
- this._setState();
563
- this._setLegend();
564
- } catch (e) {
565
- // if the layer is not there it has been removed while fetching the capabilities.
566
- if (this._layer) {
567
- this._layer.deactivate();
568
- this.visible = false;
569
- this._invalid = true;
570
- }
571
- getLogger(this.className).error(
572
- `An error occured while fetching the ${this._layerName} capabilities:`,
573
- e,
574
- );
615
+ if (this._loadOnStartup) {
616
+ await this._loadWMSChildren();
575
617
  }
576
618
  }
577
619
  }
578
620
 
579
621
  async clicked() {
580
622
  await super.clicked();
623
+ if (!this._childrenLoaded) {
624
+ await this._loadWMSChildren();
625
+ return;
626
+ }
581
627
  if (this.state === StateActionState.NONE || this._setWMSLayersExclusive) {
582
628
  return;
583
629
  }
@@ -591,12 +637,28 @@ class WMSGroupContentTreeItem extends VcsObjectContentTreeItem {
591
637
  await this._setStateToLayer();
592
638
  }
593
639
 
640
+ /**
641
+ * Returns a readonly TreeViewItem used for rendering the current item.
642
+ * @returns {import('./contentTreeItem.js').TreeViewItem}
643
+ */
644
+ getTreeViewItem() {
645
+ const item = super.getTreeViewItem();
646
+ item.forceNodeDisplay = !this._loadOnStartup;
647
+ return item;
648
+ }
594
649
  /**
595
650
  * @returns {WMSGroupContentTreeItemOptions}
596
651
  */
597
652
  toJSON() {
598
653
  const config = super.toJSON();
599
654
  config.layerName = this._layerName;
655
+ const defaultInitOpen = !this._loadOnStartup;
656
+ if (this.initOpen !== defaultInitOpen) {
657
+ config.initOpen = this.initOpen;
658
+ }
659
+ if (this._loadOnStartup) {
660
+ config.loadOnStartup = this._loadOnStartup;
661
+ }
600
662
  if (this._showWhenNotSupported) {
601
663
  config.showWhenNotSupported = this._showWhenNotSupported;
602
664
  }
@@ -15,9 +15,23 @@ export type BalloonFeatureInfoViewProps = import("./abstractFeatureInfoView.js")
15
15
  balloonTitle: string;
16
16
  balloonSubtitle: string;
17
17
  position: import("ol/coordinate.js").Coordinate;
18
- heightReference: HeightReference;
18
+ heightReference: import("@vcmap-cesium/engine").HeightReference;
19
19
  heightOffset: number;
20
20
  };
21
+ /**
22
+ * @typedef {import("./abstractFeatureInfoView.js").FeatureInfoViewOptions & { balloonTitle?: string, balloonSubtitle?: string }} BalloonFeatureInfoViewOptions
23
+ * @property {string} [balloonTitle] - optional title to overwrite default (layerName). Can be attribute key (nested key using '.'), i18n key or text
24
+ * @property {string} [balloonSubtitle] - optional window title to overwrite default (featureId). Can be attribute key (nested key using '.'), i18n key or text
25
+ */
26
+ /**
27
+ * @typedef {import("./abstractFeatureInfoView.js").FeatureInfoProps & {
28
+ * balloonTitle: string,
29
+ * balloonSubtitle: string,
30
+ * position: import("ol/coordinate.js").Coordinate
31
+ * heightReference: import("@vcmap-cesium/engine").HeightReference,
32
+ * heightOffset: number
33
+ * }} BalloonFeatureInfoViewProps
34
+ */
21
35
  /**
22
36
  * @class
23
37
  * @description An balloon view.
@@ -48,5 +62,4 @@ declare class BalloonFeatureInfoView extends AbstractFeatureInfoView {
48
62
  */
49
63
  toJSON(): BalloonFeatureInfoViewOptions;
50
64
  }
51
- import { HeightReference } from '@vcmap-cesium/engine';
52
65
  import AbstractFeatureInfoView from './abstractFeatureInfoView.js';
@@ -1,24 +1,8 @@
1
- import { Feature } from 'ol';
2
- import { getCenter } from 'ol/extent.js';
3
- import Point from 'ol/geom/Point.js';
4
- import {
5
- Cartographic,
6
- Entity,
7
- HeightReference,
8
- Math as CesiumMath,
9
- } from '@vcmap-cesium/engine';
10
- import {
11
- getGeometryHeight,
12
- getHeightInfo,
13
- isAbsoluteHeightReference,
14
- isRelativeHeightReference,
15
- Projection,
16
- VectorProperties,
17
- } from '@vcmap/core';
18
1
  import { check } from '@vcsuite/check';
19
2
  import AbstractFeatureInfoView from './abstractFeatureInfoView.js';
20
3
  import { WindowSlot } from '../manager/window/windowManager.js';
21
4
  import BalloonComponent from './BalloonComponent.vue';
5
+ import { getBalloonPositionFromFeature } from './balloonHelper.js';
22
6
 
23
7
  /**
24
8
  * derive value from attributes
@@ -47,91 +31,11 @@ export function extractNestedKey(key, attrs, defaultValue = null) {
47
31
  * balloonTitle: string,
48
32
  * balloonSubtitle: string,
49
33
  * position: import("ol/coordinate.js").Coordinate
50
- * heightReference: HeightReference,
34
+ * heightReference: import("@vcmap-cesium/engine").HeightReference,
51
35
  * heightOffset: number
52
36
  * }} BalloonFeatureInfoViewProps
53
37
  */
54
38
 
55
- /**
56
- * @param {import("@vcmap/core").Cartesian3} cartesian
57
- * @returns {import("ol/coordinate.js").Coordinate}
58
- */
59
- function cartesian3ToCoordinate(cartesian) {
60
- const cartographic = Cartographic.fromCartesian(cartesian);
61
- const wgs84position = [
62
- CesiumMath.toDegrees(cartographic.longitude),
63
- CesiumMath.toDegrees(cartographic.latitude),
64
- cartographic.height,
65
- ];
66
- return Projection.wgs84ToMercator(wgs84position);
67
- }
68
-
69
- /**
70
- * @param {FeatureType} feature
71
- * @param {import("@vcmap/core").Layer} layer
72
- * @param {import("ol/coordinate.js").Coordinate|undefinded} clickedPosition
73
- * @returns {{position?: import("ol/coordinate.js").Coordinate, heightReference: HeightReference, heightOffset: number}}
74
- */
75
- function getPositionFromFeature(feature, layer, clickedPosition) {
76
- let heightReference = HeightReference.NONE;
77
- let heightOffset = 0;
78
- let position = null;
79
- if (feature instanceof Feature && feature.getGeometry() instanceof Point) {
80
- const point = feature.getGeometry();
81
- const vectorProperties =
82
- layer.vectorProperties ??
83
- layer.featureProvider?.vectorProperties ??
84
- new VectorProperties();
85
- const renderAs = vectorProperties.renderAs(feature);
86
- if (renderAs === 'geometry') {
87
- // special case where we do not want to use the clickedPosition but the exact Position of the Point
88
- const heightInfo = getHeightInfo(feature, point, vectorProperties);
89
- ({ heightReference } = heightInfo);
90
- let height = clickedPosition?.[2] ?? 0;
91
- position = point.getCoordinates();
92
- // if clamped, do nothing
93
- if (isRelativeHeightReference(heightReference)) {
94
- height = getGeometryHeight(point, heightInfo);
95
- if (heightInfo.groundLevel != null) {
96
- // we have a groundLevel, so no need to clamp the point
97
- heightReference = HeightReference.NONE;
98
- }
99
- if (heightInfo.heightAboveGround != null) {
100
- heightOffset += heightInfo.heightAboveGround;
101
- }
102
- const extrudedHeight = heightInfo.storeyHeightsAboveGround.reduce(
103
- (acc, storeyHeight) => acc + storeyHeight,
104
- 0,
105
- );
106
- heightOffset += extrudedHeight;
107
- height += extrudedHeight;
108
- position = [position[0], position[1], height];
109
- } else if (isAbsoluteHeightReference(heightReference)) {
110
- const extrudedHeight = heightInfo.storeyHeightsAboveGround.reduce(
111
- (acc, storeyHeight) => acc + storeyHeight,
112
- 0,
113
- );
114
- position = [
115
- position[0],
116
- position[1],
117
- heightInfo.groundLevelOrMinHeight + extrudedHeight,
118
- ];
119
- }
120
- return { position, heightOffset, heightReference };
121
- }
122
- }
123
- if (clickedPosition) {
124
- position = clickedPosition.slice();
125
- } else if (feature instanceof Feature && feature.getGeometry()) {
126
- position = getCenter(feature.getGeometry().getExtent());
127
- } else if (feature instanceof Entity) {
128
- position = cartesian3ToCoordinate(feature.position);
129
- } else if (feature?.primitive?.boundingSphere?.center) {
130
- position = cartesian3ToCoordinate(feature.primitive.boundingSphere.center);
131
- }
132
- return { position, heightReference, heightOffset };
133
- }
134
-
135
39
  /**
136
40
  * @class
137
41
  * @description An balloon view.
@@ -170,11 +74,12 @@ class BalloonFeatureInfoView extends AbstractFeatureInfoView {
170
74
  */
171
75
  getProperties(featureInfo, layer) {
172
76
  const properties = super.getProperties(featureInfo, layer);
173
- const { position, heightReference, heightOffset } = getPositionFromFeature(
174
- featureInfo.feature,
175
- layer,
176
- featureInfo.position,
177
- );
77
+ const { position, heightReference, heightOffset } =
78
+ getBalloonPositionFromFeature(
79
+ featureInfo.feature,
80
+ layer,
81
+ featureInfo.position,
82
+ );
178
83
  return {
179
84
  ...properties,
180
85
  position,
@@ -20,6 +20,17 @@ export function setBalloonPosition(windowManager: import("../manager/window/wind
20
20
  * @returns {Promise<(() => void)>}
21
21
  */
22
22
  export function setupBalloonPositionListener(vcsApp: import("@vcmap/core").VcsApp, windowId: string, clickedPosition: import("ol/coordinate.js").Coordinate): Promise<(() => void)>;
23
+ /**
24
+ * @param {Feature} feature
25
+ * @param {import("@vcmap/core").Layer} layer
26
+ * @param {import("ol/coordinate.js").Coordinate|undefined} clickedPosition
27
+ * @returns {{position?: import("ol/coordinate.js").Coordinate, heightReference: HeightReference, heightOffset: number}}
28
+ */
29
+ export function getBalloonPositionFromFeature(feature: Feature, layer: import("@vcmap/core").Layer, clickedPosition: import("ol/coordinate.js").Coordinate | undefined): {
30
+ position?: import("ol/coordinate.js").Coordinate;
31
+ heightReference: HeightReference;
32
+ heightOffset: number;
33
+ };
23
34
  /**
24
35
  * balloon offset from location or click position in pixel
25
36
  * @type {{x: number, y: number}}
@@ -29,3 +40,5 @@ export const balloonOffset: {
29
40
  y: number;
30
41
  };
31
42
  import { Cartesian2 } from '@vcmap-cesium/engine';
43
+ import Feature from 'ol/Feature.js';
44
+ import { HeightReference } from '@vcmap-cesium/engine';
@@ -2,15 +2,26 @@ import {
2
2
  Cartesian2,
3
3
  Cartographic,
4
4
  SceneTransforms,
5
+ HeightReference,
6
+ Entity,
5
7
  } from '@vcmap-cesium/engine';
6
8
  import {
9
+ cartesianToMercator,
7
10
  CesiumMap,
11
+ getGeometryHeight,
12
+ getHeightInfo,
13
+ isAbsoluteHeightReference,
14
+ isRelativeHeightReference,
8
15
  ObliqueMap,
9
16
  OpenlayersMap,
10
17
  Projection,
11
18
  transformToImage,
19
+ VectorProperties,
12
20
  } from '@vcmap/core';
13
21
  import { unByKey } from 'ol/Observable.js';
22
+ import Feature from 'ol/Feature.js';
23
+ import { Point } from 'ol/geom.js';
24
+ import { getCenter } from 'ol/extent.js';
14
25
  import {
15
26
  getWindowPositionOptionsFromMapEvent,
16
27
  WindowAlignment,
@@ -173,3 +184,69 @@ export async function setupBalloonPositionListener(
173
184
 
174
185
  return destroy;
175
186
  }
187
+
188
+ /**
189
+ * @param {Feature} feature
190
+ * @param {import("@vcmap/core").Layer} layer
191
+ * @param {import("ol/coordinate.js").Coordinate|undefined} clickedPosition
192
+ * @returns {{position?: import("ol/coordinate.js").Coordinate, heightReference: HeightReference, heightOffset: number}}
193
+ */
194
+ export function getBalloonPositionFromFeature(feature, layer, clickedPosition) {
195
+ let heightReference = HeightReference.NONE;
196
+ let heightOffset = 0;
197
+ let position = null;
198
+ if (feature instanceof Feature && feature.getGeometry() instanceof Point) {
199
+ const point = feature.getGeometry();
200
+ const vectorProperties =
201
+ layer.vectorProperties ??
202
+ layer.featureProvider?.vectorProperties ??
203
+ new VectorProperties();
204
+ const renderAs = vectorProperties.renderAs(feature);
205
+ if (renderAs === 'geometry') {
206
+ // special case where we do not want to use the clickedPosition but the exact Position of the Point
207
+ const heightInfo = getHeightInfo(feature, point, vectorProperties);
208
+ ({ heightReference } = heightInfo);
209
+ let height = clickedPosition?.[2] ?? 0;
210
+ position = point.getCoordinates();
211
+ // if clamped, do nothing
212
+ if (isRelativeHeightReference(heightReference)) {
213
+ height = getGeometryHeight(point, heightInfo);
214
+ if (heightInfo.groundLevel != null) {
215
+ // we have a groundLevel, so no need to clamp the point
216
+ heightReference = HeightReference.NONE;
217
+ }
218
+ if (heightInfo.heightAboveGround != null) {
219
+ heightOffset += heightInfo.heightAboveGround;
220
+ }
221
+ const extrudedHeight = heightInfo.storeyHeightsAboveGround.reduce(
222
+ (acc, storeyHeight) => acc + storeyHeight,
223
+ 0,
224
+ );
225
+ heightOffset += extrudedHeight;
226
+ height += extrudedHeight;
227
+ position = [position[0], position[1], height];
228
+ } else if (isAbsoluteHeightReference(heightReference)) {
229
+ const extrudedHeight = heightInfo.storeyHeightsAboveGround.reduce(
230
+ (acc, storeyHeight) => acc + storeyHeight,
231
+ 0,
232
+ );
233
+ position = [
234
+ position[0],
235
+ position[1],
236
+ heightInfo.groundLevelOrMinHeight + extrudedHeight,
237
+ ];
238
+ }
239
+ return { position, heightOffset, heightReference };
240
+ }
241
+ }
242
+ if (clickedPosition) {
243
+ position = clickedPosition.slice();
244
+ } else if (feature instanceof Feature && feature.getGeometry()) {
245
+ position = getCenter(feature.getGeometry().getExtent());
246
+ } else if (feature instanceof Entity) {
247
+ position = cartesianToMercator(feature.position);
248
+ } else if (feature?.primitive?.boundingSphere?.center) {
249
+ position = cartesianToMercator(feature.primitive.boundingSphere.center);
250
+ }
251
+ return { position, heightReference, heightOffset };
252
+ }
@@ -225,9 +225,10 @@ declare class FeatureInfo extends Collection<AbstractFeatureInfoView> {
225
225
  * The cluster feature will be cloned, highlighted and added on an internal scratch layer to ensure availability until deselection.
226
226
  * The original cluster feature will be hidden until deselection.
227
227
  * @param {import("ol").Feature} clusterFeature
228
+ * @param {import("ol/coordinate.js").Coordinate} position
228
229
  * @returns {Promise<void>}
229
230
  */
230
- selectClusterFeature(clusterFeature: import("ol").Feature): Promise<void>;
231
+ selectClusterFeature(clusterFeature: import("ol").Feature, position: import("ol/coordinate.js").Coordinate): Promise<void>;
231
232
  /**
232
233
  * Clears the current feature. remove window, highlighting and provided feature.
233
234
  * @private
@@ -296,7 +296,10 @@ function setupFeatureInfoTool(app) {
296
296
  });
297
297
 
298
298
  function addFeatureInfoButton() {
299
- if (app.uiConfig.getByKey('startingFeatureInfo')?.value !== false) {
299
+ if (
300
+ app.uiConfig.getByKey('startingFeatureInfo')?.value !== false &&
301
+ !action.active
302
+ ) {
300
303
  action.callback();
301
304
  }
302
305
  if (!app.toolboxManager.has('featureInfo')) {
@@ -754,9 +757,10 @@ class FeatureInfo extends Collection {
754
757
  * The cluster feature will be cloned, highlighted and added on an internal scratch layer to ensure availability until deselection.
755
758
  * The original cluster feature will be hidden until deselection.
756
759
  * @param {import("ol").Feature} clusterFeature
760
+ * @param {import("ol/coordinate.js").Coordinate} position
757
761
  * @returns {Promise<void>}
758
762
  */
759
- async selectClusterFeature(clusterFeature) {
763
+ async selectClusterFeature(clusterFeature, position) {
760
764
  this.clearFeature();
761
765
  this._clearClusterInternal();
762
766
  const id = `cluster-at-${clusterFeature.getGeometry().getCoordinates().join('-')}`;
@@ -800,7 +804,11 @@ class FeatureInfo extends Collection {
800
804
  this._scratchLayer.addFeatures([clonedFeature]);
801
805
 
802
806
  const features = clusterFeature.get('features');
803
- const { items, groups } = getGroupedFeatureList(this._app, features);
807
+ const { items, groups } = getGroupedFeatureList(
808
+ this._app,
809
+ features,
810
+ position,
811
+ );
804
812
 
805
813
  this._clusterWindowId = id;
806
814
  this._app.windowManager.add(