• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geographika / cpsi-mapview / 6432360993

06 Oct 2023 01:35PM UTC coverage: 24.104% (+0.003%) from 24.101%
6432360993

push

github

geographika
Fix edge case and check for null not 0

446 of 2300 branches covered (0.0%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 1 file covered. (100.0%)

1310 of 4985 relevant lines covered (26.28%)

1.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.31
/app/controller/button/DrawingButtonController.js
1
/**
2
 * This class is the controller for the DrawingButton.
3
 */
4
Ext.define('CpsiMapview.controller.button.DrawingButtonController', {
1✔
5
    extend: 'Ext.app.ViewController',
6
    requires: [
7
        'BasiGX.util.Map',
8
        'BasiGX.util.Layer',
9
        'Ext.menu.Menu',
10
        'GeoExt.component.FeatureRenderer',
11
        'GeoExt.data.store.Features',
12
        'CpsiMapview.controller.button.TracingMixin'
13
    ],
14

15
    alias: 'controller.cmv_drawing_button',
16

17
    mixins: [
18
        'CpsiMapview.controller.button.TracingMixin'
19
    ],
20

21
    /**
22
     * The OpenLayers map. If not given, will be auto-detected
23
     */
24
    map: null,
25

26
    /**
27
     * The BasiGX mapComponent. If not given, will be auto-detected
28
     */
29
    mapComponent: null,
30

31
    /**
32
     * Temporary vector layer used while drawing points, lines or polygons
33
     */
34
    drawLayer: null,
35

36
    /**
37
     * OpenLayers draw interaction for drawing of lines and polygons
38
     */
39
    drawInteraction: null,
40

41
    /**
42
     * OpenLayers modify interaction
43
     * Used in polygon and point draw mode
44
     */
45
    modifyInteraction: null,
46

47
    /**
48
     * OpenLayers snap interaction for allowing easier tracing
49
     */
50
    snapInteraction: null,
51

52
    /**
53
     * If user has started to edit a line, this means the first point of a line is already set
54
     */
55
    currentlyDrawing: false,
56

57
    /**
58
     * Stores event listener keys to be un-listened to on destroy or button toggle
59
     */
60
    listenerKeys: [],
61

62
    /**
63
     * Determines if event handling is blocked.
64
     */
65
    //blockedEventHandling: false,
66

67
    constructor: function () {
68
        var me = this;
16✔
69
        me.handleDrawStart = me.handleDrawStart.bind(me);
16✔
70
        me.handleDrawEnd = me.handleDrawEnd.bind(me);
16✔
71
        me.handleModifyEnd = me.handleModifyEnd.bind(me);
16✔
72
        me.handleKeyPress = me.handleKeyPress.bind(me);
16✔
73
        me.callParent(arguments);
16✔
74
    },
75

76
    /**
77
     * Set the layer to store features drawn by the editing
78
     * tools
79
     * @param {any} layer
80
     */
81
    setDrawLayer: function (layer) {
82
        var me = this;
×
83

84
        if (!me.map) {
×
85
            return;
×
86
        }
87

88
        if (me.drawLayer) {
×
89
            me.map.removeLayer(me.drawLayer);
×
90
        }
91

92
        me.drawLayer = layer;
×
93
        me.setDrawInteraction(layer);
×
94
        me.setModifyInteraction(layer);
×
95
        me.setSnapInteraction(layer);
×
96
    },
97

98
    /**
99
     * Set the map drawing interaction
100
     * which will allow features to be added to the drawLayer
101
     * @param {any} layer
102
     */
103
    setDrawInteraction: function (layer) {
104

105
        var me = this;
×
106
        var view = me.getView();
×
107

108
        if (me.drawInteraction) {
×
109
            me.map.removeInteraction(me.drawInteraction);
×
110
        }
111

112
        var drawCondition = function (evt) {
×
113
            // the draw interaction does not work with the singleClick condition.
114
            return ol.events.condition.primaryAction(evt) && ol.events.condition.noModifierKeys(evt);
×
115
        };
116

117
        var source = layer.getSource();
×
118
        var collection = source.getFeaturesCollection();
×
119
        var drawInteractionConfig = {
×
120
            type: 'LineString',
121
            features: collection,
122
            condition: drawCondition,
123
            style: me.getDrawStyleFunction(),
124
            snapTolerance: view.getDrawInteractionSnapTolerance()
125
        };
126

127
        me.drawInteraction = new ol.interaction.Draw(drawInteractionConfig);
×
128
        me.drawInteraction.on('drawstart', me.handleDrawStart);
×
129
        me.drawInteraction.on('drawend', me.handleDrawEnd);
×
130

131
        me.map.addInteraction(me.drawInteraction);
×
132
    },
133

134
    /**
135
     * Prepare the styles retrieved from config.
136
     */
137
    prepareDrawingStyles: function () {
138
        var me = this;
×
139
        var view = me.getView();
×
140

141
        // ensure styles are applied at right conditions
142
        view.getDrawBeforeEditingPoint().setGeometry(function (feature) {
×
143
            var geom = feature.getGeometry();
×
144
            if (!me.currentlyDrawing) {
×
145
                return geom;
×
146
            }
147
        });
148
        view.getDrawStyleStartPoint().setGeometry(function (feature) {
×
149
            var geom = feature.getGeometry();
×
150
            var coords = geom.getCoordinates();
×
151
            var firstCoord = coords[0];
×
152
            return new ol.geom.Point(firstCoord);
×
153
        });
154
        view.getDrawStyleEndPoint().setGeometry(function (feature) {
×
155
            var coords = feature.getGeometry().getCoordinates();
×
156
            if (coords.length > 1) {
×
157
                var lastCoord = coords[coords.length - 1];
×
158
                return new ol.geom.Point(lastCoord);
×
159
            }
160
        });
161

162
        // ensure snap styles are always on top
163
        view.getSnappedNodeStyle().setZIndex(Infinity);
×
164
        view.getSnappedEdgeStyle().setZIndex(Infinity);
×
165
    },
166

167
    /**
168
     * Creates the style function for the drawn feature.
169
     *
170
     * @returns {Function} The style function for the drawn feature.
171
     */
172
    getDrawStyleFunction: function () {
173
        var me = this;
×
174
        var view = me.getView();
×
175

176
        return function (feature) {
×
177
            var coordinate = feature.getGeometry().getCoordinates();
×
178
            var pixel = me.map.getPixelFromCoordinate(coordinate);
×
179

180
            // remember if we have hit a referenced layer
181

182
            var node, edge, polygon, self;
183

184
            me.map.forEachFeatureAtPixel(pixel, function (foundFeature, layer) {
×
185
                if (layer) {
×
186
                    var key = layer.get('layerKey');
×
187
                    if (key === view.getNodeLayerKey()) {
×
188
                        node = foundFeature;
×
189
                    } else if (key === view.getEdgeLayerKey()) {
×
190
                        edge = foundFeature;
×
191
                    } else if (key === view.getPolygonLayerKey()) {
×
192
                        polygon = foundFeature;
×
193
                    } else if (me.drawLayer === layer) {
×
194
                        // snapping to self drawn feature
195
                        self = foundFeature;
×
196
                    }
197
                }
198
            });
199

200
            if (node) {
×
201
                return view.getSnappedNodeStyle();
×
202
            } else if (edge) {
×
203
                if (view.getShowVerticesOfSnappedEdge()) {
×
204
                    // Prepare style for vertices of snapped edge
205
                    // we create a MultiPoint from the edge's vertices
206
                    // and set it as geometry in our style function
207
                    var geom = edge.getGeometry();
×
208
                    var coords = [];
×
209
                    if (geom.getType() === 'MultiLineString') {
×
210
                        // use all vertices of containing LineStrings
211
                        var lineStrings = geom.getLineStrings();
×
212
                        Ext.each(lineStrings, function (lineString) {
×
213
                            var lineStringCoords = lineString.getCoordinates();
×
214
                            coords = coords.concat(lineStringCoords);
×
215
                        });
216
                    } else {
217
                        coords = geom.getCoordinates();
×
218
                    }
219
                    var verticesMultiPoint = new ol.geom.MultiPoint(coords);
×
220
                    var snappedEdgeVertexStyle = view.getSnappedEdgeVertexStyle().clone();
×
221
                    snappedEdgeVertexStyle.setGeometry(verticesMultiPoint);
×
222

223
                    // combine style for snapped point and vertices of snapped edge
224
                    return [
×
225
                        snappedEdgeVertexStyle,
226
                        view.getSnappedEdgeStyle()
227
                    ];
228
                } else {
229
                    return view.getSnappedEdgeStyle();
×
230
                }
231
            } else if (polygon) {
×
232
                return view.getSnappedPolygonStyle();
×
233
            } else if (self) {
×
234
                return view.getModifySnapPointStyle();
×
235
            } else {
236
                return me.defaultDrawStyle;
×
237
            }
238
        };
239
    },
240

241
    /**
242
     * Set the modify interaction, used to modify
243
     * existing features created in the drawLayer
244
     * We cannot however simply stop and start redrawing the line, adding directly to the vertices
245
     * https://stackoverflow.com/questions/45836955/openlayers-3-continue-drawing-the-initial-line-after-drawend-triggered-with-do/45859390#45859390
246
     * @param {any} layer
247
     */
248
    setModifyInteraction: function (layer) {
249

250
        var me = this;
×
251

252
        if (me.modifyInteraction) {
×
253
            me.map.removeInteraction(me.modifyInteraction);
×
254
        }
255

256
        var condition = function (evt) {
×
257
            // only allow modifying when the CTRL key is pressed, otherwise we cannot add new line
258
            // segments once the first feature is drawn
259
            return ol.events.condition.primaryAction(evt) && ol.events.condition.platformModifierKeyOnly(evt);
×
260
        };
261

262
        // create the modify interaction
263
        var modifyInteractionConfig = {
×
264
            features: layer.getSource().getFeaturesCollection(),
265
            condition: condition,
266
            // intentionally pass empty style, because modify style is
267
            // done in the draw interaction
268
            style: new ol.style.Style({})
269
        };
270
        me.modifyInteraction = new ol.interaction.Modify(modifyInteractionConfig);
×
271
        me.map.addInteraction(me.modifyInteraction);
×
272
        me.modifyInteraction.on('modifyend', me.handleModifyEnd);
×
273

274
    },
275

276
    /**
277
     * Set the snap interaction used to snap to features
278
     * @param {any} layer
279
     */
280
    setSnapInteraction: function (drawLayer) {
281

282
        var me = this;
11✔
283

284
        if (me.snapInteraction) {
11✔
285
            me.map.removeInteraction(me.snapInteraction);
1✔
286
        }
287

288
        // unbind any previous layer event listeners
289
        me.unBindLayerListeners();
11✔
290

291
        var snapCollection = new ol.Collection([], {
11✔
292
            unique: true
293
        });
294

295
        var fc = drawLayer.getSource().getFeaturesCollection();
11✔
296

297
        fc.on('add', function (evt) {
11✔
298
            snapCollection.push(evt.element);
×
299
        });
300

301
        fc.on('remove', function (evt) {
11✔
302
            snapCollection.remove(evt.element);
×
303
        });
304

305
        // Adds Features to a Collection, catches and ignores exceptions thrown
306
        // by the Collection if trying to add a duplicate feature, but still maintains
307
        // a unique collection of features. Used as an alternative to .extend but ensures
308
        // any potential errors related to unique features are handled / suppressed.
309
        var addUniqueFeaturesToCollection = function (collection, features) {
11✔
310
            Ext.Array.each(features, function (f) {
16✔
311
                // eslint-disable-next-line
312
                try { collection.push(f); } catch (e) { }
31✔
313
            });
314
        };
315

316
        // Checks if a feature exists in layers other than the current layer
317
        var isFeatureInOtherLayers = function (allLayers, currentLayer, feature) {
11✔
318
            var found = false;
4✔
319
            Ext.Array.each(allLayers, function(layer) {
4✔
320
                if(layer !== currentLayer) {
8✔
321
                    if(layer.getSource().hasFeature(feature)) {
4✔
322
                        found = true;
2✔
323
                    }
324
                }
325
            });
326
            return found;
4✔
327
        };
328

329
        // get the layers to snap to
330
        var view = me.getView();
11✔
331
        var layerKeys = view.getSnappingLayerKeys();
11✔
332
        var allowSnapToHiddenFeatures = view.getAllowSnapToHiddenFeatures();
11✔
333
        var layers = Ext.Array.map(layerKeys, function (key) {
11✔
334
            return BasiGX.util.Layer.getLayersBy('layerKey', key)[0];
16✔
335
        });
336

337
        Ext.Array.each(layers, function (layer) {
11✔
338
            var feats = layer.getSource().getFeatures(); // these are standard WFS layers so we use getSource without getFeaturesCollection here
16✔
339
            // add inital features to the snap collection, if the layer is visible
340
            // or if allowSnapToHiddenFeatures is enabled
341
            if (layer.getVisible() || allowSnapToHiddenFeatures) {
16✔
342
                addUniqueFeaturesToCollection(snapCollection, feats);
14✔
343
            }
344

345
            // Update the snapCollection on addfeature or removefeature
346
            var addFeatureKey = layer.getSource().on('addfeature', function (evt) {
16✔
347
                if (layer.getVisible() || allowSnapToHiddenFeatures) {
1!
348
                    addUniqueFeaturesToCollection(snapCollection, [evt.feature]);
1✔
349
                }
350
            });
351

352
            var removefeatureKey = layer.getSource().on('removefeature', function (evt) {
16✔
353
                if (!isFeatureInOtherLayers(layers, layer, evt.feature)) {
2✔
354
                    snapCollection.remove(evt.feature);
1✔
355
                }
356
            });
357

358
            // Update the snapCollection on layer visibility change
359
            // only handle layer visible change event if snapping to hidden features is disabled
360
            if (!allowSnapToHiddenFeatures) {
16✔
361
                var changeVisibleKey = layer.on('change:visible', function () {
14✔
362
                    var features = layer.getSource().getFeatures();
2✔
363
                    if (layer.getVisible()) {
2✔
364
                        addUniqueFeaturesToCollection(snapCollection, features);
1✔
365
                    } else {
366
                        Ext.Array.each(features, function (f) {
1✔
367
                            if (!isFeatureInOtherLayers(layers, layer, f)) {
2✔
368
                                snapCollection.remove(f);
1✔
369
                            }
370
                        });
371
                    }
372
                });
373
            }
374

375
            me.listenerKeys.push(addFeatureKey, removefeatureKey, changeVisibleKey);
16✔
376
        });
377

378
        // vector tile sources cannot be used for snapping as they
379
        // do not provide a getFeatures function
380
        // see https://openlayers.org/en/latest/apidoc/module-ol_source_VectorTile-VectorTile.html
381

382
        me.snapInteraction = new ol.interaction.Snap({
11✔
383
            features: snapCollection
384
        });
385
        me.map.addInteraction(me.snapInteraction);
11✔
386

387
    },
388

389
    getSnappedEdge: function (coord, searchLayer) {
390

391
        var me = this;
7✔
392
        var extent = me.getBufferedCoordExtent(coord);
7✔
393

394
        var features = [];
7✔
395
        // find all intersecting edges
396
        // https://openlayers.org/en/latest/apidoc/module-ol_source_Vector-VectorSource.html
397
        searchLayer.getSource().forEachFeatureIntersectingExtent(extent, function (feat) {
7✔
398
            features.push(feat);
8✔
399
        });
400

401
        if (features.length > 0) {
7✔
402
            return features[0];
4✔
403
        } else {
404
            return null;
3✔
405
        }
406
    },
407

408
    getBufferedCoordExtent: function (coord) {
409

410
        var me = this;
14✔
411
        var extent = ol.extent.boundingExtent([coord]); // still a single point
14✔
412
        var buffer = me.map.getView().getResolution() * 3; // use a 3 pixel tolerance for snapping
14✔
413
        return ol.extent.buffer(extent, buffer); // buffer the point as it may have snapped to a different feature than the nodes/edges
14✔
414

415
    },
416

417
    getSnappedFeatureId: function (coord, searchLayer) {
418

419
        var me = this;
1✔
420
        var feat = me.getSnappedEdge(coord, searchLayer);
1✔
421

422
        if (feat) {
1!
423
            // this requires all GeoJSON features used for the layer to have an id property
424
            //<debug>
425
            Ext.Assert.truthy(feat.getId());
×
426
            //</debug>
427
            return feat.getId();
×
428
        }
429

430
        return null;
1✔
431
    },
432

433
    /**
434
     * Rather than simply creating a new feature each time, attempt to
435
     * merge contiguous linestrings together if the end of the old line
436
     * matches the start of the new line
437
     * @param {any} origGeom
438
     * @param {any} newGeom
439
     */
440
    mergeLineStrings: function (origGeom, newGeom) {
441
        var newGeomFirstCoord = newGeom.getFirstCoordinate();
×
442
        var matchesFirstCoord = Ext.Array.equals(origGeom.getFirstCoordinate(), newGeomFirstCoord);
×
443
        var matchesLastCoord = Ext.Array.equals(origGeom.getLastCoordinate(), newGeomFirstCoord);
×
444

445
        if (matchesFirstCoord || matchesLastCoord) {
×
446
            var origCoords = origGeom.getCoordinates();
×
447
            // if drawing in continued from the start point of the original,
448
            // the original needs to be reversed to we end up with correct
449
            // start and end points
450
            if (matchesFirstCoord) {
×
451
                origCoords.reverse();
×
452
            }
453
            var newCoords = newGeom.getCoordinates();
×
454
            newGeom.setCoordinates(origCoords.concat(newCoords));
×
455
        } else {
456
            Ext.log('Start / End coordinates differ');
×
457
            Ext.log('origGeom start/end coords: ', origGeom.getFirstCoordinate(), origGeom.getLastCoordinate());
×
458
            Ext.log('newGeom start coord: ', newGeom.getFirstCoordinate());
×
459
        }
460

461
        return newGeom;
×
462
    },
463

464
    /**
465
     * Handles the drawstart event
466
     */
467
    handleDrawStart: function () {
468
        var me = this;
×
469
        me.currentlyDrawing = true;
×
470
    },
471

472
    /**
473
     * Handles the drawend event
474
     * @param {ol.interaction.Draw.Event} evt The OpenLayers draw event containing the features
475
     */
476
    handleDrawEnd: function (evt) {
477
        var me = this;
×
478
        var feature = evt.feature;
×
479
        var newGeom = feature.getGeometry();
×
480

481
        var drawSource = me.drawLayer.getSource();
×
482
        var currentFeature = drawSource.getFeaturesCollection().item(0);
×
483

484
        if (currentFeature) {
×
485
            // merge all linestrings to a single linestring
486
            // this is done in place
487
            me.mergeLineStrings(currentFeature.getGeometry(), newGeom);
×
488
        }
489

490
        me.calculateLineIntersections(feature);
×
491

492
        // clear all previous features so only the last drawn feature remains
493
        drawSource.getFeaturesCollection().clear();
×
494

495
        me.currentlyDrawing = false;
×
496
    },
497

498
    /**
499
    * Handles the modifyend event
500
    * @param {ol.interaction.Draw.Event} evt The OpenLayers draw event containing the features
501
    */
502
    handleModifyEnd: function (evt) {
503
        var me = this;
×
504
        var feature = evt.features.item(0);
×
505
        me.calculateLineIntersections(feature);
×
506
    },
507

508
    /**
509
     * Get a nodeId from the closest edge to the input coord
510
     * If both ends of the edge are within the snap tolerance of the
511
     * input coord then the node closest to the input point is used
512
     * @param {any} edgesLayer
513
     * @param {any} edgeLayerConfig
514
     * @param {any} coord
515
     * @returns
516
     */
517
    getNodeIdFromSnappedEdge: function (edgesLayer, edgeLayerConfig, coord) {
518
        var me = this;
4✔
519
        var nodeId;
520

521
        // check to see if the coord snaps to the start of any edges
522
        var edge = me.getSnappedEdge(coord, edgesLayer);
4✔
523
        if (!edge) {
4✔
524
            return null;
1✔
525
        }
526

527
        var inputPoint = new ol.geom.Point(coord);
3✔
528

529
        var startCoord = edge.getGeometry().getFirstCoordinate();
3✔
530
        var startExtent = me.getBufferedCoordExtent(startCoord);
3✔
531
        var startDistance = null;
3✔
532

533
        if (inputPoint.intersectsExtent(startExtent)) {
3!
534
            nodeId = edge.get(edgeLayerConfig.startNodeProperty);
3✔
535
            startDistance = new ol.geom.LineString([coord, startCoord]).getLength();
3✔
536
        }
537

538
        var endCoord = edge.getGeometry().getLastCoordinate();
3✔
539
        var endExtent = me.getBufferedCoordExtent(endCoord);
3✔
540

541
        if (inputPoint.intersectsExtent(endExtent)) {
3!
542
            var endNodeId = edge.get(edgeLayerConfig.endNodeProperty);
3✔
543
            if (startDistance !== null) {
3!
544
                // if an input coord snaps to both ends of the line, then take the closest end
545
                var endDistance = new ol.geom.LineString([coord, endCoord]).getLength();
3✔
546
                if (endDistance < startDistance) {
3✔
547
                    nodeId = endNodeId;
2✔
548
                }
549
            } else {
550
                nodeId = endNodeId;
×
551
            }
552
        }
553

554
        // if an edge has been found then it should snap at the start or the end
555
        // and we should not end up here
556

557
        //<debug>
558
        if (!nodeId) {
3!
559
            Ext.log.warn('A coordinate snapped to an edge, but no nodeId was found. Check the edgeLayerConfig');
×
560
        }
561
        //</debug>
562

563
        return nodeId;
3✔
564

565
    },
566

567
    /**
568
     * Calculate where the geometry intersects other parts of the network
569
     * @param {any} newGeom
570
     */
571
    calculateLineIntersections: function (feature) {
572

573
        var me = this;
1✔
574
        var view = me.getView();
1✔
575

576
        var newGeom = feature.getGeometry();
1✔
577

578
        var startCoord = newGeom.getFirstCoordinate();
1✔
579
        var endCoord = newGeom.getLastCoordinate();
1✔
580

581
        var foundFeatAtStart = false;
1✔
582
        var foundFeatAtEnd = false;
1✔
583

584
        // get any nodes that the line snaps to
585
        var nodeLayerKey = view.getNodeLayerKey();
1✔
586
        var startNodeId = null;
1✔
587
        var endNodeId = null;
1✔
588

589
        if (nodeLayerKey) {
1!
590
            var nodeLayer = BasiGX.util.Layer.getLayersBy('layerKey', nodeLayerKey)[0];
×
591
            startNodeId = me.getSnappedFeatureId(startCoord, nodeLayer);
×
592
            endNodeId = me.getSnappedFeatureId(endCoord, nodeLayer);
×
593

594
            foundFeatAtStart = startNodeId ? true : false;
×
595
            foundFeatAtEnd = endNodeId ? true : false;
×
596
        }
597

598
        var edgeLayerKey = view.getEdgeLayerKey();
1✔
599
        var edgesLayer;
600

601
        if (edgeLayerKey) {
1!
602
            edgesLayer = BasiGX.util.Layer.getLayersBy('layerKey', edgeLayerKey)[0];
1✔
603
        }
604

605
        // if the edge layer has been configured with to and from node fields
606
        // we will check if the feature snaps at the start or end of edges
607

608
        var edgeLayerConfig = view.getEdgeLayerConfig();
1✔
609

610
        if (edgesLayer && edgeLayerConfig) {
1!
611
            var nodeId;
612
            nodeId = me.getNodeIdFromSnappedEdge(edgesLayer, edgeLayerConfig, startCoord);
1✔
613

614
            if (nodeId) {
1!
615
                startNodeId = nodeId;
1✔
616
                foundFeatAtStart = true;
1✔
617
            }
618

619
            nodeId = me.getNodeIdFromSnappedEdge(edgesLayer, edgeLayerConfig, endCoord);
1✔
620

621
            if (nodeId) {
1!
622
                endNodeId = nodeId;
×
623
                foundFeatAtEnd = true;
×
624
            }
625
        }
626

627
        // now check for any edges at both ends, but only in the case
628
        // where there are no start and end nodes
629

630

631
        var startEdgeId = null;
1✔
632
        var endEdgeId = null;
1✔
633

634
        if (edgesLayer) {
1!
635

636
            if (!foundFeatAtStart) {
1!
637
                startEdgeId = me.getSnappedFeatureId(startCoord, edgesLayer);
×
638
                foundFeatAtStart = startEdgeId ? true : false;
×
639
            }
640

641
            if (!foundFeatAtEnd) {
1!
642
                endEdgeId = me.getSnappedFeatureId(endCoord, edgesLayer);
1✔
643
                foundFeatAtEnd = endEdgeId ? true : false;
1!
644
            }
645
        }
646

647
        // finally we will check if we have snapped to a polygon edge
648
        // this will allow us to create continua based on points around the polygon edge
649

650
        var polygonLayerKey = view.getPolygonLayerKey();
1✔
651
        var startPolygonId = null;
1✔
652
        var endPolygonId = null;
1✔
653

654
        if (polygonLayerKey) {
1!
655
            var polygonsLayer = BasiGX.util.Layer.getLayersBy('layerKey', polygonLayerKey)[0];
×
656

657
            if (!foundFeatAtStart) {
×
658
                startPolygonId = me.getSnappedFeatureId(startCoord, polygonsLayer);
×
659
            }
660

661
            if (!foundFeatAtEnd) {
×
662
                endPolygonId = me.getSnappedFeatureId(endCoord, polygonsLayer);
×
663
            }
664
        }
665

666
        var result = {
1✔
667
            startNodeId: startNodeId,
668
            endNodeId: endNodeId,
669
            startCoord: startCoord,
670
            endCoord: endCoord,
671
            startEdgeId: startEdgeId,
672
            endEdgeId: endEdgeId,
673
            startPolygonId: startPolygonId,
674
            endPolygonId: endPolygonId
675
        };
676

677
        // set the node ids on the edge feature itself
678
        // as these can be used by a polygon tool / grid
679
        // the "magic" number -2 indicates a new node should be created
680
        // for the line, rather than snapping to an existing node
681
        feature.set('startNodeId', startNodeId ? startNodeId : -2);
1!
682
        feature.set('endNodeId', endNodeId ? endNodeId : -2);
1!
683

684
        // fire an event when the drawing is complete
685
        var drawSource = me.drawLayer.getSource();
1✔
686
        drawSource.dispatchEvent({ type: 'localdrawend', result: result });
1✔
687
    },
688

689
    handleKeyPress: function (evt) {
690

691
        var me = this; // bound to the controller in the constructor
×
692

693
        // use DEL to remove last point
694
        if (evt.keyCode == 46) {
×
695
            if (evt.shiftKey === true) {
×
696
                // or set focus just on the map as per https://stackoverflow.com/questions/59453895/add-keyboard-event-to-openlayers-map
697
                // or if the delete key is used in a form it will also remove a point
698
                me.drawInteraction.removeLastPoint();
×
699
            }
700
        }
701

702
        // use ESC to cancel drawing mode
703
        if (evt.keyCode == 27) {
×
704
            me.drawInteraction.finishDrawing();
×
705
        }
706
    },
707

708
    /**
709
     * Main handler which activates or deactivates the interactions and listeners
710
     * @param {Ext.button.Button} btn The button that has been pressed
711
     * @param {boolean} pressed The toggle state of the button
712
     */
713
    onToggle: function (btn, pressed) {
714
        var me = this;
×
715
        var view = me.getView();
×
716

717
        // guess the map if not given
718
        if (!me.map) {
×
719
            me.map = BasiGX.util.Map.getMapComponent().map;
×
720
        }
721

722
        // use draw layer set in the view
723
        //<debug>
724
        Ext.Assert.truthy(view.drawLayer);
×
725
        //</debug>
726
        if (!me.drawLayer) {
×
727
            if (view.drawLayer) {
×
728
                me.drawLayer = view.drawLayer;
×
729
            }
730
        }
731

732
        me.prepareDrawingStyles();
×
733

734
        // set initial style for drawing features
735
        me.defaultDrawStyle = [
×
736
            view.getDrawBeforeEditingPoint(),
737
            view.getDrawStyleStartPoint(),
738
            view.getDrawStyleLine(),
739
            view.getDrawStyleEndPoint(),
740
        ];
741
        me.drawLayer.setStyle(me.defaultDrawStyle);
×
742

743
        me.setDrawInteraction(me.drawLayer);
×
744
        me.setModifyInteraction(me.drawLayer);
×
745
        me.setSnapInteraction(me.drawLayer);
×
746

747
        var viewPort = me.map.getViewport();
×
748

749
        var tracingLayerKeys = view.getTracingLayerKeys();
×
750

751
        if (pressed) {
×
752

753
            me.initTracing(
×
754
                tracingLayerKeys,
755
                me.drawInteraction
756
            );
757
            me.drawInteraction.setActive(true);
×
758
            me.modifyInteraction.setActive(true);
×
759
            me.snapInteraction.setActive(true);
×
760
            viewPort.addEventListener('contextmenu', me.contextHandler);
×
761
            document.addEventListener('keydown', me.handleKeyPress);
×
762
        } else {
763
            me.cleanupTracing();
×
764
            me.drawInteraction.setActive(false);
×
765
            me.modifyInteraction.setActive(false);
×
766
            me.snapInteraction.setActive(false);
×
767
            viewPort.removeEventListener('contextmenu', me.contextHandler);
×
768
            document.removeEventListener('keydown', me.handleKeyPress);
×
769
        }
770
    },
771

772
    /**
773
     * Called when new tracing coordinates are available.
774
     *
775
     * @param {ol.coordinate.Coordinate[]} appendCoords The new coordinates
776
     */
777
    handleTracingResult: function (appendCoords) {
778
        var me = this;
×
779
        me.drawInteraction.removeLastPoint();
×
780
        me.drawInteraction.appendCoordinates(appendCoords);
×
781
    },
782

783
    /**
784
     * Method shows the context menu on mouse right click
785
     * @param {Event} evt The browser event
786
     */
787
    showContextMenu: function (evt) {
788
        // suppress default browser behaviour
789
        evt.preventDefault();
×
790

791
        var me = this.scope;
×
792

793
        var menuItems = [{
×
794
            text: 'Clear All',
795
            handler: function () {
796
                try {
×
797
                    me.drawLayer.getSource().getFeaturesCollection().clear();
×
798
                } catch (error) {
799
                    // sometimes get an error here when trying to clear the features collection
800
                    // Cannot read properties of null (reading 'findIndexBy')
801
                    // TODO debug - seems to occur after the layer is reloaded, so we may need to
802
                    // update the collection on reload? the source still has the same ol_uid
803
                    Ext.log.error(error);
×
804
                }
805
            }
806
        }];
807

808
        var menu = Ext.create('Ext.menu.Menu', {
×
809
            width: 100,
810
            plain: true,
811
            renderTo: Ext.getBody(),
812
            items: menuItems
813
        });
814
        menu.showAt(evt.pageX, evt.pageY);
×
815
    },
816

817
    /**
818
     * Remove the interaction when this component gets destroyed
819
     */
820
    onBeforeDestroy: function () {
821

822
        var me = this;
×
823
        var btn = me.getView();
×
824

825
        // detoggle button
826
        me.onToggle(btn, false);
×
827

828
        // fire the button's toggle event so that the defaultClickEnabled property
829
        // is updated in CpsiMapview.util.ApplicationMixin to re-enable clicks
830
        btn.pressed = false;
×
831
        btn.fireEvent('toggle');
×
832

833

834
        if (me.drawInteraction) {
×
835
            me.map.removeInteraction(me.drawInteraction);
×
836
        }
837

838
        if (me.modifyInteraction) {
×
839
            me.map.removeInteraction(me.modifyInteraction);
×
840
        }
841

842
        if (me.snapInteraction) {
×
843
            me.map.removeInteraction(me.snapInteraction);
×
844
        }
845

846
        if (me.drawLayer) {
×
847
            me.map.removeLayer(me.drawLayer);
×
848
        }
849

850
        me.unBindLayerListeners();
×
851
        me.cleanupTracing();
×
852
    },
853

854
    /**
855
     * Remove event listeners by key, for each key in the listenerKeys array
856
     *
857
     */
858
    unBindLayerListeners: function () {
859
        Ext.Array.each(this.listenerKeys, function (key) {
11✔
860
            ol.Observable.unByKey(key);
×
861
        });
862
        this.listenerKeys = [];
11✔
863
    },
864

865
    init: function () {
866

867
        var me = this;
15✔
868

869
        // create an object for the contextmenu eventhandler
870
        // so it can be removed correctly
871
        me.contextHandler = {
15✔
872
            handleEvent: me.showContextMenu,
873
            scope: me
874
        };
875
    }
876
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc