L.Edit = L.Edit || {}; /** * @class L.Edit.Polyline * @aka L.Edit.Poly * @aka Edit.Poly */ L.Edit.Poly = L.Handler.extend({ // @method initialize(): void initialize: function (poly) { this.latlngs = [poly._latlngs]; if (poly._holes) { this.latlngs = this.latlngs.concat(poly._holes); } this._poly = poly; this._poly.on('revert-edited', this._updateLatLngs, this); }, // Compatibility method to normalize Poly* objects // between 0.7.x and 1.0+ _defaultShape: function () { if (!L.Polyline._flat) { return this._poly._latlngs; } return L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0]; }, _eachVertexHandler: function (callback) { for (var i = 0; i < this._verticesHandlers.length; i++) { callback(this._verticesHandlers[i]); } }, // @method addHooks(): void // Add listener hooks to this handler addHooks: function () { this._initHandlers(); this._eachVertexHandler(function (handler) { handler.addHooks(); }); }, // @method removeHooks(): void // Remove listener hooks from this handler removeHooks: function () { this._eachVertexHandler(function (handler) { handler.removeHooks(); }); }, // @method updateMarkers(): void // Fire an update for each vertex handler updateMarkers: function () { this._eachVertexHandler(function (handler) { handler.updateMarkers(); }); }, _initHandlers: function () { this._verticesHandlers = []; for (var i = 0; i < this.latlngs.length; i++) { this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this._poly.options.poly)); } }, _updateLatLngs: function (e) { this.latlngs = [e.layer._latlngs]; if (e.layer._holes) { this.latlngs = this.latlngs.concat(e.layer._holes); } } }); /** * @class L.Edit.PolyVerticesEdit * @aka Edit.PolyVerticesEdit */ L.Edit.PolyVerticesEdit = L.Handler.extend({ options: { icon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: 'leaflet-div-icon leaflet-editing-icon' }), touchIcon: new L.DivIcon({ iconSize: new L.Point(20, 20), className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon' }), drawError: { color: '#b00b00', timeout: 1000 } }, // @method intialize(): void initialize: function (poly, latlngs, options) { // if touch, switch to touch icon if (L.Browser.touch) { this.options.icon = this.options.touchIcon; } this._poly = poly; if (options && options.drawError) { options.drawError = L.Util.extend({}, this.options.drawError, options.drawError); } this._latlngs = latlngs; L.setOptions(this, options); }, // Compatibility method to normalize Poly* objects // between 0.7.x and 1.0+ _defaultShape: function () { if (!L.Polyline._flat) { return this._latlngs; } return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0]; }, // @method addHooks(): void // Add listener hooks to this handler. addHooks: function () { var poly = this._poly; var path = poly._path; if (!(poly instanceof L.Polygon)) { poly.options.fill = false; if (poly.options.editing) { poly.options.editing.fill = false; } } if (path) { if (poly.options.editing && poly.options.editing.className) { if (poly.options.original.className) { poly.options.original.className.split(' ').forEach(function (className) { L.DomUtil.removeClass(path, className); }); } poly.options.editing.className.split(' ').forEach(function (className) { L.DomUtil.addClass(path, className); }); } } poly.setStyle(poly.options.editing); if (this._poly._map) { this._map = this._poly._map; // Set map if (!this._markerGroup) { this._initMarkers(); } this._poly._map.addLayer(this._markerGroup); } }, // @method removeHooks(): void // Remove listener hooks from this handler. removeHooks: function () { var poly = this._poly; var path = poly._path; if (path) { if (poly.options.editing && poly.options.editing.className) { poly.options.editing.className.split(' ').forEach(function (className) { L.DomUtil.removeClass(path, className); }); if (poly.options.original.className) { poly.options.original.className.split(' ').forEach(function (className) { L.DomUtil.addClass(path, className); }); } } } poly.setStyle(poly.options.original); if (poly._map) { poly._map.removeLayer(this._markerGroup); delete this._markerGroup; delete this._markers; } }, // @method updateMarkers(): void // Clear markers and update their location updateMarkers: function () { this._markerGroup.clearLayers(); this._initMarkers(); }, _initMarkers: function () { if (!this._markerGroup) { this._markerGroup = new L.LayerGroup(); } this._markers = []; var latlngs = this._defaultShape(), i, j, len, marker; for (i = 0, len = latlngs.length; i < len; i++) { marker = this._createMarker(latlngs[i], i); marker.on('click', this._onMarkerClick, this); marker.on('contextmenu', this._onContextMenu, this); this._markers.push(marker); } var markerLeft, markerRight; for (i = 0, j = len - 1; i < len; j = i++) { if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) { continue; } markerLeft = this._markers[j]; markerRight = this._markers[i]; this._createMiddleMarker(markerLeft, markerRight); this._updatePrevNext(markerLeft, markerRight); } }, _createMarker: function (latlng, index) { // Extending L.Marker in TouchEvents.js to include touch. var marker = new L.Marker.Touch(latlng, { draggable: true, icon: this.options.icon, }); marker._origLatLng = latlng; marker._index = index; marker .on('dragstart', this._onMarkerDragStart, this) .on('drag', this._onMarkerDrag, this) .on('dragend', this._fireEdit, this) .on('touchmove', this._onTouchMove, this) .on('touchend', this._fireEdit, this) .on('MSPointerMove', this._onTouchMove, this) .on('MSPointerUp', this._fireEdit, this); this._markerGroup.addLayer(marker); return marker; }, _onMarkerDragStart: function () { this._poly.fire('editstart'); }, _spliceLatLngs: function () { var latlngs = this._defaultShape(); var removed = [].splice.apply(latlngs, arguments); this._poly._convertLatLngs(latlngs, true); this._poly.redraw(); return removed; }, _removeMarker: function (marker) { var i = marker._index; this._markerGroup.removeLayer(marker); this._markers.splice(i, 1); this._spliceLatLngs(i, 1); this._updateIndexes(i, -1); marker .off('dragstart', this._onMarkerDragStart, this) .off('drag', this._onMarkerDrag, this) .off('dragend', this._fireEdit, this) .off('touchmove', this._onMarkerDrag, this) .off('touchend', this._fireEdit, this) .off('click', this._onMarkerClick, this) .off('MSPointerMove', this._onTouchMove, this) .off('MSPointerUp', this._fireEdit, this); }, _fireEdit: function () { this._poly.edited = true; this._poly.fire('edit'); this._poly._map.fire(L.Draw.Event.EDITVERTEX, {layers: this._markerGroup, poly: this._poly}); }, _onMarkerDrag: function (e) { var marker = e.target; var poly = this._poly; var oldOrigLatLng = L.LatLngUtil.cloneLatLng(marker._origLatLng); L.extend(marker._origLatLng, marker._latlng); if (poly.options.poly) { var tooltip = poly._map._editTooltip; // Access the tooltip // If we don't allow intersections and the polygon intersects if (!poly.options.poly.allowIntersection && poly.intersects()) { L.extend(marker._origLatLng, oldOrigLatLng); marker.setLatLng(oldOrigLatLng); var originalColor = poly.options.color; poly.setStyle({color: this.options.drawError.color}); if (tooltip) { tooltip.updateContent({ text: L.drawLocal.draw.handlers.polyline.error }); } // Reset everything back to normal after a second setTimeout(function () { poly.setStyle({color: originalColor}); if (tooltip) { tooltip.updateContent({ text: L.drawLocal.edit.handlers.edit.tooltip.text, subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext }); } }, 1000); } } if (marker._middleLeft) { marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); } if (marker._middleRight) { marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); } //refresh the bounds when draging this._poly._bounds._southWest = L.latLng(Infinity, Infinity); this._poly._bounds._northEast = L.latLng(-Infinity, -Infinity); var latlngs = this._poly.getLatLngs(); this._poly._convertLatLngs(latlngs, true); this._poly.redraw(); this._poly.fire('editdrag'); }, _onMarkerClick: function (e) { var minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3, marker = e.target; // If removing this point would create an invalid polyline/polygon don't remove if (this._defaultShape().length < minPoints) { return; } // remove the marker this._removeMarker(marker); // update prev/next links of adjacent markers this._updatePrevNext(marker._prev, marker._next); // remove ghost markers near the removed marker if (marker._middleLeft) { this._markerGroup.removeLayer(marker._middleLeft); } if (marker._middleRight) { this._markerGroup.removeLayer(marker._middleRight); } // create a ghost marker in place of the removed one if (marker._prev && marker._next) { this._createMiddleMarker(marker._prev, marker._next); } else if (!marker._prev) { marker._next._middleLeft = null; } else if (!marker._next) { marker._prev._middleRight = null; } this._fireEdit(); }, _onContextMenu: function (e) { var marker = e.target; var poly = this._poly; this._poly._map.fire(L.Draw.Event.MARKERCONTEXT, {marker: marker, layers: this._markerGroup, poly: this._poly}); L.DomEvent.stopPropagation; }, _onTouchMove: function (e) { var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]), latlng = this._map.layerPointToLatLng(layerPoint), marker = e.target; L.extend(marker._origLatLng, latlng); if (marker._middleLeft) { marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); } if (marker._middleRight) { marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); } this._poly.redraw(); this.updateMarkers(); }, _updateIndexes: function (index, delta) { this._markerGroup.eachLayer(function (marker) { if (marker._index > index) { marker._index += delta; } }); }, _createMiddleMarker: function (marker1, marker2) { var latlng = this._getMiddleLatLng(marker1, marker2), marker = this._createMarker(latlng), onClick, onDragStart, onDragEnd; marker.setOpacity(0.6); marker1._middleRight = marker2._middleLeft = marker; onDragStart = function () { marker.off('touchmove', onDragStart, this); var i = marker2._index; marker._index = i; marker .off('click', onClick, this) .on('click', this._onMarkerClick, this); latlng.lat = marker.getLatLng().lat; latlng.lng = marker.getLatLng().lng; this._spliceLatLngs(i, 0, latlng); this._markers.splice(i, 0, marker); marker.setOpacity(1); this._updateIndexes(i, 1); marker2._index++; this._updatePrevNext(marker1, marker); this._updatePrevNext(marker, marker2); this._poly.fire('editstart'); }; onDragEnd = function () { marker.off('dragstart', onDragStart, this); marker.off('dragend', onDragEnd, this); marker.off('touchmove', onDragStart, this); this._createMiddleMarker(marker1, marker); this._createMiddleMarker(marker, marker2); }; onClick = function () { onDragStart.call(this); onDragEnd.call(this); this._fireEdit(); }; marker .on('click', onClick, this) .on('dragstart', onDragStart, this) .on('dragend', onDragEnd, this) .on('touchmove', onDragStart, this); this._markerGroup.addLayer(marker); }, _updatePrevNext: function (marker1, marker2) { if (marker1) { marker1._next = marker2; } if (marker2) { marker2._prev = marker1; } }, _getMiddleLatLng: function (marker1, marker2) { var map = this._poly._map, p1 = map.project(marker1.getLatLng()), p2 = map.project(marker2.getLatLng()); return map.unproject(p1._add(p2)._divideBy(2)); } }); L.Polyline.addInitHook(function () { // Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit if (this.editing) { return; } if (L.Edit.Poly) { this.editing = new L.Edit.Poly(this); if (this.options.editable) { this.editing.enable(); } } this.on('add', function () { if (this.editing && this.editing.enabled()) { this.editing.addHooks(); } }); this.on('remove', function () { if (this.editing && this.editing.enabled()) { this.editing.removeHooks(); } }); });