index.js (16394B)
1 import L from 'leaflet'; 2 import './edit_line.css'; 3 import {wrapLatLngToTarget} from '~/lib/leaflet.fixes/fixWorldCopyJump'; 4 5 L.Polyline.EditMixinOptions = { 6 className: 'leaflet-editable-line' 7 }; 8 9 L.Polyline.EditMixin = { 10 _nodeMarkersZOffset: 10000, 11 12 startEdit: function() { 13 if (this._map && !this._editing) { 14 this._editing = true; 15 this._drawingDirection = 0; 16 this.setupMarkers(); 17 this.on('remove', this.stopEdit.bind(this)); 18 this._map 19 .on('click', this.onMapClick, this) 20 .on('dragend', this.onMapEndDrag, this); 21 L.DomEvent.on(document, 'keyup', this.onKeyPress, this); 22 this._storedStyle = {weight: this.options.weight, opacity: this.options.opacity}; 23 this.setStyle({weight: 1.5, opacity: 1}); 24 L.DomUtil.addClass(this._map._container, 'leaflet-line-editing'); 25 this.fire('editstart', {target: this}); 26 } 27 }, 28 29 stopEdit: function(userCancelled) { 30 if (this._editing) { 31 this.stopDrawingLine(); 32 this._editing = false; 33 this.removeMarkers(); 34 L.DomEvent.off(document, 'keyup', this.onKeyPress, this); 35 this.off('remove', this.stopEdit.bind(this)); 36 this._map 37 .off('click', this.onMapClick, this) 38 .off('dragend', this.onMapEndDrag, this); 39 this.setStyle(this._storedStyle); 40 L.DomUtil.removeClass(this._map._container, 'leaflet-line-editing'); 41 this.fire('editend', {target: this, userCancelled}); 42 } 43 }, 44 45 removeMarkers: function() { 46 this.getLatLngs().forEach(function(node) { 47 if (node._nodeMarker) { 48 this.nodeMarkers.removeLayer(node._nodeMarker); 49 delete node._nodeMarker._lineNode; 50 delete node._nodeMarker; 51 } 52 if (node._segmentOverlay) { 53 this.segmentOverlays.removeLayer(node._segmentOverlay); 54 delete node._segmentOverlay._lineNode; 55 delete node._segmentOverlay; 56 } 57 }.bind(this) 58 ); 59 }, 60 61 getMarkerIndex: function(marker) { 62 return this.getLatLngs().indexOf(marker._lineNode); 63 }, 64 65 getSegmentOverlayIndex: function(segmentOverlay) { 66 return this.getLatLngs().indexOf(segmentOverlay._lineNode); 67 }, 68 69 onNodeMarkerDragEnd: function(e) { 70 var marker = e.target, 71 nodeIndex = this.getMarkerIndex(marker); 72 this.replaceNode(nodeIndex, marker.getLatLng()); 73 this._setupEndMarkers(); 74 }, 75 76 onNodeMarkerMovedChangeNode: function(e) { 77 var marker = e.target, 78 latlng = marker.getLatLng(), 79 node = marker._lineNode; 80 node.lat = latlng.lat; 81 node.lng = latlng.lng; 82 this.redraw(); 83 this.fire('nodeschanged'); 84 }, 85 86 onNodeMarkerDblClickedRemoveNode: function(e) { 87 if (this._disableEditOnLeftClick) { 88 return; 89 } 90 if (this.getLatLngs().length < 2 || (this._drawingDirection && this.getLatLngs().length === 2)) { 91 return; 92 } 93 var marker = e.target, 94 nodeIndex = this.getMarkerIndex(marker); 95 this.removeNode(nodeIndex); 96 this._setupEndMarkers(); 97 }, 98 99 onMapClick: function(e) { 100 if (this._drawingDirection) { 101 let newNodeIndex, 102 refNodeIndex; 103 if (this._drawingDirection === -1) { 104 newNodeIndex = 1; 105 refNodeIndex = 0; 106 } else { 107 newNodeIndex = this._latlngs.length - 1; 108 refNodeIndex = this._latlngs.length - 1; 109 } 110 this.addNode(newNodeIndex, wrapLatLngToTarget(e.latlng, this._latlngs[refNodeIndex])); 111 } else { 112 if (!this.preventStopEdit) { 113 this.stopEdit(true); 114 } 115 } 116 }, 117 118 onMapEndDrag: function(e) { 119 if (e.distance < 15) { 120 // get mouse position from map drag handler 121 var handler = e.target.dragging._draggable; 122 var mousePos = handler._startPoint.add(handler._newPos).subtract(handler._startPos); 123 var latlng = e.target.mouseEventToLatLng({clientX: mousePos.x, clientY: mousePos.y}); 124 this.onMapClick({latlng: latlng}); 125 } 126 }, 127 128 startDrawingLine: function(direction, e) { 129 if (!this._editing) { 130 return; 131 } 132 if (direction === undefined) { 133 direction = 1; 134 } 135 if (this._drawingDirection === direction) { 136 return; 137 } 138 this.stopDrawingLine(); 139 this._drawingDirection = direction; 140 if (e) { 141 var newNodeIndex = this._drawingDirection === -1 ? 0 : this.getLatLngs().length; 142 this.spliceLatLngs(newNodeIndex, 0, e.latlng); 143 this._setupEndMarkers(); 144 } 145 146 this._map.on('mousemove', this.onMouseMoveFollowEndNode, this); 147 L.DomUtil.addClass(this._map._container, 'leaflet-line-drawing'); 148 this._map.clickLocked = true; 149 }, 150 151 stopDrawingLine: function() { 152 if (!this._drawingDirection) { 153 return; 154 } 155 this._map.off('mousemove', this.onMouseMoveFollowEndNode, this); 156 var nodeIndex = this._drawingDirection === -1 ? 0 : this.getLatLngs().length - 1; 157 this.spliceLatLngs(nodeIndex, 1); 158 this._drawingDirection = 0; 159 L.DomUtil.removeClass(this._map._container, 'leaflet-line-drawing'); 160 this._map.clickLocked = false; 161 this._setupEndMarkers(); 162 this.fire('drawend'); 163 }, 164 165 onKeyPress: function(e) { 166 if (e.target.tagName.toLowerCase() === 'input') { 167 return; 168 } 169 var code = e.keyCode; 170 switch (code) { 171 case 27: 172 case 13: 173 if (this._drawingDirection) { 174 this.stopDrawingLine(); 175 } else { 176 if (!this.preventStopEdit) { 177 this.stopEdit(true); 178 } 179 } 180 L.DomEvent.stop(e); 181 break; 182 case 8: 183 case 46: 184 if (this._drawingDirection && this.getLatLngs().length > 2) { 185 const nodeIndex = this._drawingDirection === 1 ? this.getLatLngs().length - 2 : 1; 186 this.removeNode(nodeIndex); 187 L.DomEvent.preventDefault(e); 188 } 189 break; 190 191 default: 192 } 193 }, 194 195 onMouseMoveFollowEndNode: function(e) { 196 var nodeIndex = this._drawingDirection === -1 ? 0 : this.getLatLngs().length - 1; 197 let latlng = e.latlng; 198 if (this._latlngs.length > 0) { 199 latlng = wrapLatLngToTarget(latlng, this._latlngs[nodeIndex]); 200 } 201 this.spliceLatLngs(nodeIndex, 1, latlng); 202 }, 203 204 makeNodeMarker: function(nodeIndex) { 205 var node = this.getLatLngs()[nodeIndex], 206 marker = L.marker(node.clone(), { 207 icon: L.divIcon({ 208 className: 'line-editor-node-marker-halo', 209 html: '<div class="line-editor-node-marker"></div>' 210 }), 211 draggable: true, 212 zIndexOffset: this._nodeMarkersZOffset, 213 projectedShift: () => this.shiftProjectedFitMapView() 214 } 215 ); 216 marker 217 .on('drag', this.onNodeMarkerMovedChangeNode, this) 218 // .on('dragstart', this.fire.bind(this, 'editingstart')) 219 .on('dragend', this.onNodeMarkerDragEnd, this) 220 .on('dblclick', this.onNodeMarkerDblClickedRemoveNode, this) 221 .on('click', this.onNodeMarkerClickStartStopDrawing, this) 222 .on('contextmenu', function(e) { 223 this.stopDrawingLine(); 224 this.fire('noderightclick', { 225 nodeIndex: this.getMarkerIndex(marker), 226 line: this, 227 mouseEvent: e 228 } 229 ); 230 }, this 231 ); 232 marker._lineNode = node; 233 node._nodeMarker = marker; 234 marker.addTo(this.nodeMarkers); 235 }, 236 237 onNodeMarkerClickStartStopDrawing: function(e) { 238 if (this._disableEditOnLeftClick) { 239 return; 240 } 241 var marker = e.target, 242 latlngs = this.getLatLngs(), 243 latlngs_n = latlngs.length, 244 nodeIndex = this.getMarkerIndex(marker); 245 if ((this._drawingDirection === -1 && nodeIndex === 1) || 246 ((this._drawingDirection === 1 && nodeIndex === latlngs_n - 2))) { 247 this.stopDrawingLine(); 248 } else if (nodeIndex === this.getLatLngs().length - 1) { 249 this.startDrawingLine(1, e); 250 } else if (nodeIndex === 0) { 251 this.startDrawingLine(-1, e); 252 } 253 }, 254 255 makeSegmentOverlay: function(nodeIndex) { 256 const latlngs = this.getLatLngs(), 257 p1 = latlngs[nodeIndex], 258 p2 = latlngs[nodeIndex + 1]; 259 if (!p2) { 260 return; 261 } 262 const segmentOverlay = L.polyline([p1, p2], { 263 weight: 10, 264 opacity: 0.0, 265 projectedShift: () => this.shiftProjectedFitMapView() 266 }); 267 segmentOverlay.on('mousedown', this.onSegmentMouseDownAddNode, this); 268 segmentOverlay.on('contextmenu', function(e) { 269 this.stopDrawingLine(); 270 this.fire('segmentrightclick', { 271 nodeIndex: this.getSegmentOverlayIndex(segmentOverlay), 272 mouseEvent: e, 273 line: this 274 } 275 ); 276 }, this 277 ); 278 segmentOverlay._lineNode = p1; 279 p1._segmentOverlay = segmentOverlay; 280 segmentOverlay.addTo(this.segmentOverlays); 281 }, 282 283 onSegmentMouseDownAddNode: function(e) { 284 if (e.originalEvent.button !== 0 || this._disableEditOnLeftClick) { 285 return; 286 } 287 var segmentOverlay = e.target, 288 latlngs = this.getLatLngs(), 289 nodeIndex = this.getSegmentOverlayIndex(segmentOverlay) + 1; 290 const midPoint = L.latLngBounds(latlngs[nodeIndex], latlngs[nodeIndex - 1]).getCenter(); 291 this.addNode(nodeIndex, wrapLatLngToTarget(e.latlng, midPoint)); 292 if (L.Draggable._dragging) { 293 L.Draggable._dragging.finishDrag(); 294 } 295 latlngs[nodeIndex]._nodeMarker.dragging._draggable._onDown(e.originalEvent); 296 }, 297 298 addNode: function(index, latlng) { 299 var nodes = this.getLatLngs(), 300 isAddingLeft = (index === 1 && this._drawingDirection === -1), 301 isAddingRight = (index === nodes.length - 1 && this._drawingDirection === 1); 302 latlng = latlng.clone(); 303 this.spliceLatLngs(index, 0, latlng); 304 this.makeNodeMarker(index); 305 if (!isAddingLeft && (index >= 1)) { 306 if (!isAddingRight) { 307 var prevNode = nodes[index - 1]; 308 this.segmentOverlays.removeLayer(prevNode._segmentOverlay); 309 delete prevNode._segmentOverlay._lineNode; 310 delete prevNode._segmentOverlay; 311 } 312 this.makeSegmentOverlay(index - 1); 313 } 314 if (!isAddingRight) { 315 this.makeSegmentOverlay(index); 316 } 317 if (nodes.length < 3) { 318 this._setupEndMarkers(); 319 } 320 }, 321 322 removeNode: function(index) { 323 var nodes = this.getLatLngs(), 324 node = nodes[index], 325 marker = node._nodeMarker; 326 delete node._nodeMarker; 327 delete marker._lineNode; 328 this.spliceLatLngs(index, 1); 329 this.nodeMarkers.removeLayer(marker); 330 if (node._segmentOverlay) { 331 this.segmentOverlays.removeLayer(node._segmentOverlay); 332 delete node._segmentOverlay._lineNode; 333 delete node._segmentOverlay; 334 } 335 var prevNode = nodes[index - 1]; 336 if (prevNode && prevNode._segmentOverlay) { 337 this.segmentOverlays.removeLayer(prevNode._segmentOverlay); 338 delete prevNode._segmentOverlay._lineNode; 339 delete prevNode._segmentOverlay; 340 if ((index < nodes.length - 1) || (index < nodes.length && this._drawingDirection !== 1)) { 341 this.makeSegmentOverlay(index - 1); 342 } 343 } 344 }, 345 346 replaceNode: function(index, latlng) { 347 var nodes = this.getLatLngs(), 348 oldNode = nodes[index], 349 oldMarker = oldNode._nodeMarker; 350 this.nodeMarkers.removeLayer(oldNode._nodeMarker); 351 delete oldNode._nodeMarker; 352 delete oldMarker._lineNode; 353 latlng = latlng.clone(); 354 this.spliceLatLngs(index, 1, latlng); 355 this.makeNodeMarker(index); 356 if (oldNode._segmentOverlay) { 357 this.segmentOverlays.removeLayer(oldNode._segmentOverlay); 358 delete oldNode._segmentOverlay._lineNode; 359 delete oldNode._segmentOverlay; 360 this.makeSegmentOverlay(index); 361 } 362 var prevNode = nodes[index - 1]; 363 if (prevNode && prevNode._segmentOverlay) { 364 this.segmentOverlays.removeLayer(prevNode._segmentOverlay); 365 delete prevNode._segmentOverlay._lineNode; 366 delete prevNode._segmentOverlay; 367 this.makeSegmentOverlay(index - 1); 368 } 369 }, 370 371 _setupEndMarkers: function() { 372 const nodesCount = this._latlngs.length; 373 if (nodesCount === 0) { 374 return; 375 } 376 const startIndex = this._drawingDirection === -1 ? 1 : 0; 377 const endIndex = this._drawingDirection === 1 ? nodesCount - 2 : nodesCount - 1; 378 const startIcon = this._latlngs[startIndex]._nodeMarker._icon; 379 L.DomUtil[this._drawingDirection === -1 ? 'removeClass' : 'addClass']( 380 startIcon, 'line-editor-node-marker-start' 381 ); 382 if (endIndex >= 0) { 383 const endIcon = this._latlngs[endIndex]._nodeMarker._icon; 384 let func; 385 if (this._drawingDirection !== 1 && endIndex > 0) { 386 func = L.DomUtil.addClass; 387 } else { 388 func = L.DomUtil.removeClass; 389 } 390 func(endIcon, 'line-editor-node-marker-end'); 391 } 392 }, 393 394 setupMarkers: function() { 395 if (!this.segmentOverlays) { 396 this.segmentOverlays = L.featureGroup().addTo(this._map); 397 } 398 if (!this.nodeMarkers) { 399 this.nodeMarkers = L.featureGroup().addTo(this._map); 400 } 401 this.removeMarkers(); 402 var latlngs = this.getLatLngs(), 403 startNode = 0, 404 endNode = latlngs.length - 1; 405 if (this._drawingDirection === -1) { 406 startNode += 1; 407 } 408 if (this._drawingDirection === 1) { 409 endNode -= 1; 410 } 411 for (var i = startNode; i <= endNode; i++) { 412 this.makeNodeMarker(i); 413 if (i < endNode) { 414 this.makeSegmentOverlay(i); 415 } 416 } 417 this._setupEndMarkers(); 418 }, 419 420 spliceLatLngs: function(...args) { 421 const latlngs = this.getLatLngs(); 422 const res = latlngs.splice(...args); 423 this.setLatLngs(latlngs); 424 this.fire('nodeschanged'); 425 return res; 426 // this._latlngs.splice(...args); 427 // this.redraw(); 428 }, 429 430 getFixedLatLngs: function() { 431 const start = this._drawingDirection === -1 ? 1 : 0; 432 let end = this._latlngs.length; 433 if (this._drawingDirection === 1) { 434 end -= 1; 435 } 436 return this._latlngs.slice(start, end); 437 }, 438 439 disableEditOnLeftClick: function(disable) { 440 this._disableEditOnLeftClick = disable; 441 }, 442 443 highlighNodesForDeletion: function(startNodeIndex, endNodeIndex) { 444 if (!this._editing) { 445 return; 446 } 447 const nodes = this.getLatLngs(); 448 for (let i = 0; i < nodes.length; i++) { 449 const node = nodes[i]; 450 const icon = node._nodeMarker._icon; 451 L.DomUtil[i >= startNodeIndex && i <= endNodeIndex ? 'addClass' : 'removeClass'](icon, 'highlight-delete'); 452 } 453 } 454 };