@vcmap/ui 5.0.0-rc.10 → 5.0.0-rc.13
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 +12 -5
- package/build/build.js +6 -3
- package/build/buildHelpers.js +12 -4
- package/build/buildPreview.js +7 -0
- package/build/getPluginProxies.js +4 -0
- package/config/aerowest.config.json +13 -3
- package/config/base.config.json +398 -219
- package/config/codes.config.json +397 -0
- package/config/dev.config.json +375 -1
- package/config/graphFeatureInfo.config.json +100 -0
- package/config/www.config.json +1232 -0
- package/dist/assets/{cesium.eb5667.js → cesium.21663e.js} +0 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/core.63242d.js +4 -0
- package/dist/assets/core.js +1 -1
- package/dist/assets/font/OFL.txt +93 -0
- package/dist/assets/font/TitilliumWeb-Regular.woff2 +0 -0
- package/dist/assets/{index.4ccd4433.js → index.44b91cfe.js} +1 -1
- package/dist/assets/{ol.ef03b1.js → ol.88ba9d.js} +0 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/ui.3c2933.css +1 -0
- package/dist/assets/ui.3c2933.js +71 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.c897fc.js +9 -0
- package/dist/assets/vue.js +2 -1
- package/dist/assets/{vuetify.401a29.css → vuetify.147c3a.css} +1 -1
- package/dist/assets/{vuetify.401a29.js → vuetify.147c3a.js} +72 -72
- package/dist/assets/vuetify.js +2 -2
- package/dist/index.html +1 -5
- package/index.js +39 -5
- package/lib/vue.js +1 -0
- package/map.config.json +15 -6
- package/package.json +17 -8
- 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 +2 -2
- 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/example/index.js +10 -23
- 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 +76 -9
- package/plugins/test/toolbox-data.js +82 -57
- 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 +301 -116
- package/src/application/VcsMap.vue +1 -1
- package/src/application/VcsSettings.vue +1 -1
- package/src/application/vcsAppWrapper.vue +1 -0
- package/src/assets/font/OFL.txt +93 -0
- package/src/assets/font/TitilliumWeb-Regular.woff2 +0 -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 +22 -7
- package/src/components/lists/VcsTreeview.vue +4 -4
- package/src/components/lists/VcsTreeviewLeaf.vue +10 -3
- 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 +140 -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 +457 -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 +26 -0
- package/src/i18n/en.js +26 -0
- package/src/i18n/i18nCollection.js +17 -0
- package/src/icons/+all.js +80 -0
- package/src/icons/ClippingHorizontalIcon.vue +7 -0
- package/src/icons/ClippingIcon.vue +7 -0
- package/src/icons/ClippingVerticalIcon.vue +7 -0
- package/src/icons/ColorPickerIcon.vue +7 -0
- package/src/icons/ComponentsIcon.vue +2 -2
- package/src/icons/DimensionsHouseIcon.vue +11 -9
- package/src/icons/EditIcon.vue +7 -0
- package/src/icons/GlobalTerrainIcon.vue +9 -0
- package/src/icons/GroundIcon.vue +18 -0
- package/src/icons/HideIcon.vue +12 -0
- package/src/icons/LogoutIcon.vue +7 -0
- package/src/icons/ObjectAttributeIcon.vue +2 -13
- package/src/icons/PedestrianIcon.vue +2 -3
- package/src/icons/PenIcon.vue +2 -9
- package/src/icons/PoiIcon.vue +5 -2
- package/src/icons/PointSelectIcon.vue +4 -2
- package/src/icons/QueryIcon.vue +6 -7
- package/src/icons/ScreenshotIcon.vue +16 -0
- package/src/icons/ShareIcon.vue +4 -16
- package/src/icons/SkipNextIcon.vue +3 -1
- package/src/icons/TerrainBoxIcon.vue +9 -0
- package/src/icons/ToolsIcon.vue +4 -30
- package/src/icons/UploadIcon.vue +2 -9
- package/src/icons/UserProfileIcon.vue +7 -0
- package/src/icons/UserShareIcon.vue +7 -0
- package/src/icons/VideoRecorderIcon.vue +5 -9
- package/src/icons/ViewpointFlightIcon.vue +11 -0
- package/src/icons/ViewpointIcon.vue +11 -0
- package/src/icons/Viewshed360Icon.vue +7 -0
- package/src/icons/ViewshedConeIcon.vue +7 -0
- package/src/icons/ViewshedIcon.vue +7 -0
- package/src/icons/WallIcon.vue +4 -9
- package/src/legend/legendHelper.js +193 -0
- package/src/legend/styleLegendItem.vue +129 -0
- package/src/legend/vcsLegend.vue +92 -0
- package/src/manager/buttonManager.js +7 -12
- 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 +9 -9
- package/src/manager/toolbox/GroupToolboxComponent.vue +118 -0
- package/src/manager/toolbox/SelectToolboxComponent.vue +128 -0
- package/src/manager/toolbox/ToolboxManager.vue +116 -98
- package/src/manager/toolbox/toolboxManager.js +235 -86
- 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/pluginHelper.js +42 -10
- package/src/setup.js +0 -2
- package/src/state.js +256 -0
- package/src/styles/_theming.scss +0 -5
- package/src/styles/variables.scss +7 -0
- package/src/styles/vcsFont.scss +17 -0
- package/src/uiConfig.js +79 -0
- package/src/vcsUiApp.js +213 -22
- 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
- package/src/manager/toolbox/ToolboxGroupComponent.vue +0 -128
@@ -4,7 +4,8 @@ import { StateActionState } from '../actions/stateRefAction.js';
|
|
4
4
|
|
5
5
|
/**
|
6
6
|
* @typedef {ContentTreeItemOptions} LayerGroupContentTreeItemOptions
|
7
|
-
* @property {Array<string>} layerNames
|
7
|
+
* @property {Array<string>} layerNames list of LayerNames which should be activated on click
|
8
|
+
* @property {Array<string>} layerNamesToDeactivate list of LayerNames which should be deactivated on click if the click activates the layer in layerNames
|
8
9
|
* @property {string} [defaultViewpoint] - the name of an optional default viewpoint
|
9
10
|
*/
|
10
11
|
|
@@ -55,6 +56,14 @@ class LayerGroupContentTreeItem extends ContentTreeItem {
|
|
55
56
|
options.layerNames.slice() :
|
56
57
|
[];
|
57
58
|
|
59
|
+
/**
|
60
|
+
* @type {Array<string>}
|
61
|
+
* @private
|
62
|
+
*/
|
63
|
+
this._layerNamesToDeactivate = Array.isArray(options.layerNamesToDeactivate) ?
|
64
|
+
options.layerNamesToDeactivate.slice() :
|
65
|
+
[];
|
66
|
+
|
58
67
|
/**
|
59
68
|
* @type {Array<function():void>}
|
60
69
|
* @private
|
@@ -122,11 +131,18 @@ class LayerGroupContentTreeItem extends ContentTreeItem {
|
|
122
131
|
}));
|
123
132
|
}
|
124
133
|
|
134
|
+
/**
|
135
|
+
* @returns {Promise<void>}
|
136
|
+
*/
|
125
137
|
async clicked() {
|
126
138
|
const layers = this._layers;
|
127
139
|
const activate = layers.some(l => !(l.active || l.loading));
|
128
140
|
if (activate) {
|
129
141
|
await Promise.all(layers.map(l => l.activate()));
|
142
|
+
this._layerNamesToDeactivate
|
143
|
+
.map(n => this._app.layers.getByKey(n))
|
144
|
+
.filter(l => l)
|
145
|
+
.forEach(l => l.deactivate());
|
130
146
|
} else {
|
131
147
|
layers.forEach((l) => { l.deactivate(); });
|
132
148
|
}
|
@@ -138,12 +154,16 @@ class LayerGroupContentTreeItem extends ContentTreeItem {
|
|
138
154
|
toJSON() {
|
139
155
|
const config = super.toJSON();
|
140
156
|
config.layerNames = this._layerNames.slice();
|
157
|
+
config.layerNamesToDeactivate = this._layerNamesToDeactivate.slice();
|
141
158
|
if (this._defaultViewpoint) {
|
142
159
|
config.defaultViewpoint = this._defaultViewpoint;
|
143
160
|
}
|
144
161
|
return config;
|
145
162
|
}
|
146
163
|
|
164
|
+
/**
|
165
|
+
* @inheritDoc
|
166
|
+
*/
|
147
167
|
destroy() {
|
148
168
|
this._clearListeners();
|
149
169
|
super.destroy();
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<template>
|
2
|
+
<BalloonComponent
|
3
|
+
v-bind="{...$attrs}"
|
4
|
+
>
|
5
|
+
<template #default="{ attrs }">
|
6
|
+
<v-list-item two-line v-if="Object.values(attrs.attributes).length > 0">
|
7
|
+
<v-list-item-avatar
|
8
|
+
tile
|
9
|
+
size="40"
|
10
|
+
>
|
11
|
+
<v-icon size="20" color="secondary">
|
12
|
+
$vcsHomePoint
|
13
|
+
</v-icon>
|
14
|
+
</v-list-item-avatar>
|
15
|
+
<v-list-item-content>
|
16
|
+
<v-list-item-title v-if="attrs.attributes.addressName">
|
17
|
+
{{ attrs.attributes.addressName }}
|
18
|
+
</v-list-item-title>
|
19
|
+
<v-list-item-title v-else>
|
20
|
+
{{ attrs.attributes.street }} {{ attrs.attributes.number }}
|
21
|
+
</v-list-item-title>
|
22
|
+
<v-list-item-subtitle v-if="attrs.attributes.name">
|
23
|
+
{{ attrs.attributes.street }} {{ attrs.attributes.number }}
|
24
|
+
</v-list-item-subtitle>
|
25
|
+
<v-list-item-subtitle>{{ attrs.attributes.zip }} {{ attrs.attributes.city }}</v-list-item-subtitle>
|
26
|
+
<v-list-item-subtitle>{{ attrs.attributes.country }}</v-list-item-subtitle>
|
27
|
+
</v-list-item-content>
|
28
|
+
</v-list-item>
|
29
|
+
</template>
|
30
|
+
</BalloonComponent>
|
31
|
+
</template>
|
32
|
+
<script>
|
33
|
+
|
34
|
+
import BalloonComponent from './BalloonComponent.vue';
|
35
|
+
|
36
|
+
/**
|
37
|
+
* @description A balloon viewing address information
|
38
|
+
*/
|
39
|
+
export default {
|
40
|
+
name: 'AddressBalloonComponent',
|
41
|
+
components: { BalloonComponent },
|
42
|
+
};
|
43
|
+
</script>
|
44
|
+
|
45
|
+
<style>
|
46
|
+
|
47
|
+
</style>
|
@@ -0,0 +1,140 @@
|
|
1
|
+
<template>
|
2
|
+
<v-card
|
3
|
+
class="mx-auto"
|
4
|
+
max-width="400"
|
5
|
+
v-if="position"
|
6
|
+
>
|
7
|
+
<slot name="balloon-header" :attrs="{...$props, ...$attrs}">
|
8
|
+
<v-list-item two-line>
|
9
|
+
<v-list-item-avatar
|
10
|
+
tile
|
11
|
+
size="40"
|
12
|
+
>
|
13
|
+
<v-icon color="primary">
|
14
|
+
$vcsInfo
|
15
|
+
</v-icon>
|
16
|
+
</v-list-item-avatar>
|
17
|
+
<v-list-item-content>
|
18
|
+
<v-list-item-title class="text-h5">
|
19
|
+
{{ title }}
|
20
|
+
</v-list-item-title>
|
21
|
+
<v-list-item-subtitle>{{ subtitle }}</v-list-item-subtitle>
|
22
|
+
</v-list-item-content>
|
23
|
+
<VcsButton
|
24
|
+
@click.stop="close"
|
25
|
+
small
|
26
|
+
icon="mdi-close-thick"
|
27
|
+
class="mb-8"
|
28
|
+
/>
|
29
|
+
</v-list-item>
|
30
|
+
</slot>
|
31
|
+
|
32
|
+
<v-divider />
|
33
|
+
|
34
|
+
<v-card class="overflow-y-auto" max-height="250">
|
35
|
+
<slot :attrs="{...$props, ...$attrs}">
|
36
|
+
<v-list v-for="(value, name, index) in attributes" :key="`attribute-${index}`">
|
37
|
+
<v-list-item>
|
38
|
+
<v-list-item-content>
|
39
|
+
<v-list-item-title>
|
40
|
+
{{ name }}
|
41
|
+
</v-list-item-title>
|
42
|
+
<v-list-item-subtitle>{{ value }}</v-list-item-subtitle>
|
43
|
+
</v-list-item-content>
|
44
|
+
</v-list-item>
|
45
|
+
</v-list>
|
46
|
+
</slot>
|
47
|
+
</v-card>
|
48
|
+
</v-card>
|
49
|
+
</template>
|
50
|
+
<script>
|
51
|
+
|
52
|
+
import { inject, onMounted, onUnmounted, watch } from 'vue';
|
53
|
+
import { setupBalloonPositionListener } from './balloonHelper.js';
|
54
|
+
import VcsButton from '../components/buttons/VcsButton.vue';
|
55
|
+
|
56
|
+
/**
|
57
|
+
* @description A balloon viewing feature attributes. Size dynamic dependent on number of attributes.
|
58
|
+
* Scrollable, if more than 6 attributes are provided.
|
59
|
+
* @vue-prop {string} featureId - feature's id
|
60
|
+
* @vue-prop {string} title - balloon title
|
61
|
+
* @vue-prop {string} subtitle - balloon subtitle
|
62
|
+
* @vue-prop {Object} attributes - feature's attributes
|
63
|
+
* @vue-prop {Array<import("ol/coordinate").Coordinate>} position - clicked position balloon is rendered at
|
64
|
+
* @vue-data {slot} [#balloon-header] - slot to override balloon header, $props and $attrs are passed to `attrs`
|
65
|
+
* @vue-data {slot} [#default] - slot to override balloon content, $props and $attrs are passed to `attrs`
|
66
|
+
*/
|
67
|
+
export default {
|
68
|
+
name: 'BalloonComponent',
|
69
|
+
components: { VcsButton },
|
70
|
+
props: {
|
71
|
+
featureId: {
|
72
|
+
type: String,
|
73
|
+
required: true,
|
74
|
+
},
|
75
|
+
title: {
|
76
|
+
type: String,
|
77
|
+
required: true,
|
78
|
+
},
|
79
|
+
subtitle: {
|
80
|
+
type: String,
|
81
|
+
required: true,
|
82
|
+
},
|
83
|
+
attributes: {
|
84
|
+
type: Object,
|
85
|
+
required: true,
|
86
|
+
},
|
87
|
+
position: {
|
88
|
+
type: Array,
|
89
|
+
default: null,
|
90
|
+
},
|
91
|
+
},
|
92
|
+
setup(props, { attrs }) {
|
93
|
+
const app = inject('vcsApp');
|
94
|
+
const windowId = attrs['window-state'].id;
|
95
|
+
|
96
|
+
let balloonPositionListener = null;
|
97
|
+
const destroyListener = () => {
|
98
|
+
if (balloonPositionListener) {
|
99
|
+
balloonPositionListener();
|
100
|
+
}
|
101
|
+
};
|
102
|
+
|
103
|
+
onMounted(async () => {
|
104
|
+
balloonPositionListener = await setupBalloonPositionListener(app, windowId, props.position);
|
105
|
+
});
|
106
|
+
|
107
|
+
watch(() => props.featureId, async () => {
|
108
|
+
destroyListener();
|
109
|
+
balloonPositionListener = await setupBalloonPositionListener(app, windowId, props.position);
|
110
|
+
});
|
111
|
+
|
112
|
+
onUnmounted(() => {
|
113
|
+
destroyListener();
|
114
|
+
});
|
115
|
+
|
116
|
+
const close = () => {
|
117
|
+
app.windowManager.remove(attrs['window-state'].id);
|
118
|
+
destroyListener();
|
119
|
+
};
|
120
|
+
|
121
|
+
return {
|
122
|
+
close,
|
123
|
+
};
|
124
|
+
},
|
125
|
+
};
|
126
|
+
</script>
|
127
|
+
|
128
|
+
<style>
|
129
|
+
.balloon:before {
|
130
|
+
content: "";
|
131
|
+
position: absolute;
|
132
|
+
bottom: -20px;
|
133
|
+
left: 40px;
|
134
|
+
border-width: 20px 20px 0;
|
135
|
+
border-style: solid;
|
136
|
+
border-color: white transparent;
|
137
|
+
display: block;
|
138
|
+
width: 0;
|
139
|
+
}
|
140
|
+
</style>
|
@@ -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;
|