@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.
Files changed (53) hide show
  1. package/index.d.ts +597 -98
  2. package/index.js +24 -9
  3. package/package.json +2 -2
  4. package/src/category/category.js +1 -1
  5. package/src/featureProvider/abstractFeatureProvider.js +1 -18
  6. package/src/interaction/eventHandler.js +14 -0
  7. package/src/layer/cesium/clusterContext.js +12 -0
  8. package/src/layer/cesium/vectorCesiumImpl.js +2 -2
  9. package/src/layer/cesium/vectorContext.js +115 -7
  10. package/src/layer/cesiumTilesetLayer.js +0 -14
  11. package/src/layer/czmlLayer.js +1 -1
  12. package/src/layer/dataSourceLayer.js +1 -53
  13. package/src/layer/featureLayer.js +0 -44
  14. package/src/layer/featureStoreLayer.js +0 -15
  15. package/src/layer/layer.js +0 -11
  16. package/src/layer/vectorHelpers.js +0 -85
  17. package/src/layer/vectorLayer.js +0 -9
  18. package/src/layer/vectorProperties.js +150 -8
  19. package/src/layer/vectorTileLayer.js +0 -9
  20. package/src/map/cesiumMap.js +26 -7
  21. package/src/style/arcStyle.js +316 -0
  22. package/src/style/arrowStyle.js +269 -0
  23. package/src/util/editor/createFeatureSession.js +3 -1
  24. package/src/util/editor/editFeaturesSession.js +315 -0
  25. package/src/util/editor/editGeometrySession.js +5 -1
  26. package/src/util/editor/editorHelpers.js +118 -14
  27. package/src/util/editor/editorSessionHelpers.js +12 -0
  28. package/src/util/editor/editorSymbols.js +6 -0
  29. package/src/util/editor/interactions/editFeaturesMouseOverInteraction.js +120 -0
  30. package/src/util/editor/interactions/editGeometryMouseOverInteraction.js +1 -3
  31. package/src/util/editor/interactions/ensureHandlerSelectionInteraction.js +48 -0
  32. package/src/util/editor/interactions/mapInteractionController.js +5 -2
  33. package/src/util/editor/interactions/selectMultiFeatureInteraction.js +146 -0
  34. package/src/util/editor/interactions/translateVertexInteraction.js +2 -2
  35. package/src/util/editor/transformation/create2DHandlers.js +294 -0
  36. package/src/util/editor/transformation/create3DHandlers.js +575 -0
  37. package/src/util/editor/transformation/extrudeInteraction.js +91 -0
  38. package/src/util/editor/transformation/rotateInteraction.js +188 -0
  39. package/src/util/editor/transformation/scaleInteraction.js +185 -0
  40. package/src/util/editor/transformation/transformationHandler.js +168 -0
  41. package/src/util/editor/transformation/transformationTypes.js +83 -0
  42. package/src/util/editor/transformation/translateInteraction.js +209 -0
  43. package/src/util/featureconverter/arcToCesium.js +87 -0
  44. package/src/util/featureconverter/convert.js +7 -1
  45. package/src/util/featureconverter/extent3D.js +64 -1
  46. package/src/util/featureconverter/lineStringToCesium.js +103 -2
  47. package/src/util/featureconverter/pointHelpers.js +341 -0
  48. package/src/util/featureconverter/pointToCesium.js +27 -76
  49. package/src/util/geometryHelpers.js +11 -8
  50. package/src/util/math.js +99 -2
  51. package/tests/unit/helpers/cesiumHelpers.js +14 -4
  52. package/tests/unit/helpers/helpers.js +13 -0
  53. 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
- export default function startEditGeometrySession(app, layer) {
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 { Cartesian2, Cartesian3, Math as CesiumMath, Plane } from '@vcmap/cesium';
4
- import Projection from '../projection.js';
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=} [epsilon=CesiumMath.EPSILON5]
62
+ * @param {number=} epsilon
35
63
  * @returns {boolean}
36
64
  */
37
- export function pointOnLine3D(start, end, point, epsilon = CesiumMath.EPSILON5) {
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=} [epsilon=CesiumMath.EPSILON5]
89
+ * @param {number=} epsilon
63
90
  * @returns {boolean}
64
91
  */
65
- export function pointOnLine2D(start, end, point, epsilon = CesiumMath.EPSILON5) {
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 createVerticalPlane(originCoordinates, scene) {
89
- const wgs84 = Projection.mercatorToWgs84(originCoordinates);
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
- const wgs84 = Projection.mercatorToWgs84(originCoordinates);
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;