@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.
- package/README.md +11 -4
- package/build/build.js +0 -3
- package/build/buildHelpers.js +0 -1
- package/build/buildPreview.js +7 -0
- package/config/aerowest.config.json +13 -3
- package/config/base.config.json +89 -64
- package/config/codes.config.json +397 -0
- package/config/dev.config.json +169 -0
- package/config/graphFeatureInfo.config.json +100 -0
- package/config/www.config.json +1232 -0
- package/dist/assets/{cesium.eb5667.js → cesium.e67536.js} +0 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/core.ebf665.js +4 -0
- package/dist/assets/core.js +1 -1
- package/dist/assets/{index.4ccd4433.js → index.9b213929.js} +1 -1
- package/dist/assets/{ol.ef03b1.js → ol.8bbd50.js} +0 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/ui.fdfe0d.css +1 -0
- package/dist/assets/ui.fdfe0d.js +68 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.0bb7c6.js +9 -0
- package/dist/assets/vue.js +2 -1
- package/dist/assets/{vuetify.401a29.css → vuetify.53300f.css} +1 -1
- package/dist/assets/{vuetify.401a29.js → vuetify.53300f.js} +71 -71
- package/dist/assets/vuetify.js +2 -2
- package/dist/index.html +1 -1
- package/index.js +36 -5
- package/lib/vue.js +1 -0
- package/map.config.json +15 -6
- package/package.json +6 -7
- package/plugins/@vcmap/create-link/fallbackCreateLink.vue +71 -0
- package/plugins/@vcmap/create-link/index.js +83 -0
- package/plugins/@vcmap/create-link/package.json +6 -0
- package/plugins/@vcmap/pluginExample/index.js +1 -1
- package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +20 -3
- package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +1 -1
- package/plugins/@vcmap/project-selector/index.js +1 -1
- package/plugins/@vcmap/project-selector/package.json +1 -2
- package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +1 -1
- package/plugins/@vcmap/theme-changer/index.js +1 -1
- package/plugins/@vcmap/theme-changer/package.json +1 -2
- package/plugins/categoryTest/Categories.vue +89 -1
- package/plugins/categoryTest/Category.vue +1 -1
- package/plugins/simple-graph/README.md +51 -0
- package/plugins/simple-graph/SimpleGraphComponent.vue +70 -0
- package/plugins/simple-graph/index.js +17 -0
- package/plugins/simple-graph/package.json +11 -0
- package/plugins/simple-graph/simpleGraphView.js +76 -0
- package/plugins/test/editor.vue +1 -1
- package/plugins/test/index.js +63 -2
- package/plugins/test/windowManagerExample.vue +1 -1
- package/src/actions/stateRefAction.js +2 -2
- package/src/actions/styleSelector.vue +1 -1
- package/src/application/Navbar.vue +13 -2
- package/src/application/VcsApp.vue +201 -92
- package/src/application/VcsMap.vue +1 -1
- package/src/application/VcsSettings.vue +1 -1
- package/src/application/vcsAppWrapper.vue +1 -0
- package/src/components/form-inputs-controls/VcsCheckbox.vue +13 -0
- package/src/components/form-inputs-controls/VcsColorPicker.vue +1 -1
- package/src/components/form-inputs-controls/VcsRadio.vue +123 -0
- package/src/components/form-output/VcsFormattedNumber.vue +1 -1
- package/src/components/lists/VcsActionList.vue +13 -4
- package/src/components/lists/VcsTreeview.vue +4 -4
- package/src/components/lists/VcsTreeviewLeaf.vue +9 -2
- package/src/components/lists/VcsTreeviewSearchbar.vue +1 -2
- package/src/components/tables/VcsTable.vue +245 -0
- package/src/contentTree/LayerTree.vue +1 -1
- package/src/contentTree/contentTreeCollection.js +4 -4
- package/src/contentTree/contentTreeItem.js +9 -9
- package/src/contentTree/groupContentTreeItem.js +1 -1
- package/src/contentTree/layerContentTreeItem.js +15 -1
- package/src/contentTree/layerGroupContentTreeItem.js +21 -1
- package/src/contentTree/nodeContentTreeItem.js +1 -1
- package/src/featureInfo/AddressBalloonComponent.vue +47 -0
- package/src/featureInfo/BalloonComponent.vue +138 -0
- package/src/featureInfo/abstractFeatureInfoView.js +313 -0
- package/src/featureInfo/addressBalloonFeatureInfoView.js +118 -0
- package/src/featureInfo/balloonFeatureInfoView.js +151 -0
- package/src/featureInfo/balloonHelper.js +132 -0
- package/src/featureInfo/featureInfo.js +455 -0
- package/src/featureInfo/featureInfoInteraction.js +42 -0
- package/src/featureInfo/iframeFeatureInfoView.js +95 -0
- package/src/featureInfo/tableFeatureInfoView.js +106 -0
- package/src/i18n/de.js +16 -0
- package/src/i18n/en.js +16 -0
- package/src/i18n/i18nCollection.js +17 -0
- package/src/manager/buttonManager.js +5 -5
- package/src/manager/categoryManager/ComponentsManager.vue +30 -0
- package/src/manager/categoryManager/categoryManager.js +500 -0
- package/src/manager/contextMenu/contextMenuComponent.vue +43 -0
- package/src/manager/contextMenu/contextMenuInteraction.js +42 -0
- package/src/manager/contextMenu/contextMenuManager.js +197 -0
- package/src/manager/navbarManager.js +8 -8
- package/src/manager/toolbox/ToolboxManager.vue +2 -2
- package/src/manager/toolbox/toolboxManager.js +7 -3
- package/src/manager/window/WindowComponent.vue +1 -1
- package/src/manager/window/WindowManager.vue +5 -3
- package/src/manager/window/windowManager.js +118 -14
- package/src/navigation/mapNavigation.vue +3 -5
- package/src/navigation/overviewMap.js +28 -5
- package/src/navigation/vcsCompass.vue +1 -1
- package/src/setup.js +0 -2
- package/src/state.js +256 -0
- package/src/styles/_theming.scss +0 -5
- package/src/uiConfig.js +79 -0
- package/src/vcsUiApp.js +210 -20
- package/src/vuePlugins/vuetify.js +14 -4
- package/config/berlin.config.json +0 -510
- package/dist/assets/core.216494.js +0 -4
- package/dist/assets/ui.99a1a7.css +0 -1
- package/dist/assets/ui.99a1a7.js +0 -70
- package/dist/assets/vue-composition-api.c5aca1.js +0 -14
- package/dist/assets/vue-composition-api.js +0 -2
- package/dist/assets/vue.762edd.js +0 -9
- 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;
|