@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,316 @@
1
+ import { parseInteger, parseNumber } from '@vcsuite/parsers';
2
+ import { check } from '@vcsuite/check';
3
+ import { Cartesian2, Cartesian3, CatmullRomSpline, Matrix3 } from '@vcmap/cesium';
4
+ import { LineString } from 'ol/geom.js';
5
+ import { unByKey } from 'ol/Observable.js';
6
+ import { v4 as uuidv4 } from 'uuid';
7
+ import { cartesian2DDistance, getMidPoint, modulo } from '../util/math.js';
8
+ import ArrowStyle from './arrowStyle.js';
9
+
10
+ /**
11
+ * @typedef {ArrowStyleOptions} ArcStyleOptions
12
+ * @property {number} [arcFactor=0.15] - factor to calculate the 'height' of an arc, based on the distance from start to end
13
+ * @property {number} [numberOfSegments=64] - number of segments to interpolate the arc by
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} ArcStruct
18
+ * @property {import("ol/geom").LineString} [geometry] - undefined if not an arc
19
+ * @property {Array<import("ol/coordinate").Coordinate>} [coordinates] - undefined if not an arc
20
+ * @property {function():void} destroy
21
+ */
22
+
23
+ /**
24
+ * Added to feature to hold there respective arc structure
25
+ * @type {symbol}
26
+ */
27
+ export const featureArcStruct = Symbol('FeatureArcStruct');
28
+
29
+ /**
30
+ * Added to features to indicate to which arc style there ArcStruct belongs. If the style changes or a new ArcStyle is applied
31
+ * to the feature, its value will change, recalculating the ArcStruct
32
+ * @type {symbol}
33
+ */
34
+ const featureArcStyleId = Symbol('ArcStyleId');
35
+
36
+ /**
37
+ * @param {import("ol/coordinate").Coordinate} p1
38
+ * @param {import("ol/coordinate").Coordinate} p2
39
+ * @param {number} factor
40
+ * @returns {number}
41
+ */
42
+ function determineArcHeight(p1, p2, factor) {
43
+ const distance = cartesian2DDistance(p1, p2);
44
+ return distance * factor;
45
+ }
46
+
47
+ /**
48
+ * Gets the radius of the circle covering p1, p2, p3. see https://math.stackexchange.com/a/1460096
49
+ * @param {import("ol/coordinate").Coordinate} p1
50
+ * @param {import("ol/coordinate").Coordinate} p2
51
+ * @param {import("ol/coordinate").Coordinate} p3
52
+ * @returns {{ center: import("ol/coordinate").Coordinate, radius: number }|null}
53
+ */
54
+ function determineCircle(p1, p2, p3) {
55
+ const m11 = Matrix3.determinant(new Matrix3(
56
+ p1[0], p1[1], 1,
57
+ p2[0], p2[1], 1,
58
+ p3[0], p3[1], 1,
59
+ ));
60
+ if (m11 === 0) {
61
+ return null;
62
+ }
63
+ const m12 = Matrix3.determinant(new Matrix3(
64
+ (p1[0] ** 2) + (p1[1] ** 2), p1[1], 1,
65
+ (p2[0] ** 2) + (p2[1] ** 2), p2[1], 1,
66
+ (p3[0] ** 2) + (p3[1] ** 2), p3[1], 1,
67
+ ));
68
+
69
+ const m13 = Matrix3.determinant(new Matrix3(
70
+ (p1[0] ** 2) + (p1[1] ** 2), p1[0], 1,
71
+ (p2[0] ** 2) + (p2[1] ** 2), p2[0], 1,
72
+ (p3[0] ** 2) + (p3[1] ** 2), p3[0], 1,
73
+ ));
74
+
75
+ const center = [0.5 * (m12 / m11), -0.5 * (m13 / m11)];
76
+
77
+ return {
78
+ center,
79
+ radius: cartesian2DDistance(center, p1),
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Determines the midpoint of a line with a distance. see https://gamedev.stackexchange.com/questions/70075/how-can-i-find-the-perpendicular-to-a-2d-vector
85
+ * @param {import("ol/coordinate").Coordinate} p1
86
+ * @param {import("ol/coordinate").Coordinate} p2
87
+ * @param {number} arcHeight
88
+ * @returns {import("ol/coordinate").Coordinate}
89
+ */
90
+ function getMidPointOnArc(p1, p2, arcHeight) {
91
+ const lineVector = new Cartesian2(p2[0] - p1[0], p2[1] - p1[1]);
92
+ let perp = Cartesian2.normalize(lineVector, new Cartesian2());
93
+ const { x, y } = perp;
94
+ perp = new Cartesian2(y, -x);
95
+ Cartesian2.multiplyByScalar(perp, arcHeight, perp);
96
+ const midPoint = getMidPoint(p1, p2);
97
+ Cartesian2.add(perp, new Cartesian2(midPoint[0], midPoint[1]), perp);
98
+ return [perp.x, perp.y];
99
+ }
100
+
101
+ /**
102
+ * @param {import("ol/coordinate").Coordinate} center
103
+ * @param {import("ol/coordinate").Coordinate} coordinate
104
+ * @param {number} angle
105
+ * @returns {number}
106
+ */
107
+ function determineQuadrantOffset(center, coordinate, angle) {
108
+ if (center[1] <= coordinate[1]) {
109
+ return angle;
110
+ }
111
+ return Math.PI + (Math.PI - angle);
112
+ }
113
+
114
+ /**
115
+ * @param {import("ol/coordinate").Coordinate} p1
116
+ * @param {import("ol/coordinate").Coordinate} p2
117
+ * @param {import("ol/coordinate").Coordinate} center
118
+ * @param {number} radius
119
+ * @param {number} numberOfSegments
120
+ * @returns {Array<import("ol/coordinate").Coordinate>}
121
+ */
122
+ function interpolateBetweenAngles(p1, p2, center, radius, numberOfSegments) {
123
+ const zeroVector = Cartesian2.UNIT_X;
124
+ const p1V = new Cartesian2(p1[0] - center[0], p1[1] - center[1]);
125
+ const p2V = new Cartesian2(p2[0] - center[0], p2[1] - center[1]);
126
+ let startAngle = Cartesian2.angleBetween(zeroVector, p1V);
127
+ startAngle = determineQuadrantOffset(center, p1, startAngle);
128
+ const distance = Cartesian2.angleBetween(p1V, p2V);
129
+ const coordinates = new Array(numberOfSegments);
130
+
131
+ for (let i = 0; i < numberOfSegments; ++i) {
132
+ const angle = startAngle + (modulo(i, numberOfSegments) * distance) / numberOfSegments;
133
+ coordinates[i] = [
134
+ center[0] + radius * Math.cos(angle),
135
+ center[1] + radius * Math.sin(angle),
136
+ 0,
137
+ ];
138
+ }
139
+ coordinates.push([p2[0], p2[1], 0]);
140
+ return coordinates;
141
+ }
142
+
143
+ /**
144
+ * @param {import("ol/coordinate").Coordinate} p1
145
+ * @param {import("ol/coordinate").Coordinate} p2
146
+ * @param {number} arcHeight
147
+ * @param {number} numberOfSegments
148
+ * @param {number} arcFactor
149
+ * @returns {Array<import("ol/coordinate").Coordinate>}
150
+ */
151
+ function getArcCoordinates(p1, p2, arcHeight, numberOfSegments, arcFactor) {
152
+ const midPoint = getMidPoint(p1, p2);
153
+ midPoint[2] += (arcHeight / 2);
154
+ const distance = cartesian2DDistance(p1, p2) / numberOfSegments;
155
+ const spline = new CatmullRomSpline({
156
+ times: [0, 0.5, 1],
157
+ points: [
158
+ new Cartesian3(p1[0], p1[1], p1[2] ?? 0),
159
+ new Cartesian3(midPoint[0], midPoint[1], midPoint[2] ?? 0),
160
+ new Cartesian3(p2[0], p2[1], p2[2] ?? 0),
161
+ ],
162
+ firstTangent: new Cartesian3(0, 0, arcFactor * distance),
163
+ lastTangent: new Cartesian3(0, 0, -arcFactor * distance),
164
+ });
165
+
166
+ const coordinates = new Array(numberOfSegments + 1);
167
+ let scratchCartesian = new Cartesian3();
168
+ for (let i = 0; i <= numberOfSegments; i++) {
169
+ scratchCartesian = spline.evaluate(i / numberOfSegments, scratchCartesian);
170
+ coordinates[i] = [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
171
+ }
172
+ return coordinates;
173
+ }
174
+
175
+ /**
176
+ * Set the ArcStruct on the feature
177
+ * @param {number} arcFactor
178
+ * @param {import("ol").Feature} feature
179
+ * @param {number} numberOfSegments
180
+ */
181
+ function createFeatureArc(arcFactor, feature, numberOfSegments) {
182
+ if (feature[featureArcStruct]) {
183
+ feature[featureArcStruct].destroy();
184
+ }
185
+ const geometry = feature.getGeometry();
186
+ const listeners = [];
187
+ const destroy = () => {
188
+ unByKey(listeners);
189
+ };
190
+ listeners.push(feature.on('change:geometry', () => { createFeatureArc(arcFactor, feature, numberOfSegments); }));
191
+
192
+ if (geometry instanceof LineString) {
193
+ listeners.push(geometry.on('change', () => { createFeatureArc(arcFactor, feature, numberOfSegments); }));
194
+ const p1 = geometry.getFirstCoordinate();
195
+ const p2 = geometry.getLastCoordinate();
196
+ const arcHeight = determineArcHeight(p1, p2, arcFactor);
197
+ const midPoint = getMidPointOnArc(p1, p2, arcHeight);
198
+ const { center, radius } = determineCircle(p1, p2, midPoint);
199
+
200
+ const coordinates = interpolateBetweenAngles(p1, p2, center, radius, numberOfSegments);
201
+ feature[featureArcStruct] = {
202
+ geometry: new LineString(coordinates),
203
+ coordinates: getArcCoordinates(p1, p2, arcHeight, numberOfSegments, arcFactor),
204
+ destroy,
205
+ };
206
+ } else {
207
+ feature[featureArcStruct] = {
208
+ geometry,
209
+ destroy,
210
+ };
211
+ }
212
+ }
213
+
214
+ /**
215
+ * A style which applies an arc to LineString geometries depending on their first and last coordinates.
216
+ * All other coordinates will be ignored. This still will
217
+ * not render non-LineString geometries (same as {@see ArrowStyle}).
218
+ * @class
219
+ * @extends {ArrowStyle}
220
+ */
221
+ class ArcStyle extends ArrowStyle {
222
+ /**
223
+ * @param {ArcStyleOptions} [options={}]
224
+ */
225
+ constructor(options = {}) {
226
+ super(options);
227
+ /**
228
+ * @type {number}
229
+ * @private
230
+ */
231
+ this._arcFactor = parseNumber(options.arcFactor, 0.15);
232
+ /**
233
+ * This styles revision ID. This will enforce an update of a features arc struct, if the feature get applied a new ArcStyle
234
+ * @type {string}
235
+ * @private
236
+ */
237
+ this._revisionId = uuidv4();
238
+ /**
239
+ * The number of line segments to interpolate by
240
+ * @type {number}
241
+ * @private
242
+ */
243
+ this._numberOfSegments = parseInteger(options.numberOfSegments, 64);
244
+
245
+ this.setGeometry(this._getFeatureArcGeometry.bind(this));
246
+ }
247
+
248
+ /**
249
+ * The number of segments to render the arc with.
250
+ * @type {number}
251
+ */
252
+ get numberOfSegments() {
253
+ return this._numberOfSegments;
254
+ }
255
+
256
+ /**
257
+ * @param {number} value
258
+ */
259
+ set numberOfSegments(value) {
260
+ check(value, Number);
261
+ if (value !== this._numberOfSegments && value > 0 && Number.isInteger(value)) {
262
+ this._numberOfSegments = value;
263
+ this._revisionId = uuidv4();
264
+ }
265
+ }
266
+
267
+ /**
268
+ * The factor with which to calculate the 'height' of an arc using the distance from start to end of the LineString.
269
+ * @type {number}
270
+ */
271
+ get arcFactor() { return this._arcFactor; }
272
+
273
+ /**
274
+ * @param {number} value
275
+ */
276
+ set arcFactor(value) {
277
+ check(value, Number);
278
+ if (value !== this._arcFactor) {
279
+ this._arcFactor = value;
280
+ this._revisionId = uuidv4();
281
+ }
282
+ }
283
+
284
+ /**
285
+ * @param {import("ol").Feature} feature
286
+ * @returns {import("ol/geom").LineString}
287
+ * @private
288
+ */
289
+ _getFeatureArcGeometry(feature) {
290
+ if (!feature[featureArcStruct] || feature[featureArcStyleId] !== this._revisionId) {
291
+ createFeatureArc(this._arcFactor, feature, this._numberOfSegments);
292
+ feature[featureArcStyleId] = this._revisionId;
293
+ }
294
+ return feature[featureArcStruct].geometry;
295
+ }
296
+
297
+ /**
298
+ * @returns {ArcStyleOptions}
299
+ * @protected
300
+ */
301
+ _getCloneOptions() {
302
+ const options = /** @type {ArcStyleOptions} */ (super._getCloneOptions());
303
+ options.arcFactor = this._arcFactor;
304
+ options.numberOfSegments = this._numberOfSegments;
305
+ return options;
306
+ }
307
+
308
+ /**
309
+ * @returns {ArcStyle}
310
+ */
311
+ clone() {
312
+ return new ArcStyle(this._getCloneOptions());
313
+ }
314
+ }
315
+
316
+ export default ArcStyle;
@@ -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
- export default function startCreateFeatureSession(app, layer, geometryType) {
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;