index.js (16628B)
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 const code = e.keyCode; 167 const targetTag = e.target.tagName.toLowerCase(); 168 const isTargetTextInput = (targetTag === 'input' && e.target.type.toLowerCase() === 'text') || 169 targetTag === 'textarea'; 170 if (code !== 27 && isTargetTextInput) { 171 return; 172 } 173 174 switch (code) { 175 case 27: // Escape 176 case 13: // Enter 177 if (this._drawingDirection) { 178 this.stopDrawingLine(); 179 } else { 180 if (!this.preventStopEdit) { 181 this.stopEdit(true); 182 } 183 } 184 L.DomEvent.stop(e); 185 break; 186 case 8: // Backspace 187 case 46: // Delete 188 if (this._drawingDirection && this.getLatLngs().length > 2) { 189 const nodeIndex = this._drawingDirection === 1 ? this.getLatLngs().length - 2 : 1; 190 this.removeNode(nodeIndex); 191 L.DomEvent.preventDefault(e); 192 } 193 break; 194 195 default: 196 } 197 }, 198 199 onMouseMoveFollowEndNode: function(e) { 200 var nodeIndex = this._drawingDirection === -1 ? 0 : this.getLatLngs().length - 1; 201 let latlng = e.latlng; 202 if (this._latlngs.length > 0) { 203 latlng = wrapLatLngToTarget(latlng, this._latlngs[nodeIndex]); 204 } 205 this.spliceLatLngs(nodeIndex, 1, latlng); 206 }, 207 208 makeNodeMarker: function(nodeIndex) { 209 var node = this.getLatLngs()[nodeIndex], 210 marker = L.marker(node.clone(), { 211 icon: L.divIcon({ 212 className: 'line-editor-node-marker-halo', 213 html: '<div class="line-editor-node-marker"></div>' 214 }), 215 draggable: true, 216 zIndexOffset: this._nodeMarkersZOffset, 217 projectedShift: () => this.shiftProjectedFitMapView() 218 } 219 ); 220 marker 221 .on('drag', this.onNodeMarkerMovedChangeNode, this) 222 // .on('dragstart', this.fire.bind(this, 'editingstart')) 223 .on('dragend', this.onNodeMarkerDragEnd, this) 224 .on('dblclick', this.onNodeMarkerDblClickedRemoveNode, this) 225 .on('click', this.onNodeMarkerClickStartStopDrawing, this) 226 .on('contextmenu', function(e) { 227 this.stopDrawingLine(); 228 this.fire('noderightclick', { 229 nodeIndex: this.getMarkerIndex(marker), 230 line: this, 231 mouseEvent: e 232 } 233 ); 234 }, this 235 ); 236 marker._lineNode = node; 237 node._nodeMarker = marker; 238 marker.addTo(this.nodeMarkers); 239 }, 240 241 onNodeMarkerClickStartStopDrawing: function(e) { 242 if (this._disableEditOnLeftClick) { 243 return; 244 } 245 var marker = e.target, 246 latlngs = this.getLatLngs(), 247 latlngs_n = latlngs.length, 248 nodeIndex = this.getMarkerIndex(marker); 249 if ((this._drawingDirection === -1 && nodeIndex === 1) || 250 ((this._drawingDirection === 1 && nodeIndex === latlngs_n - 2))) { 251 this.stopDrawingLine(); 252 } else if (nodeIndex === this.getLatLngs().length - 1) { 253 this.startDrawingLine(1, e); 254 } else if (nodeIndex === 0) { 255 this.startDrawingLine(-1, e); 256 } 257 }, 258 259 makeSegmentOverlay: function(nodeIndex) { 260 const latlngs = this.getLatLngs(), 261 p1 = latlngs[nodeIndex], 262 p2 = latlngs[nodeIndex + 1]; 263 if (!p2) { 264 return; 265 } 266 const segmentOverlay = L.polyline([p1, p2], { 267 weight: 10, 268 opacity: 0.0, 269 projectedShift: () => this.shiftProjectedFitMapView() 270 }); 271 segmentOverlay.on('mousedown', this.onSegmentMouseDownAddNode, this); 272 segmentOverlay.on('contextmenu', function(e) { 273 this.stopDrawingLine(); 274 this.fire('segmentrightclick', { 275 nodeIndex: this.getSegmentOverlayIndex(segmentOverlay), 276 mouseEvent: e, 277 line: this 278 } 279 ); 280 }, this 281 ); 282 segmentOverlay._lineNode = p1; 283 p1._segmentOverlay = segmentOverlay; 284 segmentOverlay.addTo(this.segmentOverlays); 285 }, 286 287 onSegmentMouseDownAddNode: function(e) { 288 if (e.originalEvent.button !== 0 || this._disableEditOnLeftClick) { 289 return; 290 } 291 var segmentOverlay = e.target, 292 latlngs = this.getLatLngs(), 293 nodeIndex = this.getSegmentOverlayIndex(segmentOverlay) + 1; 294 const midPoint = L.latLngBounds(latlngs[nodeIndex], latlngs[nodeIndex - 1]).getCenter(); 295 this.addNode(nodeIndex, wrapLatLngToTarget(e.latlng, midPoint)); 296 if (L.Draggable._dragging) { 297 L.Draggable._dragging.finishDrag(); 298 } 299 latlngs[nodeIndex]._nodeMarker.dragging._draggable._onDown(e.originalEvent); 300 }, 301 302 addNode: function(index, latlng) { 303 var nodes = this.getLatLngs(), 304 isAddingLeft = (index === 1 && this._drawingDirection === -1), 305 isAddingRight = (index === nodes.length - 1 && this._drawingDirection === 1); 306 latlng = latlng.clone(); 307 this.spliceLatLngs(index, 0, latlng); 308 this.makeNodeMarker(index); 309 if (!isAddingLeft && (index >= 1)) { 310 if (!isAddingRight) { 311 var prevNode = nodes[index - 1]; 312 this.segmentOverlays.removeLayer(prevNode._segmentOverlay); 313 delete prevNode._segmentOverlay._lineNode; 314 delete prevNode._segmentOverlay; 315 } 316 this.makeSegmentOverlay(index - 1); 317 } 318 if (!isAddingRight) { 319 this.makeSegmentOverlay(index); 320 } 321 if (nodes.length < 3) { 322 this._setupEndMarkers(); 323 } 324 }, 325 326 removeNode: function(index) { 327 var nodes = this.getLatLngs(), 328 node = nodes[index], 329 marker = node._nodeMarker; 330 delete node._nodeMarker; 331 delete marker._lineNode; 332 this.spliceLatLngs(index, 1); 333 this.nodeMarkers.removeLayer(marker); 334 if (node._segmentOverlay) { 335 this.segmentOverlays.removeLayer(node._segmentOverlay); 336 delete node._segmentOverlay._lineNode; 337 delete node._segmentOverlay; 338 } 339 var prevNode = nodes[index - 1]; 340 if (prevNode && prevNode._segmentOverlay) { 341 this.segmentOverlays.removeLayer(prevNode._segmentOverlay); 342 delete prevNode._segmentOverlay._lineNode; 343 delete prevNode._segmentOverlay; 344 if ((index < nodes.length - 1) || (index < nodes.length && this._drawingDirection !== 1)) { 345 this.makeSegmentOverlay(index - 1); 346 } 347 } 348 }, 349 350 replaceNode: function(index, latlng) { 351 var nodes = this.getLatLngs(), 352 oldNode = nodes[index], 353 oldMarker = oldNode._nodeMarker; 354 this.nodeMarkers.removeLayer(oldNode._nodeMarker); 355 delete oldNode._nodeMarker; 356 delete oldMarker._lineNode; 357 latlng = latlng.clone(); 358 this.spliceLatLngs(index, 1, latlng); 359 this.makeNodeMarker(index); 360 if (oldNode._segmentOverlay) { 361 this.segmentOverlays.removeLayer(oldNode._segmentOverlay); 362 delete oldNode._segmentOverlay._lineNode; 363 delete oldNode._segmentOverlay; 364 this.makeSegmentOverlay(index); 365 } 366 var prevNode = nodes[index - 1]; 367 if (prevNode && prevNode._segmentOverlay) { 368 this.segmentOverlays.removeLayer(prevNode._segmentOverlay); 369 delete prevNode._segmentOverlay._lineNode; 370 delete prevNode._segmentOverlay; 371 this.makeSegmentOverlay(index - 1); 372 } 373 }, 374 375 _setupEndMarkers: function() { 376 const nodesCount = this._latlngs.length; 377 if (nodesCount === 0) { 378 return; 379 } 380 const startIndex = this._drawingDirection === -1 ? 1 : 0; 381 const endIndex = this._drawingDirection === 1 ? nodesCount - 2 : nodesCount - 1; 382 const startIcon = this._latlngs[startIndex]._nodeMarker._icon; 383 L.DomUtil[this._drawingDirection === -1 ? 'removeClass' : 'addClass']( 384 startIcon, 'line-editor-node-marker-start' 385 ); 386 if (endIndex >= 0) { 387 const endIcon = this._latlngs[endIndex]._nodeMarker._icon; 388 let func; 389 if (this._drawingDirection !== 1 && endIndex > 0) { 390 func = L.DomUtil.addClass; 391 } else { 392 func = L.DomUtil.removeClass; 393 } 394 func(endIcon, 'line-editor-node-marker-end'); 395 } 396 }, 397 398 setupMarkers: function() { 399 if (!this.segmentOverlays) { 400 this.segmentOverlays = L.featureGroup().addTo(this._map); 401 } 402 if (!this.nodeMarkers) { 403 this.nodeMarkers = L.featureGroup().addTo(this._map); 404 } 405 this.removeMarkers(); 406 var latlngs = this.getLatLngs(), 407 startNode = 0, 408 endNode = latlngs.length - 1; 409 if (this._drawingDirection === -1) { 410 startNode += 1; 411 } 412 if (this._drawingDirection === 1) { 413 endNode -= 1; 414 } 415 for (var i = startNode; i <= endNode; i++) { 416 this.makeNodeMarker(i); 417 if (i < endNode) { 418 this.makeSegmentOverlay(i); 419 } 420 } 421 this._setupEndMarkers(); 422 }, 423 424 spliceLatLngs: function(...args) { 425 const latlngs = this.getLatLngs(); 426 const res = latlngs.splice(...args); 427 this.setLatLngs(latlngs); 428 this.fire('nodeschanged'); 429 return res; 430 // this._latlngs.splice(...args); 431 // this.redraw(); 432 }, 433 434 getFixedLatLngs: function() { 435 const start = this._drawingDirection === -1 ? 1 : 0; 436 let end = this._latlngs.length; 437 if (this._drawingDirection === 1) { 438 end -= 1; 439 } 440 return this._latlngs.slice(start, end); 441 }, 442 443 disableEditOnLeftClick: function(disable) { 444 this._disableEditOnLeftClick = disable; 445 }, 446 447 highlighNodesForDeletion: function(startNodeIndex, endNodeIndex) { 448 if (!this._editing) { 449 return; 450 } 451 const nodes = this.getLatLngs(); 452 for (let i = 0; i < nodes.length; i++) { 453 const node = nodes[i]; 454 const icon = node._nodeMarker._icon; 455 L.DomUtil[i >= startNodeIndex && i <= endNodeIndex ? 'addClass' : 'removeClass'](icon, 'highlight-delete'); 456 } 457 } 458 };
