1
1
import { RegularShape , Style , Fill , Stroke } from 'ol/style' ;
2
2
import { Point , LineString , Polygon , MultiPoint } from 'ol/geom' ;
3
- import { fromExtent } from 'ol/geom/Polygon' ;
4
3
import Feature from 'ol/Feature' ;
5
4
import Vector from 'ol/layer/Vector' ;
6
5
import VectorSource from 'ol/source/Vector' ;
7
6
import { Pointer , Snap } from 'ol/interaction' ;
7
+ import { OverlayOp } from 'jsts/org/locationtech/jts/operation/overlay' ;
8
+ import OL3Parser from 'jsts/org/locationtech/jts/io/OL3Parser' ;
8
9
import Control from './control' ;
9
10
import cadSVG from '../../img/cad.svg' ;
10
11
import SnapEvent , { SnapEventType } from '../event/snap-event' ;
11
12
13
+ const parser = new OL3Parser ( ) ;
14
+ parser . inject ( Point , LineString , Polygon , MultiPoint ) ;
15
+
12
16
/**
13
17
* Control with snapping functionality for geometry alignment.
14
18
* @extends {ole.Control }
@@ -130,6 +134,33 @@ class CadControl extends Control {
130
134
this . standalone = false ;
131
135
}
132
136
137
+ /**
138
+ * Removes the closest node to a given coordinate from a given geometry.
139
+ * @private
140
+ * @param {ol.Geometry } geometry An openlayers geometry.
141
+ * @param {ol.Coordinate } coordinate Coordinate.
142
+ * @returns {ol.Geometry.MultiPoint } An openlayers MultiPoint geometry.
143
+ */
144
+ static getShiftedMultipoint ( geometry , coordinate ) {
145
+ // Include all but the closest vertex to the coordinate (e.g. at mouse position)
146
+ // to prevent snapping on mouse cursor node
147
+ const isPolygon = geometry instanceof Polygon ;
148
+ const shiftedMultipoint = new MultiPoint (
149
+ isPolygon ? geometry . getCoordinates ( ) [ 0 ] : geometry . getCoordinates ( ) ,
150
+ ) ;
151
+
152
+ const drawNodeCoordinate = shiftedMultipoint . getClosestPoint ( coordinate ) ;
153
+
154
+ // Exclude the node being modified
155
+ shiftedMultipoint . setCoordinates (
156
+ shiftedMultipoint . getCoordinates ( ) . filter ( ( coord ) => {
157
+ return coord . toString ( ) !== drawNodeCoordinate . toString ( ) ;
158
+ } ) ,
159
+ ) ;
160
+
161
+ return shiftedMultipoint ;
162
+ }
163
+
133
164
/**
134
165
* @inheritdoc
135
166
*/
@@ -194,12 +225,6 @@ class CadControl extends Control {
194
225
onMove ( evt ) {
195
226
const features = this . getClosestFeatures ( evt . coordinate , 5 ) ;
196
227
197
- // Don't snap on the edit feature
198
- const editFeature = this . editor . getEditFeature ( ) ;
199
- if ( editFeature && features . indexOf ( editFeature ) > - 1 ) {
200
- features . splice ( features . indexOf ( editFeature ) , 1 ) ;
201
- }
202
-
203
228
this . linesLayer . getSource ( ) . clear ( ) ;
204
229
this . snapLayer . getSource ( ) . clear ( ) ;
205
230
@@ -244,7 +269,7 @@ class CadControl extends Control {
244
269
} ) ;
245
270
246
271
const dists = Object . keys ( featureDict ) ;
247
- const features = [ ] ;
272
+ let features = [ ] ;
248
273
const count = Math . min ( dists . length , num ) ;
249
274
250
275
dists . sort ( ( a , b ) => a - b ) ;
@@ -253,9 +278,155 @@ class CadControl extends Control {
253
278
features . push ( featureDict [ dists [ i ] ] ) ;
254
279
}
255
280
281
+ const editFeature = this . editor . getEditFeature ( ) ;
282
+ // Initially exclude the edit feature from the snapping
283
+ if ( editFeature && features . indexOf ( editFeature ) > - 1 ) {
284
+ features . splice ( features . indexOf ( editFeature ) , 1 ) ;
285
+ }
286
+
287
+ // When using showSnapPoints return all features except edit/draw features
288
+ if ( this . properties . showSnapPoints ) {
289
+ return features ;
290
+ }
291
+
292
+ const drawFeature = this . editor . getDrawFeature ( ) ;
293
+ if ( drawFeature ) {
294
+ const geom = drawFeature . getGeometry ( ) ;
295
+ /* Include all nodes of the edit feature except the node at the mouse position */
296
+ // Clone drawFeature and apply adjusted snap geometry
297
+ const snapGeom = CadControl . getShiftedMultipoint ( geom , coordinate ) ;
298
+ const isPolygon = geom instanceof Polygon ;
299
+ const snapDrawFeature = drawFeature . clone ( ) ;
300
+ snapDrawFeature
301
+ . getGeometry ( )
302
+ . setCoordinates (
303
+ isPolygon ? [ snapGeom . getCoordinates ( ) ] : snapGeom . getCoordinates ( ) ,
304
+ ) ;
305
+ features = [ snapDrawFeature , ...features ] ;
306
+ }
307
+
308
+ if ( editFeature ) {
309
+ const geom = editFeature . getGeometry ( ) ;
310
+ /* Include all nodes of the edit feature except the node at the mouse position */
311
+ // Clone editFeature and apply adjusted snap geometry
312
+ const snapGeom = CadControl . getShiftedMultipoint ( geom , coordinate ) ;
313
+ const isPolygon = geom instanceof Polygon ;
314
+ const snapEditFeature = editFeature . clone ( ) ;
315
+ snapEditFeature
316
+ . getGeometry ( )
317
+ . setCoordinates (
318
+ isPolygon ? [ snapGeom . getCoordinates ( ) ] : snapGeom . getCoordinates ( ) ,
319
+ ) ;
320
+ features = [ snapEditFeature , ...features ] ;
321
+ }
322
+
256
323
return features ;
257
324
}
258
325
326
+ /**
327
+ * Returns an extent array, considers the map rotation.
328
+ * @private
329
+ * @param {ol.Geometry } geometry An OL geometry.
330
+ * @returns {Array.<number> } extent array.
331
+ */
332
+ getRotatedExtent ( geometry , coordinate ) {
333
+ const coordinates =
334
+ geometry instanceof Polygon
335
+ ? geometry . getCoordinates ( ) [ 0 ]
336
+ : geometry . getCoordinates ( ) ;
337
+
338
+ if ( ! coordinates . length ) {
339
+ // Polygons initially return a geometry with an empty coordinate array, so we need to catch it
340
+ return [ coordinate ] ;
341
+ }
342
+
343
+ // Get the extreme X and Y using pixel values so the rotation is considered
344
+ const xMin = coordinates . reduce ( ( finalMin , coord ) => {
345
+ const pixelCurrent = this . map . getPixelFromCoordinate ( coord ) ;
346
+ const pixelFinal = this . map . getPixelFromCoordinate (
347
+ finalMin || coordinates [ 0 ] ,
348
+ ) ;
349
+ return pixelCurrent [ 0 ] <= pixelFinal [ 0 ] ? coord : finalMin ;
350
+ } ) ;
351
+ const xMax = coordinates . reduce ( ( finalMax , coord ) => {
352
+ const pixelCurrent = this . map . getPixelFromCoordinate ( coord ) ;
353
+ const pixelFinal = this . map . getPixelFromCoordinate (
354
+ finalMax || coordinates [ 0 ] ,
355
+ ) ;
356
+ return pixelCurrent [ 0 ] >= pixelFinal [ 0 ] ? coord : finalMax ;
357
+ } ) ;
358
+ const yMin = coordinates . reduce ( ( finalMin , coord ) => {
359
+ const pixelCurrent = this . map . getPixelFromCoordinate ( coord ) ;
360
+ const pixelFinal = this . map . getPixelFromCoordinate (
361
+ finalMin || coordinates [ 0 ] ,
362
+ ) ;
363
+ return pixelCurrent [ 1 ] <= pixelFinal [ 1 ] ? coord : finalMin ;
364
+ } ) ;
365
+ const yMax = coordinates . reduce ( ( finalMax , coord ) => {
366
+ const pixelCurrent = this . map . getPixelFromCoordinate ( coord ) ;
367
+ const pixelFinal = this . map . getPixelFromCoordinate (
368
+ finalMax || coordinates [ 0 ] ,
369
+ ) ;
370
+ return pixelCurrent [ 1 ] >= pixelFinal [ 1 ] ? coord : finalMax ;
371
+ } ) ;
372
+
373
+ // Create four infinite lines through the extremes X and Y and rotate them
374
+ const minVertLine = new LineString ( [
375
+ [ xMin [ 0 ] , - 20037508.342789 ] ,
376
+ [ xMin [ 0 ] , 20037508.342789 ] ,
377
+ ] ) ;
378
+ minVertLine . rotate ( this . map . getView ( ) . getRotation ( ) , xMin ) ;
379
+ const maxVertLine = new LineString ( [
380
+ [ xMax [ 0 ] , - 20037508.342789 ] ,
381
+ [ xMax [ 0 ] , 20037508.342789 ] ,
382
+ ] ) ;
383
+ maxVertLine . rotate ( this . map . getView ( ) . getRotation ( ) , xMax ) ;
384
+ const minHoriLine = new LineString ( [
385
+ [ - 20037508.342789 , yMin [ 1 ] ] ,
386
+ [ 20037508.342789 , yMin [ 1 ] ] ,
387
+ ] ) ;
388
+ minHoriLine . rotate ( this . map . getView ( ) . getRotation ( ) , yMin ) ;
389
+ const maxHoriLine = new LineString ( [
390
+ [ - 20037508.342789 , yMax [ 1 ] ] ,
391
+ [ 20037508.342789 , yMax [ 1 ] ] ,
392
+ ] ) ;
393
+ maxHoriLine . rotate ( this . map . getView ( ) . getRotation ( ) , yMax ) ;
394
+
395
+ // Use intersection points of the four lines to get the extent
396
+ const intersectTopLeft = OverlayOp . intersection (
397
+ parser . read ( minVertLine ) ,
398
+ parser . read ( minHoriLine ) ,
399
+ ) ;
400
+ const intersectBottomLeft = OverlayOp . intersection (
401
+ parser . read ( minVertLine ) ,
402
+ parser . read ( maxHoriLine ) ,
403
+ ) ;
404
+ const intersectTopRight = OverlayOp . intersection (
405
+ parser . read ( maxVertLine ) ,
406
+ parser . read ( minHoriLine ) ,
407
+ ) ;
408
+ const intersectBottomRight = OverlayOp . intersection (
409
+ parser . read ( maxVertLine ) ,
410
+ parser . read ( maxHoriLine ) ,
411
+ ) ;
412
+
413
+ return [
414
+ [ intersectTopLeft . getCoordinate ( ) . x , intersectTopLeft . getCoordinate ( ) . y ] ,
415
+ [
416
+ intersectBottomLeft . getCoordinate ( ) . x ,
417
+ intersectBottomLeft . getCoordinate ( ) . y ,
418
+ ] ,
419
+ [
420
+ intersectTopRight . getCoordinate ( ) . x ,
421
+ intersectTopRight . getCoordinate ( ) . y ,
422
+ ] ,
423
+ [
424
+ intersectBottomRight . getCoordinate ( ) . x ,
425
+ intersectBottomRight . getCoordinate ( ) . y ,
426
+ ] ,
427
+ ] ;
428
+ }
429
+
259
430
/**
260
431
* Draws snap lines by building the extent for
261
432
* a pair of features.
@@ -264,35 +435,37 @@ class CadControl extends Control {
264
435
* @param {ol.Coordinate } coordinate Mouse pointer coordinate.
265
436
*/
266
437
drawSnapLines ( features , coordinate ) {
438
+ // First get all snap points: neighbouring feature vertices and extent corners
267
439
let auxCoords = [ ] ;
268
-
269
440
for ( let i = 0 ; i < features . length ; i += 1 ) {
270
441
const geom = features [ i ] . getGeometry ( ) ;
271
442
const featureCoord = geom . getCoordinates ( ) ;
272
-
273
- if ( geom instanceof Point ) {
274
- auxCoords . push ( featureCoord ) ;
275
- } else {
276
- // filling snapLayer with features vertex
277
- if ( geom instanceof LineString ) {
278
- for ( let j = 0 ; j < featureCoord . length ; j += 1 ) {
279
- auxCoords . push ( featureCoord [ j ] ) ;
280
- }
281
- } else if ( geom instanceof Polygon ) {
282
- for ( let j = 0 ; j < featureCoord [ 0 ] . length ; j += 1 ) {
283
- auxCoords . push ( featureCoord [ 0 ] [ j ] ) ;
443
+ // Polygons initially return a geometry with an empty coordinate array, so we need to catch it
444
+ if ( featureCoord . length ) {
445
+ if ( geom instanceof Point ) {
446
+ auxCoords . push ( featureCoord ) ;
447
+ } else {
448
+ // Add feature vertices
449
+ if ( geom instanceof LineString ) {
450
+ for ( let j = 0 ; j < featureCoord . length ; j += 1 ) {
451
+ auxCoords . push ( featureCoord [ j ] ) ;
452
+ }
453
+ } else if ( geom instanceof Polygon ) {
454
+ for ( let j = 0 ; j < featureCoord [ 0 ] . length ; j += 1 ) {
455
+ auxCoords . push ( featureCoord [ 0 ] [ j ] ) ;
456
+ }
284
457
}
285
- }
286
458
287
- // filling auxCoords
288
- const coords = fromExtent ( geom . getExtent ( ) ) . getCoordinates ( ) [ 0 ] ;
289
- auxCoords = auxCoords . concat ( coords ) ;
459
+ // Add extent vertices
460
+ const coords = this . getRotatedExtent ( geom , coordinate ) ;
461
+ auxCoords = auxCoords . concat ( coords ) ;
462
+ }
290
463
}
291
464
}
292
465
293
- const px = this . map . getPixelFromCoordinate ( coordinate ) ;
466
+ // Draw snaplines when cursor vertically or horizontally aligns with a snap feature
294
467
let lineCoords = null ;
295
-
468
+ const px = this . map . getPixelFromCoordinate ( coordinate ) ;
296
469
for ( let i = 0 ; i < auxCoords . length ; i += 1 ) {
297
470
const tol = this . snapTolerance ;
298
471
const auxPx = this . map . getPixelFromCoordinate ( auxCoords [ i ] ) ;
@@ -307,49 +480,62 @@ class CadControl extends Control {
307
480
let newY = px [ 1 ] ;
308
481
newY += px [ 1 ] < auxPx [ 1 ] ? - tol * 2 : tol * 2 ;
309
482
const newPt = this . map . getCoordinateFromPixel ( [ auxPx [ 0 ] , newY ] ) ;
310
- lineCoords = [ [ auxCoords [ i ] [ 0 ] , newPt [ 1 ] ] , auxCoords [ i ] ] ;
483
+ lineCoords = [ newPt , auxCoords [ i ] ] ;
311
484
} else if ( drawHLine ) {
312
485
let newX = px [ 0 ] ;
313
486
newX += px [ 0 ] < auxPx [ 0 ] ? - tol * 2 : tol * 2 ;
314
487
const newPt = this . map . getCoordinateFromPixel ( [ newX , auxPx [ 1 ] ] ) ;
315
- lineCoords = [ [ newPt [ 0 ] , auxCoords [ i ] [ 1 ] ] , auxCoords [ i ] ] ;
488
+ lineCoords = [ newPt , auxCoords [ i ] ] ;
316
489
}
317
490
318
491
if ( lineCoords ) {
319
- const g = new LineString ( lineCoords ) ;
320
- this . snapLayer . getSource ( ) . addFeature ( new Feature ( g ) ) ;
492
+ const geom = new LineString ( lineCoords ) ;
493
+ this . snapLayer . getSource ( ) . addFeature ( new Feature ( geom ) ) ;
321
494
}
322
495
}
323
496
324
- let vertArray = null ;
325
- let horiArray = null ;
497
+ // Snap to snap line intersection points
498
+ let vertLine = null ;
499
+ let horiLine = null ;
326
500
const snapFeatures = this . snapLayer . getSource ( ) . getFeatures ( ) ;
327
-
328
501
if ( snapFeatures . length ) {
329
502
snapFeatures . forEach ( ( feature ) => {
503
+ // The calculated pixels are used to get the vertical and horizontal lines
504
+ // because using the coordinate doesn't work with a rotated map
330
505
const featureCoord = feature . getGeometry ( ) . getCoordinates ( ) ;
331
- const x0 = featureCoord [ 0 ] [ 0 ] ;
332
- const x1 = featureCoord [ 1 ] [ 0 ] ;
333
- const y0 = featureCoord [ 0 ] [ 1 ] ;
334
- const y1 = featureCoord [ 1 ] [ 1 ] ;
335
-
336
- if ( x0 === x1 ) {
337
- vertArray = x0 ;
506
+ const pixelA = this . map . getPixelFromCoordinate ( featureCoord [ 0 ] ) ;
507
+ const pixelB = this . map . getPixelFromCoordinate ( featureCoord [ 1 ] ) ;
508
+ const x0 = pixelA [ 0 ] ;
509
+ const x1 = pixelB [ 0 ] ;
510
+ const y0 = pixelA [ 1 ] ;
511
+ const y1 = pixelB [ 1 ] ;
512
+
513
+ // The pixels are rounded to avoid micro differences in the decimals
514
+ if ( x0 . toFixed ( 4 ) === x1 . toFixed ( 4 ) ) {
515
+ vertLine = feature ;
338
516
}
339
- if ( y0 === y1 ) {
340
- horiArray = y0 ;
517
+ if ( y0 . toFixed ( 4 ) === y1 . toFixed ( 4 ) ) {
518
+ horiLine = feature ;
341
519
}
342
520
} ) ;
343
521
344
- const snapPt = [ ] ;
522
+ // We check if horizontal and vertical snap lines intersect and calculate the intersection coordinate
523
+ const snapLinesIntersectCoords =
524
+ vertLine &&
525
+ horiLine &&
526
+ OverlayOp . intersection (
527
+ parser . read ( vertLine . getGeometry ( ) ) ,
528
+ parser . read ( horiLine . getGeometry ( ) ) ,
529
+ ) ?. getCoordinates ( ) [ 0 ] ;
345
530
346
- if ( vertArray && horiArray ) {
347
- snapPt . push ( vertArray ) ;
348
- snapPt . push ( horiArray ) ;
531
+ if ( snapLinesIntersectCoords ) {
349
532
this . linesLayer . getSource ( ) . addFeatures ( snapFeatures ) ;
350
533
351
534
this . snapLayer . getSource ( ) . clear ( ) ;
352
- const snapGeom = new Point ( snapPt ) ;
535
+ const snapGeom = new Point ( [
536
+ snapLinesIntersectCoords . x ,
537
+ snapLinesIntersectCoords . y ,
538
+ ] ) ;
353
539
this . snapLayer . getSource ( ) . addFeature ( new Feature ( snapGeom ) ) ;
354
540
}
355
541
}
0 commit comments