@vcmap/core 5.0.0-rc.22 → 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 +631 -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/featureProvider/wmsFeatureProvider.js +1 -1
- 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/layer/wmsHelpers.js +2 -0
- package/src/map/baseOLMap.js +12 -6
- package/src/map/cesiumMap.js +48 -38
- package/src/map/openlayersMap.js +6 -5
- package/src/map/vcsMap.js +22 -0
- 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/mapCollection.js +25 -0
- package/src/util/math.js +99 -2
- package/tests/unit/helpers/cesiumHelpers.js +17 -5
- package/tests/unit/helpers/helpers.js +13 -0
- package/src/featureProvider/featureProviderHelpers.js +0 -50
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { Cartesian3 } from '@vcmap/cesium';
|
|
2
|
+
import {
|
|
3
|
+
Circle,
|
|
4
|
+
Fill,
|
|
5
|
+
Icon,
|
|
6
|
+
Stroke,
|
|
7
|
+
Style,
|
|
8
|
+
Image as OlImage,
|
|
9
|
+
} from 'ol/style.js';
|
|
10
|
+
import { parseEnumValue, parseNumber } from '@vcsuite/parsers';
|
|
11
|
+
import { getCartesianBearing } from '../util/math.js';
|
|
12
|
+
import { PrimitiveOptionsType } from '../layer/vectorProperties.js';
|
|
13
|
+
import { getStringColor, parseColor } from './styleHelpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} ArrowStyleOptions
|
|
17
|
+
* @property {import("ol/color").Color|import("ol/colorlike").ColorLike} [color='#000000'] - color used to color in the line & the icon
|
|
18
|
+
* @property {number} [width=1] - passed to ol.Style.Stroke
|
|
19
|
+
* @property {number} [zIndex] - passed to ol.Style
|
|
20
|
+
* @property {import("ol/style/Icon").Options|import("ol/style").Image} [arrowIcon] - icon options to use. if none are provided, if is attempted to derive the arrow icon from the primitive options. if providing your own icon, the color will not be options.color by default
|
|
21
|
+
* @property {VectorPropertiesPrimitiveOptions} [primitiveOptions] - the default primitive options are a cylinder with a bottom radius of 1/3 its length and 0 top radius
|
|
22
|
+
* @property {ArrowEnd} [end=ArrowEnd.END] - end to place the arrow head at
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @enum {string}
|
|
27
|
+
*/
|
|
28
|
+
export const ArrowEnd = {
|
|
29
|
+
NONE: 'none',
|
|
30
|
+
BOTH: 'both',
|
|
31
|
+
START: 'start',
|
|
32
|
+
END: 'end',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {VectorPropertiesPrimitiveOptions} primitiveOptions
|
|
37
|
+
* @param {number} [twoDFactor=1.5]
|
|
38
|
+
* @returns {string}
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
export function getDefaultArrowIconSrc(primitiveOptions, twoDFactor = 1.5) {
|
|
42
|
+
let height = 13;
|
|
43
|
+
let width = 13;
|
|
44
|
+
let points = [[0, 13], [13, 13], [6, 0]];
|
|
45
|
+
|
|
46
|
+
if (primitiveOptions.type === PrimitiveOptionsType.SPHERE) {
|
|
47
|
+
const radius = Math.floor((primitiveOptions.geometryOptions?.radius ?? 1) * twoDFactor);
|
|
48
|
+
return `<svg height="${radius * 2}" width="${radius * 2}" xmlns="http://www.w3.org/2000/svg">
|
|
49
|
+
<circle cx="${radius}" cy="${radius}" r="${radius}" style="fill:white;" />
|
|
50
|
+
</svg>`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (
|
|
54
|
+
primitiveOptions.type === PrimitiveOptionsType.BOX &&
|
|
55
|
+
primitiveOptions.geometryOptions.minimum &&
|
|
56
|
+
primitiveOptions.geometryOptions.maximum
|
|
57
|
+
) {
|
|
58
|
+
const min = Array.isArray(primitiveOptions.geometryOptions.minimum) ?
|
|
59
|
+
primitiveOptions.geometryOptions.minimum :
|
|
60
|
+
Cartesian3.pack(primitiveOptions.geometryOptions.minimum, []);
|
|
61
|
+
|
|
62
|
+
const max = Array.isArray(primitiveOptions.geometryOptions.maximum) ?
|
|
63
|
+
primitiveOptions.geometryOptions.maximum :
|
|
64
|
+
Cartesian3.pack(primitiveOptions.geometryOptions.maximum, []);
|
|
65
|
+
|
|
66
|
+
width = Math.floor((max[0] - min[0]) * twoDFactor);
|
|
67
|
+
height = Math.floor((max[1] - min[1]) * twoDFactor);
|
|
68
|
+
|
|
69
|
+
points = [
|
|
70
|
+
[0, 0],
|
|
71
|
+
[width, 0],
|
|
72
|
+
[width, height],
|
|
73
|
+
[0, height],
|
|
74
|
+
];
|
|
75
|
+
} else if (
|
|
76
|
+
primitiveOptions.type === PrimitiveOptionsType.CYLINDER &&
|
|
77
|
+
primitiveOptions.geometryOptions.length
|
|
78
|
+
) {
|
|
79
|
+
const topRadius = Math.floor(primitiveOptions.geometryOptions.topRadius * twoDFactor);
|
|
80
|
+
const bottomRadius = Math.floor(primitiveOptions.geometryOptions.bottomRadius * twoDFactor);
|
|
81
|
+
const maxRadius = Math.max(topRadius, bottomRadius);
|
|
82
|
+
|
|
83
|
+
height = Math.floor(primitiveOptions.geometryOptions.length * twoDFactor);
|
|
84
|
+
width = maxRadius * 2;
|
|
85
|
+
|
|
86
|
+
points = [
|
|
87
|
+
[(width / 2) - bottomRadius, height],
|
|
88
|
+
[(width / 2) + bottomRadius, height],
|
|
89
|
+
[(width / 2) + topRadius, 0],
|
|
90
|
+
[(width / 2) - topRadius, 0],
|
|
91
|
+
];
|
|
92
|
+
if (bottomRadius === 0) {
|
|
93
|
+
points.splice(1, 1);
|
|
94
|
+
} else if (topRadius === 0) {
|
|
95
|
+
points.splice(2, 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return `<svg height="${height}" width="${width}" xmlns="http://www.w3.org/2000/svg"><polygon points="${points.map(p => p.join(',')).join(' ')}" style="fill:white;" /></svg>`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @returns {VectorPropertiesPrimitiveOptions}
|
|
103
|
+
*/
|
|
104
|
+
function getDefaultArrowPrimitive() {
|
|
105
|
+
return {
|
|
106
|
+
type: PrimitiveOptionsType.CYLINDER,
|
|
107
|
+
geometryOptions: {
|
|
108
|
+
length: 9,
|
|
109
|
+
bottomRadius: 3,
|
|
110
|
+
topRadius: 0,
|
|
111
|
+
},
|
|
112
|
+
offset: [0, 0, -4.3],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A style which renders arrow heads at the ends of a line string. This style cannot be applied to non-LineString geometries.
|
|
118
|
+
* When setting this on a layer with heterogeneous geometry types, use a style function.
|
|
119
|
+
* @class
|
|
120
|
+
* @extends {Style}
|
|
121
|
+
*/
|
|
122
|
+
class ArrowStyle extends Style {
|
|
123
|
+
/**
|
|
124
|
+
* @param {ArrowStyleOptions} [options={}]
|
|
125
|
+
*/
|
|
126
|
+
constructor(options = {}) {
|
|
127
|
+
const color = options.color ?? '#000000';
|
|
128
|
+
const styleOptions = {
|
|
129
|
+
stroke: new Stroke({
|
|
130
|
+
color,
|
|
131
|
+
width: parseNumber(options.width, 1),
|
|
132
|
+
}),
|
|
133
|
+
zIndex: options.zIndex,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const primitiveOptions = options.primitiveOptions ?? getDefaultArrowPrimitive();
|
|
137
|
+
const iconOptions = options.arrowIcon ?? {
|
|
138
|
+
src: `data:image/svg+xml,${encodeURIComponent(getDefaultArrowIconSrc(primitiveOptions))}`,
|
|
139
|
+
color: parseColor(color),
|
|
140
|
+
};
|
|
141
|
+
styleOptions.image = iconOptions instanceof OlImage ? iconOptions : new Icon(iconOptions);
|
|
142
|
+
super(styleOptions);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @type {VectorPropertiesPrimitiveOptions}
|
|
146
|
+
*/
|
|
147
|
+
this.primitiveOptions = primitiveOptions;
|
|
148
|
+
/**
|
|
149
|
+
* @type {ArrowEnd}
|
|
150
|
+
*/
|
|
151
|
+
this.end = parseEnumValue(options.end, ArrowEnd, ArrowEnd.END);
|
|
152
|
+
this.setRenderer(this._render.bind(this));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Same as getStroke().getWidth() / getStroke().setWidth()
|
|
157
|
+
* @type {number}
|
|
158
|
+
*/
|
|
159
|
+
get width() {
|
|
160
|
+
return this.getStroke().getWidth();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {number} width
|
|
165
|
+
*/
|
|
166
|
+
set width(width) {
|
|
167
|
+
this.getStroke().setWidth(width);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The color of the stroke and icon styles. Setting the color will not re-apply the icons color.
|
|
172
|
+
* @type {import("ol/color").Color|import("ol/colorlike").ColorLike}
|
|
173
|
+
*/
|
|
174
|
+
get color() {
|
|
175
|
+
return this.getStroke().getColor();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {import("ol/color").Color|import("ol/colorlike").ColorLike} color
|
|
180
|
+
*/
|
|
181
|
+
set color(color) {
|
|
182
|
+
this.getStroke().setColor(color);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
187
|
+
* @param {import("ol/coordinate").Coordinate} imagePosition
|
|
188
|
+
* @param {number} rotation
|
|
189
|
+
* @param {number} pixelRatio
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
_drawArrow(ctx, imagePosition, rotation, pixelRatio) {
|
|
193
|
+
ctx.save();
|
|
194
|
+
let scale = this.getImage().getScale();
|
|
195
|
+
scale = Array.isArray(scale) ? scale : [scale, scale];
|
|
196
|
+
ctx.setTransform(scale[0], 0, 0, scale[1], imagePosition[0], imagePosition[1]);
|
|
197
|
+
ctx.rotate(Math.PI - rotation);
|
|
198
|
+
const image = this.getImage().getImage(pixelRatio);
|
|
199
|
+
ctx.translate(0, Math.floor(image.height / 2));
|
|
200
|
+
ctx.drawImage(image, -image.width / 2, -image.height / 2);
|
|
201
|
+
ctx.restore();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @param {Array<import("ol/coordinate").Coordinate>} geom
|
|
206
|
+
* @param {import("ol/render").State} e
|
|
207
|
+
* @private
|
|
208
|
+
*/
|
|
209
|
+
_render(geom, e) {
|
|
210
|
+
if (e.geometry.getType() === 'LineString' && geom.length > 1) {
|
|
211
|
+
const ctx = e.context;
|
|
212
|
+
if (this.end !== ArrowEnd.NONE) {
|
|
213
|
+
if (this.end === ArrowEnd.START || this.end === ArrowEnd.BOTH) {
|
|
214
|
+
this._drawArrow(ctx, geom[0], getCartesianBearing(geom[1], geom[0]), e.pixelRatio);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (this.end === ArrowEnd.END || this.end === ArrowEnd.BOTH) {
|
|
218
|
+
this._drawArrow(ctx, geom.at(-1), getCartesianBearing(geom.at(-2), geom.at(-1)), e.pixelRatio);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
ctx.save();
|
|
222
|
+
ctx.lineJoin = 'round';
|
|
223
|
+
ctx.lineWidth = this.width;
|
|
224
|
+
ctx.strokeStyle = getStringColor(this.color);
|
|
225
|
+
ctx.beginPath();
|
|
226
|
+
ctx.moveTo(geom[0][0], geom[0][1]);
|
|
227
|
+
for (let i = 0; i < geom.length; i++) {
|
|
228
|
+
ctx.lineTo(geom[i][0], geom[i][1]);
|
|
229
|
+
}
|
|
230
|
+
ctx.stroke();
|
|
231
|
+
ctx.restore();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Returns the style used to render primitives (a style with a circle image and the color of this style).
|
|
237
|
+
* @returns {import("ol/style").Style}
|
|
238
|
+
*/
|
|
239
|
+
getOlcsStyle() {
|
|
240
|
+
return new Style({
|
|
241
|
+
image: new Circle({
|
|
242
|
+
radius: 2,
|
|
243
|
+
fill: new Fill({ color: this.color }),
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @returns {ArrowStyleOptions}
|
|
250
|
+
* @protected
|
|
251
|
+
*/
|
|
252
|
+
_getCloneOptions() {
|
|
253
|
+
return {
|
|
254
|
+
color: this.color,
|
|
255
|
+
width: this.width,
|
|
256
|
+
arrowIcon: this.getImage().clone(),
|
|
257
|
+
zIndex: this.getZIndex(),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @returns {ArrowStyle}
|
|
263
|
+
*/
|
|
264
|
+
clone() {
|
|
265
|
+
return new ArrowStyle(this._getCloneOptions());
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default ArrowStyle;
|
|
@@ -30,7 +30,7 @@ import ObliqueMap from '../../map/obliqueMap.js';
|
|
|
30
30
|
* @param {GeometryType} geometryType
|
|
31
31
|
* @returns {CreateFeatureSession}
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
function startCreateFeatureSession(app, layer, geometryType) {
|
|
34
34
|
check(app, VcsApp);
|
|
35
35
|
check(layer, VectorLayer);
|
|
36
36
|
check(geometryType, Object.values(GeometryType));
|
|
@@ -180,3 +180,5 @@ export default function startCreateFeatureSession(app, layer, geometryType) {
|
|
|
180
180
|
stop,
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
|
+
|
|
184
|
+
export default startCreateFeatureSession;
|
|
@@ -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;
|