@vcmap/core 5.0.0-rc.23 → 5.0.0-rc.24
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/index.d.ts +597 -98
- package/index.js +24 -9
- package/package.json +2 -2
- package/src/category/category.js +1 -1
- package/src/featureProvider/abstractFeatureProvider.js +1 -18
- package/src/interaction/eventHandler.js +14 -0
- package/src/layer/cesium/clusterContext.js +12 -0
- package/src/layer/cesium/vectorCesiumImpl.js +2 -2
- package/src/layer/cesium/vectorContext.js +115 -7
- package/src/layer/cesiumTilesetLayer.js +0 -14
- package/src/layer/czmlLayer.js +1 -1
- package/src/layer/dataSourceLayer.js +1 -53
- package/src/layer/featureLayer.js +0 -44
- package/src/layer/featureStoreLayer.js +0 -15
- package/src/layer/layer.js +0 -11
- package/src/layer/vectorHelpers.js +0 -85
- package/src/layer/vectorLayer.js +0 -9
- package/src/layer/vectorProperties.js +150 -8
- package/src/layer/vectorTileLayer.js +0 -9
- package/src/map/cesiumMap.js +26 -7
- package/src/style/arcStyle.js +316 -0
- package/src/style/arrowStyle.js +269 -0
- package/src/util/editor/createFeatureSession.js +3 -1
- package/src/util/editor/editFeaturesSession.js +315 -0
- package/src/util/editor/editGeometrySession.js +5 -1
- package/src/util/editor/editorHelpers.js +118 -14
- package/src/util/editor/editorSessionHelpers.js +12 -0
- package/src/util/editor/editorSymbols.js +6 -0
- package/src/util/editor/interactions/editFeaturesMouseOverInteraction.js +120 -0
- package/src/util/editor/interactions/editGeometryMouseOverInteraction.js +1 -3
- package/src/util/editor/interactions/ensureHandlerSelectionInteraction.js +48 -0
- package/src/util/editor/interactions/mapInteractionController.js +5 -2
- package/src/util/editor/interactions/selectMultiFeatureInteraction.js +146 -0
- package/src/util/editor/interactions/translateVertexInteraction.js +2 -2
- package/src/util/editor/transformation/create2DHandlers.js +294 -0
- package/src/util/editor/transformation/create3DHandlers.js +575 -0
- package/src/util/editor/transformation/extrudeInteraction.js +91 -0
- package/src/util/editor/transformation/rotateInteraction.js +188 -0
- package/src/util/editor/transformation/scaleInteraction.js +185 -0
- package/src/util/editor/transformation/transformationHandler.js +168 -0
- package/src/util/editor/transformation/transformationTypes.js +83 -0
- package/src/util/editor/transformation/translateInteraction.js +209 -0
- package/src/util/featureconverter/arcToCesium.js +87 -0
- package/src/util/featureconverter/convert.js +7 -1
- package/src/util/featureconverter/extent3D.js +64 -1
- package/src/util/featureconverter/lineStringToCesium.js +103 -2
- package/src/util/featureconverter/pointHelpers.js +341 -0
- package/src/util/featureconverter/pointToCesium.js +27 -76
- package/src/util/geometryHelpers.js +11 -8
- package/src/util/math.js +99 -2
- package/tests/unit/helpers/cesiumHelpers.js +14 -4
- package/tests/unit/helpers/helpers.js +13 -0
- package/src/featureProvider/featureProviderHelpers.js +0 -50
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { Circle, Fill, Style, Stroke } from 'ol/style.js';
|
|
2
|
+
import { getLogger } from '@vcsuite/logger';
|
|
3
|
+
|
|
4
|
+
import VcsEvent from '../../vcsEvent.js';
|
|
5
|
+
import { SessionType, setupInteractionChain, setupScratchLayer } from './editorSessionHelpers.js';
|
|
6
|
+
import SelectMultiFeatureInteraction from './interactions/selectMultiFeatureInteraction.js';
|
|
7
|
+
import EditFeaturesMouseOverInteraction from './interactions/editFeaturesMouseOverInteraction.js';
|
|
8
|
+
import createTransformationHandler from './transformation/transformationHandler.js';
|
|
9
|
+
import { TransformationMode } from './transformation/transformationTypes.js';
|
|
10
|
+
import MapInteractionController from './interactions/mapInteractionController.js';
|
|
11
|
+
import TranslateInteraction from './transformation/translateInteraction.js';
|
|
12
|
+
import RotateInteraction from './transformation/rotateInteraction.js';
|
|
13
|
+
import ScaleInteraction from './transformation/scaleInteraction.js';
|
|
14
|
+
import { createSync, obliqueGeometry } from '../../layer/vectorSymbols.js';
|
|
15
|
+
import ExtrudeInteraction from './transformation/extrudeInteraction.js';
|
|
16
|
+
import { ModificationKeyType } from '../../interaction/interactionType.js';
|
|
17
|
+
import ObliqueMap from '../../map/obliqueMap.js';
|
|
18
|
+
import { ensureFeatureAbsolute } from './editorHelpers.js';
|
|
19
|
+
import CesiumMap from '../../map/cesiumMap.js';
|
|
20
|
+
import EnsureHandlerSelectionInteraction from './interactions/ensureHandlerSelectionInteraction.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {EditorSession} EditFeaturesSession
|
|
24
|
+
* @property {SelectMultiFeatureInteraction} featureSelection
|
|
25
|
+
* @property {TransformationMode} mode - read only access to the current mode
|
|
26
|
+
* @property {function(TransformationMode):void} setMode
|
|
27
|
+
* @property {import("@vcmap/core").VcsEvent<TransformationMode>} modeChanged
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a selection set from the selected features. This maintains `createSync` symbol, highlight and allow picking on
|
|
32
|
+
* currently selected features and listens to changes until stopped.
|
|
33
|
+
* @param {import("@vcmap/core").VectorLayer} layer
|
|
34
|
+
* @param {SelectMultiFeatureInteraction} selectFeatureInteraction
|
|
35
|
+
* @param {import("ol/style").Style} highlightStyle
|
|
36
|
+
* @param {import("@vcmap/core").EventHandler} eventHandler
|
|
37
|
+
* @returns {function():void} un-highlight all and stop listening to changes
|
|
38
|
+
*/
|
|
39
|
+
function createSelectionSet(layer, selectFeatureInteraction, highlightStyle, eventHandler) {
|
|
40
|
+
let currentIds = new Set();
|
|
41
|
+
/** @type {Map<string|number, boolean|undefined>} */
|
|
42
|
+
const allowPickingMap = new Map();
|
|
43
|
+
const modifierChangedListener = eventHandler.modifierChanged.addEventListener((mod) => { // CTRL is used to modify the current selection set, we must allow picking again so you can deselect a feature
|
|
44
|
+
const allowPicking = mod === ModificationKeyType.CTRL;
|
|
45
|
+
selectFeatureInteraction.selectedFeatures.forEach((f) => { f.set('olcs_allowPicking', allowPicking); });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const clearFeature = (f) => {
|
|
49
|
+
delete f[createSync];
|
|
50
|
+
const allowPicking = allowPickingMap.get(f.getId());
|
|
51
|
+
if (allowPicking != null) {
|
|
52
|
+
f.set('olcs_allowPicking', allowPicking);
|
|
53
|
+
} else {
|
|
54
|
+
f.unset('olcs_allowPicking');
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const featureChangedListener = selectFeatureInteraction.featuresChanged.addEventListener((newFeatures) => {
|
|
59
|
+
const newIds = new Set(newFeatures.map((f) => {
|
|
60
|
+
const id = f.getId();
|
|
61
|
+
if (!allowPickingMap.has(id)) {
|
|
62
|
+
allowPickingMap.set(id, f.get('olcs_allowPicking'));
|
|
63
|
+
}
|
|
64
|
+
f[createSync] = true;
|
|
65
|
+
f.set('olcs_allowPicking', false);
|
|
66
|
+
return id;
|
|
67
|
+
}));
|
|
68
|
+
const idsToHighlight = [];
|
|
69
|
+
newIds.forEach((id) => {
|
|
70
|
+
if (!currentIds.has(id)) {
|
|
71
|
+
idsToHighlight.push(id);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const idsToUnHighlight = [];
|
|
76
|
+
currentIds.forEach((id) => {
|
|
77
|
+
if (!newIds.has(id)) {
|
|
78
|
+
idsToUnHighlight.push(id);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
layer.featureVisibility.unHighlight(idsToUnHighlight);
|
|
83
|
+
layer.featureVisibility.highlight(Object.fromEntries(idsToHighlight.map(id => [id, highlightStyle])));
|
|
84
|
+
layer.getFeaturesById(idsToUnHighlight)
|
|
85
|
+
.forEach(clearFeature);
|
|
86
|
+
|
|
87
|
+
currentIds = newIds;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return () => {
|
|
91
|
+
modifierChangedListener();
|
|
92
|
+
featureChangedListener();
|
|
93
|
+
if (currentIds.size > 0) {
|
|
94
|
+
const idsToUnHighlight = [...currentIds];
|
|
95
|
+
layer.getFeaturesById(idsToUnHighlight)
|
|
96
|
+
.forEach(clearFeature);
|
|
97
|
+
layer.featureVisibility.unHighlight(idsToUnHighlight);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @returns {import("ol/style").Style}
|
|
104
|
+
*/
|
|
105
|
+
function getDefaultHighlightStyle() {
|
|
106
|
+
const fill = new Fill({ color: 'rgba(76,175,80,0.2)' });
|
|
107
|
+
const stroke = new Stroke({ color: '#4CAF50', width: 2 });
|
|
108
|
+
|
|
109
|
+
return new Style({
|
|
110
|
+
fill,
|
|
111
|
+
stroke,
|
|
112
|
+
image: new Circle({
|
|
113
|
+
fill,
|
|
114
|
+
stroke,
|
|
115
|
+
radius: 14,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates an editor session to select, translate, rotate & scale the feature on a given layer
|
|
122
|
+
* @param {import("@vcmap/core").VcsApp} app
|
|
123
|
+
* @param {import("@vcmap/core").VectorLayer} layer
|
|
124
|
+
* @param {TransformationMode=} [initialMode=TransformationMode.TRANSLATE]
|
|
125
|
+
* @param {import("ol/style").Style=} [highlightStyle]
|
|
126
|
+
* @returns {EditFeaturesSession}
|
|
127
|
+
*/
|
|
128
|
+
function startEditFeaturesSession(
|
|
129
|
+
app,
|
|
130
|
+
layer,
|
|
131
|
+
initialMode = TransformationMode.TRANSLATE,
|
|
132
|
+
highlightStyle = getDefaultHighlightStyle(),
|
|
133
|
+
) {
|
|
134
|
+
const scratchLayer = setupScratchLayer(app.layers);
|
|
135
|
+
/** @type {VcsEvent<void>} */
|
|
136
|
+
const stopped = new VcsEvent();
|
|
137
|
+
const {
|
|
138
|
+
interactionChain,
|
|
139
|
+
removed: interactionRemoved,
|
|
140
|
+
destroy: destroyInteractionChain,
|
|
141
|
+
} = setupInteractionChain(app.maps.eventHandler);
|
|
142
|
+
const selectFeatureInteraction = new SelectMultiFeatureInteraction(layer);
|
|
143
|
+
const destroySelectionSet = createSelectionSet(
|
|
144
|
+
layer,
|
|
145
|
+
selectFeatureInteraction,
|
|
146
|
+
highlightStyle,
|
|
147
|
+
app.maps.eventHandler,
|
|
148
|
+
);
|
|
149
|
+
interactionChain.addInteraction(selectFeatureInteraction);
|
|
150
|
+
interactionChain.addInteraction(new EnsureHandlerSelectionInteraction(selectFeatureInteraction));
|
|
151
|
+
|
|
152
|
+
const mapInteractionController = new MapInteractionController();
|
|
153
|
+
interactionChain.addInteraction(mapInteractionController);
|
|
154
|
+
|
|
155
|
+
const mouseOverInteraction = new EditFeaturesMouseOverInteraction(
|
|
156
|
+
layer.name,
|
|
157
|
+
selectFeatureInteraction,
|
|
158
|
+
);
|
|
159
|
+
interactionChain.addInteraction(mouseOverInteraction);
|
|
160
|
+
|
|
161
|
+
let mode = initialMode;
|
|
162
|
+
let destroyTransformation = () => {};
|
|
163
|
+
const createTransformations = () => {
|
|
164
|
+
destroyTransformation();
|
|
165
|
+
const transformationHandler = createTransformationHandler(
|
|
166
|
+
app.maps.activeMap,
|
|
167
|
+
layer,
|
|
168
|
+
selectFeatureInteraction,
|
|
169
|
+
scratchLayer,
|
|
170
|
+
mode,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
let interaction;
|
|
174
|
+
if (mode === TransformationMode.TRANSLATE) {
|
|
175
|
+
interaction = new TranslateInteraction(transformationHandler);
|
|
176
|
+
interaction.translated.addEventListener(([dx, dy, dz]) => {
|
|
177
|
+
transformationHandler.translate(dx, dy, dz);
|
|
178
|
+
selectFeatureInteraction.selectedFeatures.forEach((f) => {
|
|
179
|
+
const geometry = f[obliqueGeometry] ?? f.getGeometry();
|
|
180
|
+
geometry.applyTransform((input, output) => {
|
|
181
|
+
const inputLength = input.length;
|
|
182
|
+
for (let i = 0; i < inputLength; i += 3) {
|
|
183
|
+
output[i] = input[i] + dx;
|
|
184
|
+
output[i + 1] = input[i + 1] + dy;
|
|
185
|
+
output[i + 2] = input[i + 2] + dz;
|
|
186
|
+
}
|
|
187
|
+
return output;
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
} else if (mode === TransformationMode.EXTRUDE) {
|
|
192
|
+
interaction = new ExtrudeInteraction(transformationHandler);
|
|
193
|
+
interaction.extruded.addEventListener((dz) => {
|
|
194
|
+
selectFeatureInteraction.selectedFeatures.forEach((f) => {
|
|
195
|
+
ensureFeatureAbsolute(f, layer, app.maps.activeMap);
|
|
196
|
+
let extrudedHeight = f.get('olcs_extrudedHeight') ?? 0;
|
|
197
|
+
extrudedHeight += dz;
|
|
198
|
+
f.set('olcs_extrudedHeight', extrudedHeight);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
} else if (mode === TransformationMode.ROTATE) {
|
|
202
|
+
interaction = new RotateInteraction(transformationHandler);
|
|
203
|
+
interaction.rotated.addEventListener(({ angle }) => {
|
|
204
|
+
const { center } = transformationHandler;
|
|
205
|
+
selectFeatureInteraction.selectedFeatures.forEach((f) => {
|
|
206
|
+
const geometry = f[obliqueGeometry] ?? f.getGeometry();
|
|
207
|
+
geometry.rotate(angle, center);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
} else if (mode === TransformationMode.SCALE) {
|
|
211
|
+
interaction = new ScaleInteraction(transformationHandler);
|
|
212
|
+
interaction.scaled.addEventListener(([sx, sy]) => {
|
|
213
|
+
const { center } = transformationHandler;
|
|
214
|
+
selectFeatureInteraction.selectedFeatures.forEach((f) => {
|
|
215
|
+
const geometry = f[obliqueGeometry] ?? f.getGeometry();
|
|
216
|
+
geometry.scale(sx, sy, center);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
throw new Error(`Unknown transformation mode ${mode}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interactionChain.addInteraction(interaction);
|
|
224
|
+
|
|
225
|
+
destroyTransformation = () => {
|
|
226
|
+
interactionChain.removeInteraction(interaction);
|
|
227
|
+
interaction.destroy();
|
|
228
|
+
transformationHandler.destroy();
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @type {VcsEvent<TransformationMode>}
|
|
234
|
+
*/
|
|
235
|
+
const modeChanged = new VcsEvent();
|
|
236
|
+
const setMode = (newMode) => {
|
|
237
|
+
if (newMode !== mode) {
|
|
238
|
+
if (newMode === TransformationMode.EXTRUDE && !(app.maps.activeMap instanceof CesiumMap)) {
|
|
239
|
+
getLogger('EditFeaturesSession').warning('Cannot set extrude mode if map is not a CesiumMap');
|
|
240
|
+
} else {
|
|
241
|
+
mode = newMode;
|
|
242
|
+
createTransformations();
|
|
243
|
+
modeChanged.raiseEvent(mode);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* @type {ObliqueMap|null}
|
|
249
|
+
*/
|
|
250
|
+
let obliqueMap = null;
|
|
251
|
+
/**
|
|
252
|
+
* @type {function():void}
|
|
253
|
+
*/
|
|
254
|
+
let obliqueImageChangedListener = () => {};
|
|
255
|
+
const mapChanged = (map) => {
|
|
256
|
+
obliqueImageChangedListener();
|
|
257
|
+
if (map instanceof ObliqueMap) {
|
|
258
|
+
selectFeatureInteraction.clear();
|
|
259
|
+
obliqueImageChangedListener = map.imageChanged.addEventListener(() => {
|
|
260
|
+
selectFeatureInteraction.clear();
|
|
261
|
+
createTransformations();
|
|
262
|
+
});
|
|
263
|
+
obliqueMap = map;
|
|
264
|
+
} else {
|
|
265
|
+
if (obliqueMap) {
|
|
266
|
+
selectFeatureInteraction.clear();
|
|
267
|
+
}
|
|
268
|
+
obliqueMap = null;
|
|
269
|
+
obliqueImageChangedListener = () => {};
|
|
270
|
+
}
|
|
271
|
+
if (mode === TransformationMode.EXTRUDE && !(map instanceof CesiumMap)) {
|
|
272
|
+
setMode(TransformationMode.TRANSLATE);
|
|
273
|
+
} else {
|
|
274
|
+
createTransformations();
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const mapChangedListener = app.maps.mapActivated.addEventListener(mapChanged);
|
|
278
|
+
mapChanged(app.maps.activeMap);
|
|
279
|
+
|
|
280
|
+
selectFeatureInteraction.featuresChanged.addEventListener((selectedFeatures) => {
|
|
281
|
+
if (obliqueMap) {
|
|
282
|
+
obliqueMap.switchEnabled = selectedFeatures.length === 0;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const stop = () => {
|
|
287
|
+
obliqueImageChangedListener();
|
|
288
|
+
if (obliqueMap) {
|
|
289
|
+
obliqueMap.switchEnabled = true;
|
|
290
|
+
}
|
|
291
|
+
mapChangedListener();
|
|
292
|
+
destroyTransformation();
|
|
293
|
+
destroySelectionSet();
|
|
294
|
+
app.layers.remove(scratchLayer);
|
|
295
|
+
destroyInteractionChain();
|
|
296
|
+
stopped.raiseEvent();
|
|
297
|
+
stopped.destroy();
|
|
298
|
+
modeChanged.destroy();
|
|
299
|
+
};
|
|
300
|
+
interactionRemoved.addEventListener(stop);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
type: SessionType.EDIT_FEATURES,
|
|
304
|
+
featureSelection: selectFeatureInteraction,
|
|
305
|
+
stopped,
|
|
306
|
+
stop,
|
|
307
|
+
get mode() {
|
|
308
|
+
return mode;
|
|
309
|
+
},
|
|
310
|
+
modeChanged,
|
|
311
|
+
setMode,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default startEditFeaturesSession;
|
|
@@ -14,6 +14,7 @@ import geometryIsValid from './validateGeoemetry.js';
|
|
|
14
14
|
import ObliqueMap from '../../map/obliqueMap.js';
|
|
15
15
|
import { emptyStyle } from '../../style/styleHelpers.js';
|
|
16
16
|
import MapInteractionController from './interactions/mapInteractionController.js';
|
|
17
|
+
import { originalStyle } from '../../layer/featureVisibility.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* @typedef {EditorSession} EditGeometrySession
|
|
@@ -228,6 +229,7 @@ function createEditSimplePolygonInteraction(feature, scratchLayer) {
|
|
|
228
229
|
function createEditPointInteraction(feature, scratchLayer) {
|
|
229
230
|
const vertex = createVertex(feature.getGeometry().getCoordinates());
|
|
230
231
|
const featureStyle = feature.getStyle();
|
|
232
|
+
feature[originalStyle] = featureStyle;
|
|
231
233
|
feature.setStyle(emptyStyle);
|
|
232
234
|
scratchLayer.addFeatures([vertex]);
|
|
233
235
|
const translateVertex = new TranslateVertexInteraction();
|
|
@@ -255,7 +257,7 @@ function createEditPointInteraction(feature, scratchLayer) {
|
|
|
255
257
|
* @param {import("@vcmap/core").VectorLayer} layer
|
|
256
258
|
* @returns {EditGeometrySession}
|
|
257
259
|
*/
|
|
258
|
-
|
|
260
|
+
function startEditGeometrySession(app, layer) {
|
|
259
261
|
const {
|
|
260
262
|
interactionChain,
|
|
261
263
|
removed: interactionRemoved,
|
|
@@ -399,3 +401,5 @@ export default function startEditGeometrySession(app, layer) {
|
|
|
399
401
|
stop,
|
|
400
402
|
};
|
|
401
403
|
}
|
|
404
|
+
|
|
405
|
+
export default startEditGeometrySession;
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import Point from 'ol/geom/Point.js';
|
|
2
2
|
import Feature from 'ol/Feature.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
Cartesian2,
|
|
5
|
+
Cartesian3,
|
|
6
|
+
Math as CesiumMath,
|
|
7
|
+
Plane,
|
|
8
|
+
Ray, IntersectionTests, Cartographic, HeightReference,
|
|
9
|
+
} from '@vcmap/cesium';
|
|
10
|
+
|
|
11
|
+
import { mercatorToCartesian } from '../math.js';
|
|
12
|
+
import { getFlatCoordinatesFromGeometry } from '../geometryHelpers.js';
|
|
13
|
+
import CesiumMap from '../../map/cesiumMap.js';
|
|
5
14
|
import { vertexSymbol } from './editorSymbols.js';
|
|
6
15
|
import Vector from '../../layer/vectorLayer.js';
|
|
7
16
|
|
|
@@ -27,14 +36,33 @@ let scratchCartesian31 = new Cartesian3();
|
|
|
27
36
|
let scratchCartesian32 = new Cartesian3();
|
|
28
37
|
let scratchCartesian33 = new Cartesian3();
|
|
29
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Returns the closest point on a 2D line. the Z index is taken from the point.
|
|
41
|
+
* @param {import("ol/coordinate").Coordinate} start - line segment start
|
|
42
|
+
* @param {import("ol/coordinate").Coordinate} end - line segment end
|
|
43
|
+
* @param {import("ol/coordinate").Coordinate} point - point to project
|
|
44
|
+
* @returns {!import("ol/coordinate").Coordinate}
|
|
45
|
+
*/
|
|
46
|
+
export function getClosestPointOn2DLine(start, end, point) {
|
|
47
|
+
scratchCartesian21 = Cartesian2.fromElements(end[0] - start[0], end[1] - start[1], scratchCartesian21);
|
|
48
|
+
if (scratchCartesian21.equals(Cartesian2.ZERO)) {
|
|
49
|
+
scratchCartesian21 = Cartesian2.fromElements(1, 1, scratchCartesian21);
|
|
50
|
+
}
|
|
51
|
+
scratchCartesian21 = Cartesian2.normalize(scratchCartesian21, scratchCartesian21);
|
|
52
|
+
scratchCartesian22 = Cartesian2.fromElements(point[0] - start[0], point[1] - start[1], scratchCartesian22);
|
|
53
|
+
const lambda = Cartesian2.dot(scratchCartesian21, scratchCartesian22);
|
|
54
|
+
scratchCartesian21 = Cartesian2.multiplyByScalar(scratchCartesian21, lambda, scratchCartesian21);
|
|
55
|
+
return [scratchCartesian21.x + start[0], scratchCartesian21.y + start[1], point[2]];
|
|
56
|
+
}
|
|
57
|
+
|
|
30
58
|
/**
|
|
31
59
|
* @param {import("ol/coordinate").Coordinate} start - line segment start
|
|
32
60
|
* @param {import("ol/coordinate").Coordinate} end - line segment end
|
|
33
61
|
* @param {import("ol/coordinate").Coordinate} point - the point to project
|
|
34
|
-
* @param {number=}
|
|
62
|
+
* @param {number=} epsilon
|
|
35
63
|
* @returns {boolean}
|
|
36
64
|
*/
|
|
37
|
-
export function pointOnLine3D(start, end, point, epsilon
|
|
65
|
+
export function pointOnLine3D(start, end, point, epsilon) {
|
|
38
66
|
scratchCartesian31 = Cartesian3
|
|
39
67
|
.fromElements(end[0] - start[0], end[1] - start[1], end[2] - start[2], scratchCartesian31);
|
|
40
68
|
scratchCartesian32 = Cartesian3
|
|
@@ -51,18 +79,17 @@ export function pointOnLine3D(start, end, point, epsilon = CesiumMath.EPSILON5)
|
|
|
51
79
|
|
|
52
80
|
scratchCartesian31 = Cartesian3.normalize(scratchCartesian31, scratchCartesian31);
|
|
53
81
|
scratchCartesian32 = Cartesian3.normalize(scratchCartesian32, scratchCartesian32);
|
|
54
|
-
return scratchCartesian31.equalsEpsilon(scratchCartesian32, epsilon);
|
|
82
|
+
return scratchCartesian31.equalsEpsilon(scratchCartesian32, epsilon || CesiumMath.EPSILON5);
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
|
|
58
85
|
/**
|
|
59
86
|
* @param {import("ol/coordinate").Coordinate} start - line segment start
|
|
60
87
|
* @param {import("ol/coordinate").Coordinate} end - line segment end
|
|
61
88
|
* @param {import("ol/coordinate").Coordinate} point - the point to project
|
|
62
|
-
* @param {number=}
|
|
89
|
+
* @param {number=} epsilon
|
|
63
90
|
* @returns {boolean}
|
|
64
91
|
*/
|
|
65
|
-
export function pointOnLine2D(start, end, point, epsilon
|
|
92
|
+
export function pointOnLine2D(start, end, point, epsilon) {
|
|
66
93
|
scratchCartesian21 = Cartesian2.fromElements(end[0] - start[0], end[1] - start[1], scratchCartesian21);
|
|
67
94
|
scratchCartesian22 = Cartesian2.fromElements(point[0] - start[0], point[1] - start[1], scratchCartesian22);
|
|
68
95
|
scratchCartesian23 = Cartesian2.fromElements(point[0] - end[0], point[1] - end[1], scratchCartesian23);
|
|
@@ -77,7 +104,7 @@ export function pointOnLine2D(start, end, point, epsilon = CesiumMath.EPSILON5)
|
|
|
77
104
|
scratchCartesian21 = Cartesian2.normalize(scratchCartesian21, scratchCartesian21);
|
|
78
105
|
scratchCartesian22 = Cartesian2.normalize(scratchCartesian22, scratchCartesian22);
|
|
79
106
|
|
|
80
|
-
return scratchCartesian21.equalsEpsilon(scratchCartesian22, epsilon);
|
|
107
|
+
return scratchCartesian21.equalsEpsilon(scratchCartesian22, epsilon || CesiumMath.EPSILON5);
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
/**
|
|
@@ -85,9 +112,8 @@ export function pointOnLine2D(start, end, point, epsilon = CesiumMath.EPSILON5)
|
|
|
85
112
|
* @param {import("@vcmap/cesium").Scene} scene
|
|
86
113
|
* @returns {!import("@vcmap/cesium").Plane}
|
|
87
114
|
*/
|
|
88
|
-
export function
|
|
89
|
-
|
|
90
|
-
scratchCartesian31 = Cartesian3.fromDegrees(wgs84[0], wgs84[1], wgs84[2]);
|
|
115
|
+
export function createCameraVerticalPlane(originCoordinates, scene) {
|
|
116
|
+
scratchCartesian31 = mercatorToCartesian(originCoordinates, scratchCartesian31);
|
|
91
117
|
scratchCartesian32 = scene.globe.ellipsoid.geodeticSurfaceNormal(scratchCartesian31, scratchCartesian32);
|
|
92
118
|
scratchCartesian32 = Cartesian3.cross(scene.camera.rightWC, scratchCartesian32, scratchCartesian32);
|
|
93
119
|
scratchCartesian32 = Cartesian3.normalize(scratchCartesian32, scratchCartesian32);
|
|
@@ -101,9 +127,87 @@ export function createVerticalPlane(originCoordinates, scene) {
|
|
|
101
127
|
* @returns {!import("@vcmap/cesium").Plane}
|
|
102
128
|
*/
|
|
103
129
|
export function createHorizontalPlane(originCoordinates, scene) {
|
|
104
|
-
|
|
105
|
-
scratchCartesian31 = Cartesian3.fromDegrees(wgs84[0], wgs84[1], wgs84[2]);
|
|
130
|
+
scratchCartesian31 = mercatorToCartesian(originCoordinates, scratchCartesian31);
|
|
106
131
|
scratchCartesian32 = scene.globe.ellipsoid.geodeticSurfaceNormal(scratchCartesian31, scratchCartesian32);
|
|
107
132
|
|
|
108
133
|
return Plane.fromPointNormal(scratchCartesian31, scratchCartesian32);
|
|
109
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {import("@vcmap/cesium").Plane} plane
|
|
138
|
+
* @param {import("@vcmap/cesium").Camera} camera
|
|
139
|
+
* @param {import("@vcmap/cesium").Cartesian2} windowPosition
|
|
140
|
+
* @returns {import("@vcmap/cesium").Cartographic}
|
|
141
|
+
*/
|
|
142
|
+
export function getCartographicFromPlane(plane, camera, windowPosition) {
|
|
143
|
+
const ray = camera.getPickRay(windowPosition, new Ray());
|
|
144
|
+
const intersection = IntersectionTests.rayPlane(ray, plane);
|
|
145
|
+
if (intersection) {
|
|
146
|
+
return Cartographic.fromCartesian(intersection);
|
|
147
|
+
}
|
|
148
|
+
return Cartographic.ZERO;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Drapes a geometry onto the terrain by placing each coordinate at its height.
|
|
153
|
+
* @param {import("ol/geom").Geometry} geometry
|
|
154
|
+
* @param {import("@vcmap/core").VcsMap} map
|
|
155
|
+
* @returns {Promise<void>}
|
|
156
|
+
*/
|
|
157
|
+
export async function drapeGeometryOnTerrain(geometry, map) {
|
|
158
|
+
if (map instanceof CesiumMap) {
|
|
159
|
+
const coordinates = geometry.getCoordinates();
|
|
160
|
+
const flats = getFlatCoordinatesFromGeometry(geometry, coordinates);
|
|
161
|
+
await map.getHeightFromTerrain(flats);
|
|
162
|
+
geometry.setCoordinates(coordinates);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Places a geometry onto the terrain at its lowest point.
|
|
168
|
+
* @param {import("ol/geom").Geometry} geometry
|
|
169
|
+
* @param {import("@vcmap/core").VcsMap} map
|
|
170
|
+
* @returns {Promise<void>}
|
|
171
|
+
*/
|
|
172
|
+
export async function placeGeometryOnTerrain(geometry, map) {
|
|
173
|
+
if (map instanceof CesiumMap) {
|
|
174
|
+
const coordinates = geometry.getCoordinates();
|
|
175
|
+
const flats = getFlatCoordinatesFromGeometry(geometry, coordinates);
|
|
176
|
+
await map.getHeightFromTerrain(flats);
|
|
177
|
+
let minHeight = Infinity;
|
|
178
|
+
flats.forEach((coord) => {
|
|
179
|
+
if (minHeight > coord[2]) {
|
|
180
|
+
minHeight = coord[2];
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
flats.forEach((coord) => {
|
|
184
|
+
coord[2] = minHeight;
|
|
185
|
+
});
|
|
186
|
+
geometry.setCoordinates(coordinates);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {import("ol").Feature} feature
|
|
192
|
+
* @param {import("@vcmap/core").VectorLayer} layer
|
|
193
|
+
* @param {import("@vcmap/core").VcsMap} cesiumMap
|
|
194
|
+
* @returns {Promise<void>}
|
|
195
|
+
*/
|
|
196
|
+
export async function ensureFeatureAbsolute(feature, layer, cesiumMap) {
|
|
197
|
+
const layerIsClamped = layer.vectorProperties.altitudeMode === HeightReference.CLAMP_TO_GROUND;
|
|
198
|
+
const altitudeMode = feature.get('olcs_altitudeMode');
|
|
199
|
+
if (altitudeMode === 'clampToGround' || (!altitudeMode && layerIsClamped)) {
|
|
200
|
+
feature.set('olcs_altitudeMode', 'absolute', true);
|
|
201
|
+
const geometry = feature.getGeometry();
|
|
202
|
+
if (geometry) {
|
|
203
|
+
await placeGeometryOnTerrain(geometry, cesiumMap);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {import("ol").Feature} feature
|
|
210
|
+
*/
|
|
211
|
+
export function clampFeature(feature) {
|
|
212
|
+
feature.set('olcs_altitudeMode', 'clampToGround');
|
|
213
|
+
}
|
|
@@ -5,6 +5,7 @@ import VcsEvent from '../../vcsEvent.js';
|
|
|
5
5
|
import { EventType } from '../../interaction/interactionType.js';
|
|
6
6
|
import { maxZIndex } from '../layerCollection.js';
|
|
7
7
|
import { markVolatile } from '../../context.js';
|
|
8
|
+
import { PrimitiveOptionsType } from '../../layer/vectorProperties.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* An editor session is a currently set of interactions to create or edit geometries & features.
|
|
@@ -19,6 +20,9 @@ import { markVolatile } from '../../context.js';
|
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @enum {string}
|
|
23
|
+
* @property {string} CREATE
|
|
24
|
+
* @property {string} EDIT_GEOMETRY
|
|
25
|
+
* @property {string} EDIT_FEATURES
|
|
22
26
|
*/
|
|
23
27
|
export const SessionType = {
|
|
24
28
|
CREATE: 'create',
|
|
@@ -38,6 +42,14 @@ export function setupScratchLayer(layerCollection) { // IDEA pass in stopped and
|
|
|
38
42
|
vectorProperties: {
|
|
39
43
|
altitudeMode: 'clampToGround',
|
|
40
44
|
eyeOffset: [0, 0, -1],
|
|
45
|
+
primitiveOptions: {
|
|
46
|
+
type: PrimitiveOptionsType.SPHERE,
|
|
47
|
+
geometryOptions: {
|
|
48
|
+
radius: 4,
|
|
49
|
+
},
|
|
50
|
+
depthFailColor: 'rgba(255,255,255,0.47)',
|
|
51
|
+
},
|
|
52
|
+
modelAutoScale: true,
|
|
41
53
|
},
|
|
42
54
|
isDynamic: true,
|
|
43
55
|
zIndex: maxZIndex,
|
|
@@ -8,3 +8,9 @@ export const vertexSymbol = Symbol('Vertex');
|
|
|
8
8
|
* @type {symbol}
|
|
9
9
|
*/
|
|
10
10
|
export const vertexIndex = Symbol('VertexIndex');
|
|
11
|
+
/**
|
|
12
|
+
* Symbol added to primitives and features to denote that these are handlers. It is expected, that the value of the symobl is
|
|
13
|
+
* equal to an {@see AXIS_AND_PLANES}
|
|
14
|
+
* @type {symbol}
|
|
15
|
+
*/
|
|
16
|
+
export const handlerSymbol = Symbol('Handler');
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { handlerSymbol } from '../editorSymbols.js';
|
|
2
|
+
import AbstractInteraction from '../../../interaction/abstractInteraction.js';
|
|
3
|
+
import { ModificationKeyType, EventType } from '../../../interaction/interactionType.js';
|
|
4
|
+
import { vcsLayerName } from '../../../layer/layerSymbols.js';
|
|
5
|
+
import { cursorMap } from './editGeometryMouseOverInteraction.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A class to handle mouse over effects on features for editor sessions.
|
|
9
|
+
* @class
|
|
10
|
+
* @extends {AbstractInteraction}
|
|
11
|
+
*/
|
|
12
|
+
class EditFeaturesMouseOverInteraction extends AbstractInteraction {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} layerName - the layer name of the currently editing layer
|
|
15
|
+
* @param {import("@vcmap/core").SelectMultiFeatureInteraction} selectMultiFeatureInteraction
|
|
16
|
+
*/
|
|
17
|
+
constructor(layerName, selectMultiFeatureInteraction) {
|
|
18
|
+
super(EventType.MOVE, ModificationKeyType.ALL);
|
|
19
|
+
/**
|
|
20
|
+
* @type {import("ol").Feature|import("@vcmap/cesium").Cesium3DTileFeature|import("@vcmap/cesium").Cesium3DTilePointFeature|null}
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
this._lastFeature = null;
|
|
24
|
+
/**
|
|
25
|
+
* @type {import("@vcmap/core").SelectMultiFeatureInteraction}
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
this._selectMultiFeatureInteraction = selectMultiFeatureInteraction;
|
|
29
|
+
/**
|
|
30
|
+
* The layer name to react to
|
|
31
|
+
* @type {string}
|
|
32
|
+
*/
|
|
33
|
+
this.layerName = layerName;
|
|
34
|
+
/**
|
|
35
|
+
* @type {CSSStyleDeclaration}
|
|
36
|
+
*/
|
|
37
|
+
this.cursorStyle = document.body.style;
|
|
38
|
+
|
|
39
|
+
this.setActive();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @inheritDoc
|
|
44
|
+
* @param {InteractionEvent} event
|
|
45
|
+
* @returns {Promise<InteractionEvent>}
|
|
46
|
+
*/
|
|
47
|
+
async pipe(event) {
|
|
48
|
+
if (
|
|
49
|
+
event.feature &&
|
|
50
|
+
(event.feature[vcsLayerName] === this.layerName || event.feature[handlerSymbol])
|
|
51
|
+
) {
|
|
52
|
+
this._lastFeature = /** @type {import("ol").Feature|import("@vcmap/cesium").Cesium3DTileFeature|import("@vcmap/cesium").Cesium3DTilePointFeature} */
|
|
53
|
+
(event.feature);
|
|
54
|
+
} else {
|
|
55
|
+
this._lastFeature = null;
|
|
56
|
+
}
|
|
57
|
+
this._evaluate(event.key);
|
|
58
|
+
return event;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @inheritDoc
|
|
63
|
+
* @param {ModificationKeyType} modifier
|
|
64
|
+
*/
|
|
65
|
+
modifierChanged(modifier) {
|
|
66
|
+
this._evaluate(modifier);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @inheritDoc
|
|
71
|
+
* @param {(boolean|number)=} active
|
|
72
|
+
*/
|
|
73
|
+
setActive(active) {
|
|
74
|
+
super.setActive(active);
|
|
75
|
+
this.reset();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reset the cursorStyle to auto
|
|
80
|
+
*/
|
|
81
|
+
reset() {
|
|
82
|
+
if (this.cursorStyle && this.cursorStyle.cursor) {
|
|
83
|
+
this.cursorStyle.cursor = cursorMap.auto;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {ModificationKeyType} modifier
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
_evaluate(modifier) {
|
|
92
|
+
if (this._lastFeature) {
|
|
93
|
+
if (this._lastFeature[handlerSymbol]) {
|
|
94
|
+
this.cursorStyle.cursor = cursorMap.select;
|
|
95
|
+
} else if (modifier === ModificationKeyType.CTRL) {
|
|
96
|
+
if (!this._selectMultiFeatureInteraction.hasFeatureId(/** @type {string} */ (this._lastFeature.getId()))) {
|
|
97
|
+
this.cursorStyle.cursor = cursorMap.addToSelection;
|
|
98
|
+
} else {
|
|
99
|
+
this.cursorStyle.cursor = cursorMap.removeFromSelection;
|
|
100
|
+
}
|
|
101
|
+
} else if (!this._selectMultiFeatureInteraction.hasFeatureId(/** @type {string} */ (this._lastFeature.getId()))) {
|
|
102
|
+
this.cursorStyle.cursor = cursorMap.select;
|
|
103
|
+
} else {
|
|
104
|
+
this.cursorStyle.cursor = cursorMap.auto;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
this.cursorStyle.cursor = cursorMap.auto;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @inheritDoc
|
|
113
|
+
*/
|
|
114
|
+
destroy() {
|
|
115
|
+
this.cursorStyle = null;
|
|
116
|
+
super.destroy();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default EditFeaturesMouseOverInteraction;
|