@vcmap/viewshed 2.0.1
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/CHANGELOG.md +4 -0
- package/LICENSE.md +13 -0
- package/README.md +4 -0
- package/dist/index.js +1423 -0
- package/package.json +75 -0
- package/src/ViewshedConfigEditor.vue +121 -0
- package/src/ViewshedWindow.vue +477 -0
- package/src/index.js +211 -0
- package/src/util/toolboxHelper.js +133 -0
- package/src/util/windowHelper.js +128 -0
- package/src/viewshed.js +578 -0
- package/src/viewshedCategory.js +216 -0
- package/src/viewshedInteraction.js +57 -0
- package/src/viewshedManager.js +416 -0
- package/src/viewshedPrimitive.js +101 -0
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vcmap/viewshed",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Tool to simulate a \"viewer\" and their field of vision from a certain standpoint in the map. It allows the user to see which areas and buildings can or can't be seen from a specific standpoint, regarding different viewing distances, viewers' sizes and orientations.Th",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"prepublishOnly": "vcmplugin build",
|
|
9
|
+
"build": "vcmplugin build",
|
|
10
|
+
"bundle": "vcmplugin bundle",
|
|
11
|
+
"start": "vcmplugin serve",
|
|
12
|
+
"preview": "vcmplugin preview",
|
|
13
|
+
"buildStagingApp": "vcmplugin buildStagingApp",
|
|
14
|
+
"lint:js": "eslint . --ext .vue,.js,.cjs,.mjs,.ts,.cts,.mts",
|
|
15
|
+
"lint:prettier": "prettier --check .",
|
|
16
|
+
"lint": "npm run lint:js && npm run lint:prettier",
|
|
17
|
+
"format": "prettier --write --list-different . && npm run lint:js -- --fix",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"coverage": "vitest run --coverage"
|
|
20
|
+
},
|
|
21
|
+
"author": "Tobias Krauth <tkrauth@vc.systems>",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"vcmap",
|
|
25
|
+
"plugin"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"src/",
|
|
29
|
+
"dist/",
|
|
30
|
+
"plugin-assets/",
|
|
31
|
+
"LICENSE.md",
|
|
32
|
+
"README.md",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"exports": {
|
|
36
|
+
".": "./src/index.js",
|
|
37
|
+
"./dist": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"eslintIgnore": [
|
|
40
|
+
"node_modules",
|
|
41
|
+
"dist",
|
|
42
|
+
"plugin-assets"
|
|
43
|
+
],
|
|
44
|
+
"eslintConfig": {
|
|
45
|
+
"root": true,
|
|
46
|
+
"extends": "@vcsuite/eslint-config/vue"
|
|
47
|
+
},
|
|
48
|
+
"prettier": "@vcsuite/eslint-config/prettier.js",
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@vcmap-cesium/engine": "^4.0.3",
|
|
51
|
+
"@vcmap/core": "^5.1.0-rc.2",
|
|
52
|
+
"@vcmap/ui": "^5.1.0-rc.1",
|
|
53
|
+
"ol": "^7.5.2",
|
|
54
|
+
"vue": "~2.7.3",
|
|
55
|
+
"vuetify": "~2.6.7"
|
|
56
|
+
},
|
|
57
|
+
"overrides": {
|
|
58
|
+
"@vcmap/core": "^5.1.0-rc.2",
|
|
59
|
+
"@vcmap/ui": "^5.1.0-rc.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@vcmap/plugin-cli": "^2.1.16",
|
|
63
|
+
"@vcsuite/eslint-config": "^3.0.5",
|
|
64
|
+
"@vitest/coverage-v8": "^0.34.6",
|
|
65
|
+
"jest-canvas-mock": "^2.5.2",
|
|
66
|
+
"jsdom": "^22.1.0",
|
|
67
|
+
"resize-observer-polyfill": "^1.5.1",
|
|
68
|
+
"vitest": "^0.34.6"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@vcsuite/check": "^1.1.2",
|
|
72
|
+
"@vcsuite/parsers": "^1.0.3"
|
|
73
|
+
},
|
|
74
|
+
"mapVersion": "^5.1"
|
|
75
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<AbstractConfigEditor @submit="apply" v-bind="{ ...$attrs, ...$props }">
|
|
3
|
+
<VcsFormSection heading="viewshed.editor.general" v-if="localConfig">
|
|
4
|
+
<v-container class="px-1 py-0">
|
|
5
|
+
<v-row no-gutters>
|
|
6
|
+
<v-col>
|
|
7
|
+
<VcsLabel html-for="viewshed-editor-tools">{{
|
|
8
|
+
$t('viewshed.editor.tools')
|
|
9
|
+
}}</VcsLabel>
|
|
10
|
+
</v-col>
|
|
11
|
+
<v-col>
|
|
12
|
+
<VcsSelect
|
|
13
|
+
id="viewshed-tools"
|
|
14
|
+
:items="toolItems"
|
|
15
|
+
v-model="localConfig.tools"
|
|
16
|
+
:multiple="true"
|
|
17
|
+
/>
|
|
18
|
+
</v-col>
|
|
19
|
+
</v-row>
|
|
20
|
+
<v-row no-gutters>
|
|
21
|
+
<v-col>
|
|
22
|
+
<VcsLabel html-for="viewshed-editor-visible-color">{{
|
|
23
|
+
$t('viewshed.editor.visibleColor')
|
|
24
|
+
}}</VcsLabel>
|
|
25
|
+
</v-col>
|
|
26
|
+
<v-col>
|
|
27
|
+
<VColorPicker v-model="localConfig.visibleColor" mode="rgba" />
|
|
28
|
+
</v-col>
|
|
29
|
+
</v-row>
|
|
30
|
+
<v-row no-gutters>
|
|
31
|
+
<v-col>
|
|
32
|
+
<VcsLabel html-for="viewshed-editor-shadow-color">{{
|
|
33
|
+
$t('viewshed.editor.shadowColor')
|
|
34
|
+
}}</VcsLabel>
|
|
35
|
+
</v-col>
|
|
36
|
+
<v-col>
|
|
37
|
+
<VColorPicker v-model="localConfig.shadowColor" mode="rgba" />
|
|
38
|
+
</v-col>
|
|
39
|
+
</v-row>
|
|
40
|
+
</v-container>
|
|
41
|
+
</VcsFormSection>
|
|
42
|
+
</AbstractConfigEditor>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
import { ref } from 'vue';
|
|
47
|
+
import { VCol, VColorPicker, VContainer, VRow } from 'vuetify/lib';
|
|
48
|
+
import {
|
|
49
|
+
AbstractConfigEditor,
|
|
50
|
+
VcsFormSection,
|
|
51
|
+
VcsLabel,
|
|
52
|
+
VcsSelect,
|
|
53
|
+
} from '@vcmap/ui';
|
|
54
|
+
import Viewshed, { ViewshedTypes } from './viewshed.js';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Object} ViewshedConfig
|
|
58
|
+
* @property {string} visibleColor The color of the viewsheds visible parts.
|
|
59
|
+
* @property {string} shadowColor The color of the viewsheds hidden parts.
|
|
60
|
+
* @property {Array<ViewshedTypes>} tools The tools that are available in the toolbox.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns the default viewshed options.
|
|
65
|
+
* @returns {ViewshedConfig}
|
|
66
|
+
*/
|
|
67
|
+
export function getDefaultOptions() {
|
|
68
|
+
return {
|
|
69
|
+
visibleColor: Viewshed.getDefaultOptions().visibleColor,
|
|
70
|
+
shadowColor: Viewshed.getDefaultOptions().shadowColor,
|
|
71
|
+
tools: [...Object.values(ViewshedTypes)],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
name: 'ViewshedConfigEditor',
|
|
77
|
+
title: 'Viewshed Editor',
|
|
78
|
+
components: {
|
|
79
|
+
AbstractConfigEditor,
|
|
80
|
+
VcsFormSection,
|
|
81
|
+
VContainer,
|
|
82
|
+
VRow,
|
|
83
|
+
VCol,
|
|
84
|
+
VcsLabel,
|
|
85
|
+
VcsSelect,
|
|
86
|
+
VColorPicker,
|
|
87
|
+
},
|
|
88
|
+
props: {
|
|
89
|
+
getConfig: {
|
|
90
|
+
type: Function,
|
|
91
|
+
required: true,
|
|
92
|
+
},
|
|
93
|
+
setConfig: {
|
|
94
|
+
type: Function,
|
|
95
|
+
required: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
setup(props) {
|
|
99
|
+
/** @type {import("vue").Ref<import("./index.js").ViewshedPluginOptions>} */
|
|
100
|
+
const localConfig = ref({});
|
|
101
|
+
/** @type {import("vue").Ref<import("./index.js").ViewshedPluginOptions>} */
|
|
102
|
+
props.getConfig().then((config) => {
|
|
103
|
+
localConfig.value = Object.assign(getDefaultOptions(), config);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
async function apply() {
|
|
107
|
+
await props.setConfig(localConfig.value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
localConfig,
|
|
112
|
+
ViewshedTypes,
|
|
113
|
+
apply,
|
|
114
|
+
toolItems: Object.values(ViewshedTypes).map((type) => ({
|
|
115
|
+
value: type,
|
|
116
|
+
text: `viewshed.${type}`,
|
|
117
|
+
})),
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
</script>
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="viewshedMode === ViewshedPluginModes.CREATE" class="px-2 py-1">
|
|
4
|
+
{{ $t('viewshed.createDescription') }}
|
|
5
|
+
</div>
|
|
6
|
+
<VcsFormSection
|
|
7
|
+
v-else-if="
|
|
8
|
+
viewshedMode === ViewshedPluginModes.EDIT ||
|
|
9
|
+
viewshedMode === ViewshedPluginModes.MOVE
|
|
10
|
+
"
|
|
11
|
+
:header-actions="headerActions"
|
|
12
|
+
heading="viewshed.viewpoint"
|
|
13
|
+
:action-button-list-overflow-count="3"
|
|
14
|
+
>
|
|
15
|
+
<v-container class="px-1 py-0">
|
|
16
|
+
<v-row no-gutters>
|
|
17
|
+
<span class="px-1 py-0">{{ $t('viewshed.position') }}</span>
|
|
18
|
+
</v-row>
|
|
19
|
+
<v-row
|
|
20
|
+
v-if="
|
|
21
|
+
viewshedMode === ViewshedPluginModes.MOVE &&
|
|
22
|
+
heightMode === HeightModes.ABSOLUTE
|
|
23
|
+
"
|
|
24
|
+
no-gutters
|
|
25
|
+
>
|
|
26
|
+
<VcsFeatureTransforms
|
|
27
|
+
:transformation-mode="TransformationMode.TRANSLATE"
|
|
28
|
+
:feature-properties="{ altitudeMode: 'absolute' }"
|
|
29
|
+
:allow-z-input="true"
|
|
30
|
+
/>
|
|
31
|
+
</v-row>
|
|
32
|
+
<v-row v-else no-gutters>
|
|
33
|
+
<v-col
|
|
34
|
+
v-for="(value, key, index) in position"
|
|
35
|
+
:key="index"
|
|
36
|
+
class="pb-1"
|
|
37
|
+
>
|
|
38
|
+
<VcsTextField
|
|
39
|
+
:value="value"
|
|
40
|
+
@input="(v) => setPosition(v, key, index)"
|
|
41
|
+
type="number"
|
|
42
|
+
:prefix="key"
|
|
43
|
+
:show-spin-buttons="true"
|
|
44
|
+
:step="key === 'Z' ? 1 : 0.0001"
|
|
45
|
+
:unit="key === 'Z' ? 'm' : '°'"
|
|
46
|
+
:decimals="key === 'Z' ? 2 : 8"
|
|
47
|
+
:disabled="
|
|
48
|
+
viewshedMode === ViewshedPluginModes.MOVE &&
|
|
49
|
+
!(key === 'Z' && heightMode === HeightModes.RELATIVE)
|
|
50
|
+
"
|
|
51
|
+
/>
|
|
52
|
+
</v-col>
|
|
53
|
+
</v-row>
|
|
54
|
+
</v-container>
|
|
55
|
+
</VcsFormSection>
|
|
56
|
+
<v-divider />
|
|
57
|
+
<v-container class="px-1 py-0">
|
|
58
|
+
<v-row no-gutters>
|
|
59
|
+
<v-col>
|
|
60
|
+
<VcsLabel>
|
|
61
|
+
{{ $t('viewshed.heightMode') }}
|
|
62
|
+
</VcsLabel>
|
|
63
|
+
</v-col>
|
|
64
|
+
<v-col>
|
|
65
|
+
<VcsSelect
|
|
66
|
+
:items="
|
|
67
|
+
Object.values(HeightModes).map((item) => ({
|
|
68
|
+
value: item,
|
|
69
|
+
text: `viewshed.${item}`,
|
|
70
|
+
}))
|
|
71
|
+
"
|
|
72
|
+
:value="heightMode"
|
|
73
|
+
@input="changeHeightMode"
|
|
74
|
+
></VcsSelect>
|
|
75
|
+
</v-col>
|
|
76
|
+
</v-row>
|
|
77
|
+
</v-container>
|
|
78
|
+
<v-container
|
|
79
|
+
class="px-1 pt-0 pb-2"
|
|
80
|
+
v-if="
|
|
81
|
+
viewshedMode === ViewshedPluginModes.EDIT ||
|
|
82
|
+
viewshedMode === ViewshedPluginModes.MOVE
|
|
83
|
+
"
|
|
84
|
+
>
|
|
85
|
+
<v-row no-gutters v-for="(value, key) in parameters" :key="key">
|
|
86
|
+
<v-col
|
|
87
|
+
v-if="
|
|
88
|
+
key === 'distance' ||
|
|
89
|
+
(viewshedType === ViewshedTypes.CONE && key !== 'showPrimitive')
|
|
90
|
+
"
|
|
91
|
+
>
|
|
92
|
+
<v-row no-gutters class="pr-1">
|
|
93
|
+
<v-col>
|
|
94
|
+
<VcsLabel :html-for="key">
|
|
95
|
+
{{ $t(`viewshed.${key}`) }}
|
|
96
|
+
</VcsLabel>
|
|
97
|
+
</v-col>
|
|
98
|
+
<v-col class="d-flex justify-end align-center">
|
|
99
|
+
<span v-if="key === 'distance'">{{ `${value} m` }}</span>
|
|
100
|
+
<span v-else>{{ `${value} °` }}</span>
|
|
101
|
+
</v-col>
|
|
102
|
+
</v-row>
|
|
103
|
+
<v-row no-gutters>
|
|
104
|
+
<v-col>
|
|
105
|
+
<VcsSlider
|
|
106
|
+
:id="key"
|
|
107
|
+
:value="value"
|
|
108
|
+
@input="(v) => setParameter(key, v)"
|
|
109
|
+
type="number"
|
|
110
|
+
step="1"
|
|
111
|
+
:min="parameterRanges[key][0]"
|
|
112
|
+
:max="parameterRanges[key][1]"
|
|
113
|
+
height="15"
|
|
114
|
+
/>
|
|
115
|
+
</v-col>
|
|
116
|
+
</v-row>
|
|
117
|
+
</v-col>
|
|
118
|
+
</v-row>
|
|
119
|
+
</v-container>
|
|
120
|
+
<v-divider />
|
|
121
|
+
<div
|
|
122
|
+
v-if="
|
|
123
|
+
viewshedMode === ViewshedPluginModes.EDIT ||
|
|
124
|
+
viewshedMode === ViewshedPluginModes.MOVE
|
|
125
|
+
"
|
|
126
|
+
class="d-flex w-full justify-space-between px-2 pt-2 pb-1"
|
|
127
|
+
>
|
|
128
|
+
<VcsFormButton
|
|
129
|
+
@click="addToMyWorkspace()"
|
|
130
|
+
tooltip="viewshed.addToMyWorkspace"
|
|
131
|
+
icon="$vcsComponentsPlus"
|
|
132
|
+
:disabled="currentIsPersisted || undefined"
|
|
133
|
+
/>
|
|
134
|
+
<VcsFormButton @click="createNewViewshed()" variant="filled">
|
|
135
|
+
{{ $t('viewshed.new') }}
|
|
136
|
+
</VcsFormButton>
|
|
137
|
+
</div>
|
|
138
|
+
<div
|
|
139
|
+
v-else-if="viewshedMode === ViewshedPluginModes.CREATE"
|
|
140
|
+
class="d-flex w-full justify-end px-2 pt-2 pb-1"
|
|
141
|
+
>
|
|
142
|
+
<VcsFormButton @click="cancel()" variant="filled">
|
|
143
|
+
{{ $t('viewshed.cancel') }}
|
|
144
|
+
</VcsFormButton>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</template>
|
|
148
|
+
|
|
149
|
+
<script>
|
|
150
|
+
import {
|
|
151
|
+
computed,
|
|
152
|
+
inject,
|
|
153
|
+
onMounted,
|
|
154
|
+
onUnmounted,
|
|
155
|
+
reactive,
|
|
156
|
+
ref,
|
|
157
|
+
shallowRef,
|
|
158
|
+
watch,
|
|
159
|
+
watchEffect,
|
|
160
|
+
} from 'vue';
|
|
161
|
+
import { VCol, VContainer, VDivider, VRow } from 'vuetify/lib';
|
|
162
|
+
import { TransformationMode, Viewpoint } from '@vcmap/core';
|
|
163
|
+
import {
|
|
164
|
+
VcsFormButton,
|
|
165
|
+
VcsFormSection,
|
|
166
|
+
VcsLabel,
|
|
167
|
+
VcsSelect,
|
|
168
|
+
VcsSlider,
|
|
169
|
+
VcsTextField,
|
|
170
|
+
VcsFeatureTransforms,
|
|
171
|
+
} from '@vcmap/ui';
|
|
172
|
+
import { ViewshedPluginModes, HeightModes } from './viewshedManager.js';
|
|
173
|
+
import Viewshed, { ViewshedTypes } from './viewshed.js';
|
|
174
|
+
|
|
175
|
+
const parameterRanges = {
|
|
176
|
+
distance: [Viewshed.MIN_DISTANCE, 2000],
|
|
177
|
+
fov: [25, 85],
|
|
178
|
+
heading: [0, 360],
|
|
179
|
+
pitch: [-90, 90],
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export default {
|
|
183
|
+
components: {
|
|
184
|
+
VcsTextField,
|
|
185
|
+
VContainer,
|
|
186
|
+
VRow,
|
|
187
|
+
VCol,
|
|
188
|
+
VcsSlider,
|
|
189
|
+
VcsLabel,
|
|
190
|
+
VcsFormSection,
|
|
191
|
+
VDivider,
|
|
192
|
+
VcsSelect,
|
|
193
|
+
VcsFormButton,
|
|
194
|
+
VcsFeatureTransforms,
|
|
195
|
+
},
|
|
196
|
+
name: 'ViewshedWindow',
|
|
197
|
+
props: {
|
|
198
|
+
getViewshed: {
|
|
199
|
+
type: Function,
|
|
200
|
+
required: false,
|
|
201
|
+
default: undefined,
|
|
202
|
+
},
|
|
203
|
+
selection: {
|
|
204
|
+
type: Object,
|
|
205
|
+
required: false,
|
|
206
|
+
default: undefined,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
setup(props) {
|
|
210
|
+
const viewshedManager =
|
|
211
|
+
/** @type {import("./viewshedManager.js").ViewshedManager} */ (
|
|
212
|
+
inject('manager')
|
|
213
|
+
);
|
|
214
|
+
const app = /** @type {import("@vcmap/ui").VcsUiApp} */ (
|
|
215
|
+
inject('vcsApp')
|
|
216
|
+
);
|
|
217
|
+
const {
|
|
218
|
+
currentViewshed,
|
|
219
|
+
mode: viewshedMode,
|
|
220
|
+
currentIsPersisted,
|
|
221
|
+
} = viewshedManager;
|
|
222
|
+
|
|
223
|
+
const selection = ref(props.selection);
|
|
224
|
+
|
|
225
|
+
let removePositionChangedListener = () => {};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Position of the viewshed as coordinates array. Z value is either absolute height (heightMode === HeightModes.ABSOLUTE) or relative height (heightMode === HeightModes.RELATIVE);
|
|
229
|
+
* @type {{X: number | undefined, Y: number | undefined, Z: number | undefined}}
|
|
230
|
+
*/
|
|
231
|
+
const position = reactive({
|
|
232
|
+
X: undefined,
|
|
233
|
+
Y: undefined,
|
|
234
|
+
Z: undefined,
|
|
235
|
+
});
|
|
236
|
+
/** Cached view point when jumping to view point of viewshed. This allows to jump back to prev position when clicking 'jump to viewpoint' again, without changing position. */
|
|
237
|
+
const cachedViewpoint = shallowRef();
|
|
238
|
+
/** If currently in `jumpToViewpoint` mode: sets cached viewpoint on null, deletes camera changed listener and reactivates viewshed. Otherwise empty function. */
|
|
239
|
+
let deleteCachedViewpoint = () => {};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @type {{showPrimitive: boolean, distance: number | undefined, fov: number | undefined, heading: number | undefined, pitch: number | undefined}}
|
|
243
|
+
*/
|
|
244
|
+
const parameters = reactive({
|
|
245
|
+
showPrimitive: false,
|
|
246
|
+
distance: undefined,
|
|
247
|
+
fov: undefined,
|
|
248
|
+
heading: undefined,
|
|
249
|
+
pitch: undefined,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const viewshedType = ref();
|
|
253
|
+
|
|
254
|
+
function updatePosition() {
|
|
255
|
+
if (currentViewshed.value) {
|
|
256
|
+
position.X = currentViewshed.value.position[0];
|
|
257
|
+
position.Y = currentViewshed.value.position[1];
|
|
258
|
+
position.Z =
|
|
259
|
+
viewshedManager.heightMode.value === HeightModes.ABSOLUTE
|
|
260
|
+
? currentViewshed.value.position[2]
|
|
261
|
+
: currentViewshed.value.heightOffset;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function updateWindow() {
|
|
266
|
+
updatePosition();
|
|
267
|
+
|
|
268
|
+
if (currentViewshed.value) {
|
|
269
|
+
parameters.distance = currentViewshed.value.distance;
|
|
270
|
+
parameters.fov = currentViewshed.value.fov;
|
|
271
|
+
parameters.heading = currentViewshed.value.heading;
|
|
272
|
+
parameters.pitch = currentViewshed.value.pitch;
|
|
273
|
+
parameters.showPrimitive = currentViewshed.value.showPrimitive;
|
|
274
|
+
|
|
275
|
+
viewshedType.value = currentViewshed.value.type;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
watchEffect(() => {
|
|
280
|
+
if (
|
|
281
|
+
currentViewshed.value &&
|
|
282
|
+
viewshedMode.value !== ViewshedPluginModes.CREATE
|
|
283
|
+
) {
|
|
284
|
+
updateWindow();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
watch(
|
|
289
|
+
currentViewshed,
|
|
290
|
+
() => {
|
|
291
|
+
if (currentViewshed.value) {
|
|
292
|
+
removePositionChangedListener();
|
|
293
|
+
removePositionChangedListener =
|
|
294
|
+
currentViewshed.value.positionChanged.addEventListener(() => {
|
|
295
|
+
updatePosition();
|
|
296
|
+
deleteCachedViewpoint();
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{ immediate: true },
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Deactivates viewshed and sets up a camera changes listener, which is destroyed when `deleteCachedViewpoint()` is called.
|
|
305
|
+
*/
|
|
306
|
+
async function setUpCameraChangedListener() {
|
|
307
|
+
currentViewshed.value?.deactivate();
|
|
308
|
+
const camera = /** @type {import("@vcmap/core").CesiumMap} */ (
|
|
309
|
+
app.maps.activeMap
|
|
310
|
+
).getScene()?.camera;
|
|
311
|
+
const listener = camera?.changed.addEventListener(() => {
|
|
312
|
+
deleteCachedViewpoint();
|
|
313
|
+
});
|
|
314
|
+
deleteCachedViewpoint = () => {
|
|
315
|
+
listener?.();
|
|
316
|
+
cachedViewpoint.value = null;
|
|
317
|
+
currentViewshed.value?.activate(app.maps.activeMap);
|
|
318
|
+
deleteCachedViewpoint = () => {};
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
onMounted(() => {
|
|
323
|
+
const viewshed = props.getViewshed?.();
|
|
324
|
+
if (viewshed) {
|
|
325
|
+
// only the case for collection component editor
|
|
326
|
+
viewshedManager.editViewshed(viewshed);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
onUnmounted(() => {
|
|
331
|
+
removePositionChangedListener();
|
|
332
|
+
deleteCachedViewpoint();
|
|
333
|
+
|
|
334
|
+
if (!currentIsPersisted.value) {
|
|
335
|
+
// in case of temp editor
|
|
336
|
+
if (
|
|
337
|
+
(viewshedMode.value === ViewshedPluginModes.EDIT ||
|
|
338
|
+
viewshedMode.value === ViewshedPluginModes.MOVE) &&
|
|
339
|
+
currentViewshed.value
|
|
340
|
+
) {
|
|
341
|
+
viewshedManager.viewViewshed(currentViewshed.value);
|
|
342
|
+
} else if (viewshedMode.value === ViewshedPluginModes.CREATE) {
|
|
343
|
+
viewshedManager.stop();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (selection.value) {
|
|
347
|
+
// in case of collection component editor
|
|
348
|
+
if (selection.value.length > 1) {
|
|
349
|
+
viewshedManager.setupMultiSelect();
|
|
350
|
+
} else if (selection.value.length === 1 && currentViewshed.value) {
|
|
351
|
+
viewshedManager.viewViewshed(currentViewshed.value);
|
|
352
|
+
} else if (
|
|
353
|
+
// when a persited viewshed is deselected, stops plugin
|
|
354
|
+
!selection.value.length &&
|
|
355
|
+
viewshedMode.value === ViewshedPluginModes.EDIT
|
|
356
|
+
) {
|
|
357
|
+
viewshedManager.stop(false);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
updateWindow();
|
|
363
|
+
|
|
364
|
+
function setParameter(key, value) {
|
|
365
|
+
deleteCachedViewpoint();
|
|
366
|
+
if (currentViewshed.value) {
|
|
367
|
+
parameters[key] = value;
|
|
368
|
+
currentViewshed.value[key] = value;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const headerActions = computed(() => {
|
|
373
|
+
const actions = [];
|
|
374
|
+
if (viewshedType.value === ViewshedTypes.CONE) {
|
|
375
|
+
actions.push({
|
|
376
|
+
name: 'jumpToViewpoint',
|
|
377
|
+
title: cachedViewpoint.value
|
|
378
|
+
? 'viewshed.returnToViewpoint'
|
|
379
|
+
: 'viewshed.jumpToViewpoint',
|
|
380
|
+
icon: 'mdi-human-male',
|
|
381
|
+
active: !!cachedViewpoint.value,
|
|
382
|
+
async callback() {
|
|
383
|
+
viewshedManager.moveCurrentViewshed(false);
|
|
384
|
+
if (currentViewshed.value && !cachedViewpoint.value) {
|
|
385
|
+
cachedViewpoint.value =
|
|
386
|
+
await app.maps.activeMap?.getViewpoint();
|
|
387
|
+
await app.maps.activeMap?.gotoViewpoint(
|
|
388
|
+
new Viewpoint({
|
|
389
|
+
groundPosition: [
|
|
390
|
+
currentViewshed.value.position[0],
|
|
391
|
+
currentViewshed.value.position[1],
|
|
392
|
+
currentViewshed.value.position[2] +
|
|
393
|
+
currentViewshed.value.heightOffset,
|
|
394
|
+
],
|
|
395
|
+
heading: currentViewshed.value.heading,
|
|
396
|
+
pitch: currentViewshed.value.pitch,
|
|
397
|
+
distance: 0,
|
|
398
|
+
animate: true, // if not animate, async does not work and the cameraChanged listener is triggered immediately. alternatively set timeout
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
401
|
+
setUpCameraChangedListener();
|
|
402
|
+
} else if (cachedViewpoint.value) {
|
|
403
|
+
app.maps.activeMap?.gotoViewpoint(cachedViewpoint.value);
|
|
404
|
+
deleteCachedViewpoint();
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
actions.push({
|
|
410
|
+
name: 'moveViewshed',
|
|
411
|
+
title: 'viewshed.move',
|
|
412
|
+
icon:
|
|
413
|
+
viewshedManager.heightMode.value === HeightModes.RELATIVE
|
|
414
|
+
? '$vcsEditVertices'
|
|
415
|
+
: 'mdi-axis-arrow',
|
|
416
|
+
active: viewshedMode.value === ViewshedPluginModes.MOVE,
|
|
417
|
+
callback() {
|
|
418
|
+
viewshedManager.moveCurrentViewshed(
|
|
419
|
+
viewshedMode.value !== ViewshedPluginModes.MOVE,
|
|
420
|
+
);
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
actions.unshift({
|
|
424
|
+
name: 'showPrimitive',
|
|
425
|
+
title: 'viewshed.showPrimitive',
|
|
426
|
+
icon: 'mdi-eye',
|
|
427
|
+
active: parameters.showPrimitive,
|
|
428
|
+
callback() {
|
|
429
|
+
this.active = !this.active;
|
|
430
|
+
setParameter('showPrimitive', this.active);
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
return actions;
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
position,
|
|
438
|
+
setPosition(value, key, index) {
|
|
439
|
+
deleteCachedViewpoint();
|
|
440
|
+
if (!currentViewshed.value) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (
|
|
445
|
+
index === 2 &&
|
|
446
|
+
viewshedManager.heightMode.value === HeightModes.RELATIVE
|
|
447
|
+
) {
|
|
448
|
+
currentViewshed.value.heightOffset = Number(value);
|
|
449
|
+
} else {
|
|
450
|
+
const newPosition = [...currentViewshed.value.position];
|
|
451
|
+
newPosition[index] = Number(value);
|
|
452
|
+
currentViewshed.value.position = newPosition;
|
|
453
|
+
}
|
|
454
|
+
position[key] = Number(value);
|
|
455
|
+
},
|
|
456
|
+
parameters,
|
|
457
|
+
setParameter,
|
|
458
|
+
parameterRanges,
|
|
459
|
+
ViewshedPluginModes,
|
|
460
|
+
viewshedMode,
|
|
461
|
+
currentIsPersisted,
|
|
462
|
+
viewshedType,
|
|
463
|
+
ViewshedTypes,
|
|
464
|
+
HeightModes,
|
|
465
|
+
heightMode: viewshedManager.heightMode,
|
|
466
|
+
TransformationMode,
|
|
467
|
+
changeHeightMode: viewshedManager.changeHeightMode,
|
|
468
|
+
async createNewViewshed() {
|
|
469
|
+
await viewshedManager.createViewshed(viewshedType.value);
|
|
470
|
+
},
|
|
471
|
+
addToMyWorkspace: () => viewshedManager.persistCurrent(),
|
|
472
|
+
cancel: () => viewshedManager.stop(),
|
|
473
|
+
headerActions,
|
|
474
|
+
};
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
</script>
|