@vcmap/ui 5.0.0-rc.10 → 5.0.0-rc.11

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 (116) hide show
  1. package/README.md +11 -4
  2. package/build/build.js +0 -3
  3. package/build/buildHelpers.js +0 -1
  4. package/build/buildPreview.js +7 -0
  5. package/config/aerowest.config.json +13 -3
  6. package/config/base.config.json +89 -64
  7. package/config/codes.config.json +397 -0
  8. package/config/dev.config.json +169 -0
  9. package/config/graphFeatureInfo.config.json +100 -0
  10. package/config/www.config.json +1232 -0
  11. package/dist/assets/{cesium.eb5667.js → cesium.e67536.js} +0 -0
  12. package/dist/assets/cesium.js +1 -1
  13. package/dist/assets/core.ebf665.js +4 -0
  14. package/dist/assets/core.js +1 -1
  15. package/dist/assets/{index.4ccd4433.js → index.9b213929.js} +1 -1
  16. package/dist/assets/{ol.ef03b1.js → ol.8bbd50.js} +0 -0
  17. package/dist/assets/ol.js +1 -1
  18. package/dist/assets/ui.fdfe0d.css +1 -0
  19. package/dist/assets/ui.fdfe0d.js +68 -0
  20. package/dist/assets/ui.js +1 -1
  21. package/dist/assets/vue.0bb7c6.js +9 -0
  22. package/dist/assets/vue.js +2 -1
  23. package/dist/assets/{vuetify.401a29.css → vuetify.53300f.css} +1 -1
  24. package/dist/assets/{vuetify.401a29.js → vuetify.53300f.js} +71 -71
  25. package/dist/assets/vuetify.js +2 -2
  26. package/dist/index.html +1 -1
  27. package/index.js +36 -5
  28. package/lib/vue.js +1 -0
  29. package/map.config.json +15 -6
  30. package/package.json +6 -7
  31. package/plugins/@vcmap/create-link/fallbackCreateLink.vue +71 -0
  32. package/plugins/@vcmap/create-link/index.js +83 -0
  33. package/plugins/@vcmap/create-link/package.json +6 -0
  34. package/plugins/@vcmap/pluginExample/index.js +1 -1
  35. package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +20 -3
  36. package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +1 -1
  37. package/plugins/@vcmap/project-selector/index.js +1 -1
  38. package/plugins/@vcmap/project-selector/package.json +1 -2
  39. package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +1 -1
  40. package/plugins/@vcmap/theme-changer/index.js +1 -1
  41. package/plugins/@vcmap/theme-changer/package.json +1 -2
  42. package/plugins/categoryTest/Categories.vue +89 -1
  43. package/plugins/categoryTest/Category.vue +1 -1
  44. package/plugins/simple-graph/README.md +51 -0
  45. package/plugins/simple-graph/SimpleGraphComponent.vue +70 -0
  46. package/plugins/simple-graph/index.js +17 -0
  47. package/plugins/simple-graph/package.json +11 -0
  48. package/plugins/simple-graph/simpleGraphView.js +76 -0
  49. package/plugins/test/editor.vue +1 -1
  50. package/plugins/test/index.js +63 -2
  51. package/plugins/test/windowManagerExample.vue +1 -1
  52. package/src/actions/stateRefAction.js +2 -2
  53. package/src/actions/styleSelector.vue +1 -1
  54. package/src/application/Navbar.vue +13 -2
  55. package/src/application/VcsApp.vue +201 -92
  56. package/src/application/VcsMap.vue +1 -1
  57. package/src/application/VcsSettings.vue +1 -1
  58. package/src/application/vcsAppWrapper.vue +1 -0
  59. package/src/components/form-inputs-controls/VcsCheckbox.vue +13 -0
  60. package/src/components/form-inputs-controls/VcsColorPicker.vue +1 -1
  61. package/src/components/form-inputs-controls/VcsRadio.vue +123 -0
  62. package/src/components/form-output/VcsFormattedNumber.vue +1 -1
  63. package/src/components/lists/VcsActionList.vue +13 -4
  64. package/src/components/lists/VcsTreeview.vue +4 -4
  65. package/src/components/lists/VcsTreeviewLeaf.vue +9 -2
  66. package/src/components/lists/VcsTreeviewSearchbar.vue +1 -2
  67. package/src/components/tables/VcsTable.vue +245 -0
  68. package/src/contentTree/LayerTree.vue +1 -1
  69. package/src/contentTree/contentTreeCollection.js +4 -4
  70. package/src/contentTree/contentTreeItem.js +9 -9
  71. package/src/contentTree/groupContentTreeItem.js +1 -1
  72. package/src/contentTree/layerContentTreeItem.js +15 -1
  73. package/src/contentTree/layerGroupContentTreeItem.js +21 -1
  74. package/src/contentTree/nodeContentTreeItem.js +1 -1
  75. package/src/featureInfo/AddressBalloonComponent.vue +47 -0
  76. package/src/featureInfo/BalloonComponent.vue +138 -0
  77. package/src/featureInfo/abstractFeatureInfoView.js +313 -0
  78. package/src/featureInfo/addressBalloonFeatureInfoView.js +118 -0
  79. package/src/featureInfo/balloonFeatureInfoView.js +151 -0
  80. package/src/featureInfo/balloonHelper.js +132 -0
  81. package/src/featureInfo/featureInfo.js +455 -0
  82. package/src/featureInfo/featureInfoInteraction.js +42 -0
  83. package/src/featureInfo/iframeFeatureInfoView.js +95 -0
  84. package/src/featureInfo/tableFeatureInfoView.js +106 -0
  85. package/src/i18n/de.js +16 -0
  86. package/src/i18n/en.js +16 -0
  87. package/src/i18n/i18nCollection.js +17 -0
  88. package/src/manager/buttonManager.js +5 -5
  89. package/src/manager/categoryManager/ComponentsManager.vue +30 -0
  90. package/src/manager/categoryManager/categoryManager.js +500 -0
  91. package/src/manager/contextMenu/contextMenuComponent.vue +43 -0
  92. package/src/manager/contextMenu/contextMenuInteraction.js +42 -0
  93. package/src/manager/contextMenu/contextMenuManager.js +197 -0
  94. package/src/manager/navbarManager.js +8 -8
  95. package/src/manager/toolbox/ToolboxManager.vue +2 -2
  96. package/src/manager/toolbox/toolboxManager.js +7 -3
  97. package/src/manager/window/WindowComponent.vue +1 -1
  98. package/src/manager/window/WindowManager.vue +5 -3
  99. package/src/manager/window/windowManager.js +118 -14
  100. package/src/navigation/mapNavigation.vue +3 -5
  101. package/src/navigation/overviewMap.js +28 -5
  102. package/src/navigation/vcsCompass.vue +1 -1
  103. package/src/setup.js +0 -2
  104. package/src/state.js +256 -0
  105. package/src/styles/_theming.scss +0 -5
  106. package/src/uiConfig.js +79 -0
  107. package/src/vcsUiApp.js +210 -20
  108. package/src/vuePlugins/vuetify.js +14 -4
  109. package/config/berlin.config.json +0 -510
  110. package/dist/assets/core.216494.js +0 -4
  111. package/dist/assets/ui.99a1a7.css +0 -1
  112. package/dist/assets/ui.99a1a7.js +0 -70
  113. package/dist/assets/vue-composition-api.c5aca1.js +0 -14
  114. package/dist/assets/vue-composition-api.js +0 -2
  115. package/dist/assets/vue.762edd.js +0 -9
  116. package/lib/vue-composition-api.js +0 -2
@@ -0,0 +1,313 @@
1
+ import { VcsObject } from '@vcmap/core';
2
+ import { WindowSlot } from '../manager/window/windowManager.js';
3
+
4
+ /**
5
+ * @typedef {Object} FeatureInfoProps
6
+ * @property {string} featureId
7
+ * @property {string} layerName
8
+ * @property {Object} layerProperties
9
+ * @property {Object} attributes
10
+ */
11
+
12
+ /**
13
+ * @typedef {import("@vcmap/core").VcsObjectOptions} FeatureInfoViewOptions
14
+ * @property {Array<string>} [attributeKeys] - list of keys to filter attributes of selected feature
15
+ * @property {Object<string,string>} [keyMapping] - object providing text replacements or i18n strings for attribute keys
16
+ * @property {Object<string, string|Object<string,string>>} [valueMapping] - object providing text replacements or i18n strings for attribute values
17
+ * @property {WindowComponentOptions} [window] - state, slot, position can be set. Other options are predefined.
18
+ */
19
+
20
+ /**
21
+ * @param {string|Object<string,string>} mappedValue
22
+ * @param {string} value
23
+ * @returns {string}
24
+ */
25
+ function getMappedValue(mappedValue, value) {
26
+ if (typeof mappedValue === 'string') {
27
+ return mappedValue.replace(/\${value}/g, value);
28
+ }
29
+ return mappedValue[value] ?? value;
30
+ }
31
+
32
+ /**
33
+ * Replaces values by new values according to mapping table. Nested keys are represented by a "."
34
+ * @param {Object<string, *>} attributes
35
+ * @param {Object<string, string|Object<string,string>>} mapping - value mapping
36
+ */
37
+ export function applyValueMapping(attributes, mapping) {
38
+ Object.keys(mapping)
39
+ .sort((a, b) => {
40
+ const aLen = a.split('.').length;
41
+ const bLen = b.split('.').length;
42
+ if (aLen > bLen) {
43
+ return -1;
44
+ }
45
+ if (bLen > aLen) {
46
+ return 1;
47
+ }
48
+ return 0;
49
+ })
50
+ .forEach((mappingKey) => {
51
+ if (Object.hasOwn(attributes, mappingKey)) {
52
+ attributes[mappingKey] = getMappedValue(mapping[mappingKey], attributes[mappingKey]);
53
+ } else {
54
+ const mappingKeys = mappingKey.split('.');
55
+ mappingKeys.reduce((obj, key, index) => {
56
+ if (obj && Object.hasOwn(obj, key) && index === mappingKeys.length - 1) {
57
+ obj[key] = getMappedValue(mapping[mappingKey], obj[key]);
58
+ }
59
+ return obj?.[key];
60
+ }, attributes);
61
+ }
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Replaces keys by new keys according to mapping table.
67
+ * Nested keys to replace are represented by a ".". Keys will
68
+ * be replaced by the given string literal. This will always lead
69
+ * to a new top level key or an overwritting of an existing key.
70
+ * Deletes old keys!
71
+ * @example
72
+ * const getAttrs = () => { foo: { bar: true }, 'foo.baz': true };
73
+ * const nestedMapping = { 'foo.bar': 'bar' };
74
+ * const nestedMappingWithPeriod = { 'foo.bar': 'bar.foo' };
75
+ * const flatMapping = { 'foo': 'bar' };
76
+ * const periodMapping = { 'foo.baz': 'foo' };
77
+ * // apply nested key mapping will replace nested key with top level key
78
+ * const nestedAttrs = applyKeyMapping(getAttrs(), nestedMapping);
79
+ * assert(nestedAttrs.bar === true);
80
+ * // apply nested key mapping with a new key with a period. will replace nested key with a top level key.
81
+ * const nestedAttrsWithPeriod = applyKeyMapping(getAttrs(), nestedMappingWithPeriod);
82
+ * assert(nestedAttrsWithPeriod['bar.foo'] === true);
83
+ * // apply flat mapping: will replace top level key with another top level key
84
+ * const flatAttrs = applyKeyMapping(getAttrs(), flatMapping);
85
+ * assert(flatAttrs.bar.bar === true);
86
+ * // apply flat mapping of a key with a period. this will overwrite an existing _other_ key
87
+ * const periodAttrs = applyKeyMapping(getAttrs(), periodMapping);
88
+ * assert(periodAttrs.foo === true);
89
+ * @param {Object<string, *>} attributes
90
+ * @param {Object<string,string>} mapping - key mapping
91
+ */
92
+ export function applyKeyMapping(attributes, mapping) {
93
+ Object.keys(mapping)
94
+ .sort((a, b) => {
95
+ const aLen = a.split('.').length;
96
+ const bLen = b.split('.').length;
97
+ if (aLen > bLen) {
98
+ return -1;
99
+ }
100
+ if (bLen > aLen) {
101
+ return 1;
102
+ }
103
+ return 0;
104
+ })
105
+ .forEach((mappingKey) => {
106
+ if (Object.hasOwn(attributes, mappingKey)) {
107
+ attributes[mapping[mappingKey]] = attributes[mappingKey];
108
+ delete attributes[mappingKey];
109
+ } else {
110
+ const mappingKeys = mappingKey.split('.');
111
+ mappingKeys.reduce((obj, key, index) => {
112
+ if (obj && Object.hasOwn(obj, key) && index === mappingKeys.length - 1) {
113
+ attributes[mapping[mappingKey]] = obj[key];
114
+ delete obj[key];
115
+ }
116
+ return obj?.[key];
117
+ }, attributes);
118
+ }
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Applies an attribute filtering. Nested attributes are represented by a ".".
124
+ * @example
125
+ * const attrs = { foo: { bar: true, baz: false }, bar: true, baz: true, foobar: { foo: true, bar: true } };
126
+ * const filter = ["foo.bar", "baz", "foobar"];
127
+ * const filtered = applyAttributeFilter(attrs, filter)
128
+ * // nested keys will also filter for their parent
129
+ * assert(filtered.foo.bar === true);
130
+ * // only keys filtered will be present
131
+ * assert(filtered.foo.baz === undefined);
132
+ * assert(filtered.bar === undefined);
133
+ * assert(filtered.baz === true);
134
+ * // if filtering parent top level keys, will pass on a reference of the actual value and its children.
135
+ * assert(deepEquals(filtered.foobar, attrs.foobar));
136
+ * @param {Object<string, *>} attributes
137
+ * @param {Array<string>} keys
138
+ * @param {Object<string, *>=} result
139
+ * @returns {Object<string, *>}
140
+ */
141
+ export function applyAttributeFilter(attributes, keys, result = {}) {
142
+ const nestedKeys = {};
143
+ keys.forEach((k) => {
144
+ if (Object.hasOwn(attributes, k)) {
145
+ result[k] = attributes[k];
146
+ } else if (k.includes('.')) {
147
+ const [parent, ...rest] = k.split('.');
148
+ if (!nestedKeys[parent]) {
149
+ nestedKeys[parent] = [];
150
+ }
151
+ nestedKeys[parent].push(rest.join('.'));
152
+ }
153
+ });
154
+
155
+ Object.entries(nestedKeys)
156
+ .forEach(([parent, pKs]) => {
157
+ if (typeof attributes[parent] === 'object') {
158
+ result[parent] = {};
159
+ applyAttributeFilter(attributes[parent], pKs, result[parent]);
160
+ }
161
+ });
162
+ return result;
163
+ }
164
+
165
+ /**
166
+ * Abstract class to be extended by FeatureInfoView classes
167
+ * Subclasses must always provide a component and may overwrite class methods.
168
+ * @abstract
169
+ * @class
170
+ * @extends {VcsObject}
171
+ */
172
+ class AbstractFeatureInfoView extends VcsObject {
173
+ /**
174
+ * @type {string}
175
+ */
176
+ static get className() { return 'AbstractFeatureInfoView'; }
177
+
178
+ /** @returns {FeatureInfoViewOptions} */
179
+ static getDefaultOptions() {
180
+ return {
181
+ attributeKeys: [],
182
+ keyMapping: undefined,
183
+ valueMapping: undefined,
184
+ window: {},
185
+ };
186
+ }
187
+
188
+ /**
189
+ * @param {FeatureInfoViewOptions} options
190
+ * @param {import("vue").Component} component
191
+ */
192
+ constructor(options, component) {
193
+ super(options);
194
+ const defaultOptions = AbstractFeatureInfoView.getDefaultOptions();
195
+ /**
196
+ * @type {string[]}
197
+ */
198
+ this.attributeKeys = options.attributeKeys || defaultOptions.attributeKeys;
199
+ /**
200
+ * @type {null|Object<string,string>}
201
+ */
202
+ this.keyMapping = options.keyMapping || defaultOptions.keyMapping;
203
+ /**
204
+ * @type {null|Object<string, string|Object<string,string>>}
205
+ */
206
+ this.valueMapping = options.valueMapping || defaultOptions.valueMapping;
207
+ /**
208
+ * @type {WindowComponentOptions|Object}
209
+ * @private
210
+ */
211
+ this._window = options.window || defaultOptions.window;
212
+ /**
213
+ * @type {import("vue").Component|undefined}
214
+ * @private
215
+ */
216
+ this._component = component;
217
+ }
218
+
219
+ /**
220
+ * window options, configured in a context, used only internally by AbstractFeatureInfoView or subclass
221
+ * @type {WindowComponentOptions|Object}
222
+ * @readonly
223
+ */
224
+ get window() { return this._window; }
225
+
226
+ /**
227
+ * component provided by a FeatureInfoView class, passed to featureInfo via `getWindowComponentOptions()`
228
+ * @type {import("vue").Component|undefined}
229
+ * @readonly
230
+ */
231
+ get component() { return this._component; }
232
+
233
+ /**
234
+ * This method returns all relevant attributes for this view.
235
+ * Called by `getProperties()` to pass attributes as props object to the VueComponent of this view.
236
+ * May be overwritten by classes extending AbstractFeatureInfoView.
237
+ * It filters attributes of the feature by keys, performs value and key mapping, if provided.
238
+ * @param {undefined|import("ol").Feature<import("ol/geom/Geometry").default>|import("@vcmap/cesium").Cesium3DTileFeature|import("@vcmap/cesium").Cesium3DTilePointFeature} feature
239
+ * @returns {Object}
240
+ */
241
+ getAttributes(feature) {
242
+ let attributes = feature.getProperty('attributes') || {};
243
+ if (this.attributeKeys.length > 0) {
244
+ attributes = applyAttributeFilter(attributes, this.attributeKeys);
245
+ }
246
+ if (this.valueMapping) {
247
+ applyValueMapping(attributes, this.valueMapping);
248
+ }
249
+ if (this.keyMapping) {
250
+ applyKeyMapping(attributes, this.keyMapping);
251
+ }
252
+ return attributes;
253
+ }
254
+
255
+ /**
256
+ * This method returns all relevant properties passed to the VueComponent of this view.
257
+ * May be overwritten by classes extending AbstractFeatureInfoView.
258
+ * Called by `getWindowComponentOptions()`.
259
+ * @param {FeatureInfoEvent} featureInfo
260
+ * @param {import("@vcmap/core").Layer} layer
261
+ * @returns {FeatureInfoProps}
262
+ */
263
+ getProperties({ feature }, layer) {
264
+ return {
265
+ featureId: feature.getId(),
266
+ layerName: layer.name,
267
+ layerProperties: layer.properties,
268
+ attributes: this.getAttributes(feature),
269
+ };
270
+ }
271
+
272
+ /**
273
+ * This method is being called by featureInfo, whenever a new window is created (added to the windowManager).
274
+ * May be overwritten by classes extending AbstractFeatureInfoView.
275
+ * @param {FeatureInfoEvent} featureInfo
276
+ * @param {import("@vcmap/core").Layer} layer
277
+ * @returns {WindowComponentOptions}
278
+ */
279
+ getWindowComponentOptions(featureInfo, layer) {
280
+ return {
281
+ state: this.window.state ?? {
282
+ headerTitle: layer.properties?.title || layer.name,
283
+ headerIcon: '$vcsInfo',
284
+ },
285
+ slot: this.window.slot ?? WindowSlot.DYNAMIC_LEFT,
286
+ component: this.component,
287
+ position: this.window.position,
288
+ props: this.getProperties(featureInfo, layer),
289
+ };
290
+ }
291
+
292
+ /**
293
+ * @returns {FeatureInfoViewOptions}
294
+ */
295
+ toJSON() {
296
+ const config = super.toJSON();
297
+ if (this.attributeKeys.length > 0) {
298
+ config.attributeKeys = this.attributeKeys.slice(0);
299
+ }
300
+ if (this.keyMapping) {
301
+ config.keyMapping = { ...this.keyMapping };
302
+ }
303
+ if (this.valueMapping) {
304
+ config.valueMapping = JSON.parse(JSON.stringify(this.valueMapping));
305
+ }
306
+ if (Object.keys(this._window).length > 0) {
307
+ config.window = { ...this._window };
308
+ }
309
+ return config;
310
+ }
311
+ }
312
+
313
+ export default AbstractFeatureInfoView;
@@ -0,0 +1,118 @@
1
+ import BalloonFeatureInfoView, { extractNestedKey } from './balloonFeatureInfoView.js';
2
+ import AddressBalloonComponent from './AddressBalloonComponent.vue';
3
+
4
+ /**
5
+ * @typedef {FeatureInfoViewOptions} AddressBalloonFeatureInfoViewOptions
6
+ * @property {string|null} [addressName='gml:name'] key to evaluate for name. Use null to suppress
7
+ * @property {string|null} [street='Address.Street'] key to evaluate for street. Use null to suppress
8
+ * @property {string|null} [number='Address.HouseNumber'] key to evaluate for house number. Use null to suppress
9
+ * @property {string|null} [city='Address.City'] key to evaluate for city. Use null to suppress
10
+ * @property {string|null} [zip='Address.ZipCode'] key to evaluate for zip code. Use null to suppress
11
+ * @property {string|null} [country='Address.Country'] key to evaluate for country. Use null to suppress
12
+ */
13
+
14
+ /**
15
+ * @class
16
+ * @description An balloon view.
17
+ * @extends {BalloonFeatureInfoView}
18
+ */
19
+ class AddressBalloonFeatureInfoView extends BalloonFeatureInfoView {
20
+ /**
21
+ * @type {string}
22
+ */
23
+ static get className() { return 'AddressBalloonFeatureInfoView'; }
24
+
25
+ /** @returns {AddressBalloonFeatureInfoViewOptions} */
26
+ static getDefaultOptions() {
27
+ return {
28
+ addressName: 'gml:name',
29
+ street: 'Address.Street',
30
+ number: 'Address.HouseNumber',
31
+ city: 'Address.City',
32
+ zip: 'Address.ZipCode',
33
+ country: 'Address.Country',
34
+ };
35
+ }
36
+
37
+ /**
38
+ * @param {AddressBalloonFeatureInfoViewOptions} options
39
+ */
40
+ constructor(options) {
41
+ super(options, AddressBalloonComponent);
42
+ const defaultOptions = AddressBalloonFeatureInfoView.getDefaultOptions();
43
+
44
+ /**
45
+ * @type {string|null}
46
+ */
47
+ this.addressName = options.addressName !== undefined ? options.addressName : defaultOptions.addressName;
48
+ /**
49
+ * @type {string|null}
50
+ */
51
+ this.street = options.street !== undefined ? options.street : defaultOptions.street;
52
+ /**
53
+ * @type {string|null}
54
+ */
55
+ this.number = options.number !== undefined ? options.number : defaultOptions.number;
56
+ /**
57
+ * @type {string|null}
58
+ */
59
+ this.city = options.city !== undefined ? options.city : defaultOptions.city;
60
+ /**
61
+ * @type {string|null}
62
+ */
63
+ this.zip = options.zip !== undefined ? options.zip : defaultOptions.zip;
64
+ /**
65
+ * @type {string|null}
66
+ */
67
+ this.country = options.country !== undefined ? options.country : defaultOptions.country;
68
+ }
69
+
70
+ /**
71
+ * derives address attributes from addressKeys
72
+ * @param {undefined|import("ol").Feature<import("ol/geom/Geometry").default>|import("@vcmap/cesium").Cesium3DTileFeature|import("@vcmap/cesium").Cesium3DTilePointFeature} feature
73
+ * @returns {Object}
74
+ */
75
+ getAttributes(feature) {
76
+ const attributes = super.getAttributes(feature);
77
+ const obj = {};
78
+ const applyAddressKeys = (key) => {
79
+ if (this[key]) {
80
+ const derivedValue = extractNestedKey(this[key], attributes);
81
+ if (derivedValue) {
82
+ obj[key] = derivedValue;
83
+ }
84
+ }
85
+ };
86
+ Object.keys(AddressBalloonFeatureInfoView.getDefaultOptions()).forEach(key => applyAddressKeys(key));
87
+ return obj;
88
+ }
89
+
90
+ /**
91
+ * @returns {AddressBalloonFeatureInfoViewOptions}
92
+ */
93
+ toJSON() {
94
+ const config = super.toJSON();
95
+ const defaultOptions = AddressBalloonFeatureInfoView.getDefaultOptions();
96
+ if (this.addressName !== defaultOptions.addressName) {
97
+ config.addressName = this.addressName;
98
+ }
99
+ if (this.street !== defaultOptions.street) {
100
+ config.street = this.street;
101
+ }
102
+ if (this.number !== defaultOptions.number) {
103
+ config.number = this.number;
104
+ }
105
+ if (this.city !== defaultOptions.city) {
106
+ config.city = this.city;
107
+ }
108
+ if (this.zip !== defaultOptions.zip) {
109
+ config.zip = this.zip;
110
+ }
111
+ if (this.country !== defaultOptions.country) {
112
+ config.country = this.country;
113
+ }
114
+ return config;
115
+ }
116
+ }
117
+
118
+ export default AddressBalloonFeatureInfoView;
@@ -0,0 +1,151 @@
1
+ import { Feature } from 'ol';
2
+ import { getCenter } from 'ol/extent.js';
3
+ import { Cartographic, Entity, Math as CesiumMath } from '@vcmap/cesium';
4
+ import { Projection } from '@vcmap/core';
5
+ import { check } from '@vcsuite/check';
6
+ import AbstractFeatureInfoView from './abstractFeatureInfoView.js';
7
+ import { getWindowPositionOptions, WindowAlignment, WindowSlot } from '../manager/window/windowManager.js';
8
+ import BalloonComponent from './BalloonComponent.vue';
9
+ import { balloonOffset } from './balloonHelper.js';
10
+
11
+ /**
12
+ * derive value from attributes
13
+ * @param {string} key - key or nested key
14
+ * @param {Object} attrs
15
+ * @param {string|null} [defaultValue]
16
+ * @returns {string|null}
17
+ */
18
+ export function extractNestedKey(key, attrs, defaultValue = null) {
19
+ check(key, String);
20
+ check(attrs, Object);
21
+
22
+ const keys = key.split('.');
23
+ const derivedValue = keys.reduce((obj, prop) => (obj[prop] || {}), attrs);
24
+ return typeof derivedValue === 'string' ? derivedValue : defaultValue;
25
+ }
26
+
27
+ /**
28
+ * @typedef {FeatureInfoViewOptions} BalloonFeatureInfoViewOptions
29
+ * @property {string} [title] - optional title to overwrite default (layerName). Can be attribute key (nested key using '.'), i18n key or text
30
+ * @property {string} [subtitle] - optional window title to overwrite default (featureId). Can be attribute key (nested key using '.'), i18n key or text
31
+ */
32
+
33
+ /**
34
+ * @typedef {FeatureInfoProps} BalloonFeatureInfoViewProps
35
+ * @property {string} title
36
+ * @property {string} subtitle
37
+ * @property {import("ol/coordinate").Coordinate} position
38
+ */
39
+
40
+ /**
41
+ * @param {import("@vcmap/core").Cartesian3} cartesian
42
+ * @returns {import("ol/coordinate").Coordinate}
43
+ */
44
+ function cartesian3ToCoordinate(cartesian) {
45
+ const cartographic = Cartographic.fromCartesian(cartesian);
46
+ const wgs84position = [
47
+ CesiumMath.toDegrees(cartographic.longitude),
48
+ CesiumMath.toDegrees(cartographic.latitude),
49
+ cartographic.height,
50
+ ];
51
+ return Projection.wgs84ToMercator(wgs84position);
52
+ }
53
+
54
+ /**
55
+ * @param {FeatureType} feature
56
+ * @returns {import("ol/coordinate").Coordinate|null}
57
+ */
58
+ function getPositionFromFeature(feature) {
59
+ if (feature instanceof Feature && feature.getGeometry()) {
60
+ return getCenter(feature.getGeometry().getExtent());
61
+ } else if (feature instanceof Entity) {
62
+ return cartesian3ToCoordinate(feature.position);
63
+ } else if (feature?.primitive?.boundingSphere?.center) {
64
+ return cartesian3ToCoordinate(feature.primitive.boundingSphere.center);
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * @class
71
+ * @description An balloon view.
72
+ * @extends {AbstractFeatureInfoView}
73
+ */
74
+ class BalloonFeatureInfoView extends AbstractFeatureInfoView {
75
+ /**
76
+ * @type {string}
77
+ */
78
+ static get className() { return 'BalloonFeatureInfoView'; }
79
+
80
+ /**
81
+ * @param {BalloonFeatureInfoViewOptions} options
82
+ * @param {import("vue").Component} [component=BalloonComponent]
83
+ */
84
+ constructor(options, component) {
85
+ super(options, component || BalloonComponent);
86
+
87
+ /**
88
+ * @type {string}
89
+ */
90
+ this.title = options.title;
91
+
92
+ /**
93
+ * @type {string}
94
+ */
95
+ this.subtitle = options.subtitle;
96
+ }
97
+
98
+ /**
99
+ * @param {FeatureInfoEvent} featureInfo
100
+ * @param {import("@vcmap/core").Layer} layer
101
+ * @returns {BalloonFeatureInfoViewProps}
102
+ */
103
+ getProperties(featureInfo, layer) {
104
+ const properties = super.getProperties(featureInfo, layer);
105
+ return {
106
+ ...properties,
107
+ position: featureInfo.position ?? getPositionFromFeature(featureInfo.feature),
108
+ title: this.title ?
109
+ extractNestedKey(this.title, properties.attributes, this.title) :
110
+ properties.layerProperties.title,
111
+ subtitle: this.subtitle ?
112
+ extractNestedKey(this.subtitle, properties.attributes, this.subtitle) :
113
+ properties.featureId,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * @param {FeatureInfoEvent} featureInfo
119
+ * @param {import("@vcmap/core").Layer} layer
120
+ * @returns {WindowComponentOptions}
121
+ */
122
+ getWindowComponentOptions(featureInfo, layer) {
123
+ const options = super.getWindowComponentOptions(featureInfo, layer);
124
+ options.state.hideHeader = true;
125
+ options.state.classes = ['balloon'];
126
+ options.slot = WindowSlot.DETACHED;
127
+ options.position = getWindowPositionOptions(
128
+ (featureInfo.windowPosition?.[0] ?? 0) - balloonOffset.x, // if we do not have a windowPosition, let the next render handle it
129
+ (featureInfo.windowPosition?.[1] ?? 0) - balloonOffset.y,
130
+ null,
131
+ WindowAlignment.BOTTOM_LEFT,
132
+ );
133
+ return options;
134
+ }
135
+
136
+ /**
137
+ * @returns {BalloonFeatureInfoViewOptions}
138
+ */
139
+ toJSON() {
140
+ const config = super.toJSON();
141
+ if (this.title) {
142
+ config.title = this.title;
143
+ }
144
+ if (this.subtitle) {
145
+ config.subtitle = this.subtitle;
146
+ }
147
+ return config;
148
+ }
149
+ }
150
+
151
+ export default BalloonFeatureInfoView;