'use strict'; (function (factory, window) {// 1.2 alpha /*globals define, module, require*/ // define an AMD module that relies on 'leaflet' if (typeof define === 'function' && define.amd) { define(['leaflet'], factory); // define a Common JS module that relies on 'leaflet' } else if (typeof exports === 'object') { module.exports = factory(require('leaflet')); } // attach your plugin to the global 'L' variable if(typeof window !== 'undefined' && window.L){ factory(window.L); } }(function (L) { // 🍂miniclass CancelableEvent (Event objects) // 🍂method cancel() // Cancel any subsequent action. // 🍂miniclass VertexEvent (Event objects) // 🍂property vertex: VertexMarker // The vertex that fires the event. // 🍂miniclass ShapeEvent (Event objects) // 🍂property shape: Array // The shape (LatLngs array) subject of the action. // 🍂miniclass CancelableVertexEvent (Event objects) // 🍂inherits VertexEvent // 🍂inherits CancelableEvent // 🍂miniclass CancelableShapeEvent (Event objects) // 🍂inherits ShapeEvent // 🍂inherits CancelableEvent // 🍂miniclass LayerEvent (Event objects) // 🍂property layer: object // The Layer (Marker, Polyline…) subject of the action. // 🍂namespace Editable; 🍂class Editable; 🍂aka L.Editable // Main edition handler. By default, it is attached to the map // as `map.editTools` property. // Leaflet.Editable is made to be fully extendable. You have three ways to customize // the behaviour: using options, listening to events, or extending. L.Editable = L.Evented.extend({ statics: { FORWARD: 1, BACKWARD: -1 }, options: { // You can pass them when creating a map using the `editOptions` key. // 🍂option zIndex: int = 1000 // The default zIndex of the editing tools. zIndex: 1000, // 🍂option polygonClass: class = L.Polygon // Class to be used when creating a new Polygon. polygonClass: L.Polygon, // 🍂option polylineClass: class = L.Polyline // Class to be used when creating a new Polyline. polylineClass: L.Polyline, // 🍂option markerClass: class = L.Marker // Class to be used when creating a new Marker. markerClass: L.Marker, // 🍂option rectangleClass: class = L.Rectangle // Class to be used when creating a new Rectangle. rectangleClass: L.Rectangle, // 🍂option circleClass: class = L.Circle // Class to be used when creating a new Circle. circleClass: L.Circle, // 🍂option drawingCSSClass: string = 'leaflet-editable-drawing' // CSS class to be added to the map container while drawing. drawingCSSClass: 'leaflet-editable-drawing', // 🍂option drawingCursor: const = 'crosshair' // Cursor mode set to the map while drawing. drawingCursor: 'crosshair', // 🍂option editLayer: Layer = new L.LayerGroup() // Layer used to store edit tools (vertex, line guide…). editLayer: undefined, // 🍂option featuresLayer: Layer = new L.LayerGroup() // Default layer used to store drawn features (Marker, Polyline…). featuresLayer: undefined, // 🍂option polylineEditorClass: class = PolylineEditor // Class to be used as Polyline editor. polylineEditorClass: undefined, // 🍂option polygonEditorClass: class = PolygonEditor // Class to be used as Polygon editor. polygonEditorClass: undefined, // 🍂option markerEditorClass: class = MarkerEditor // Class to be used as Marker editor. markerEditorClass: undefined, // 🍂option rectangleEditorClass: class = RectangleEditor // Class to be used as Rectangle editor. rectangleEditorClass: undefined, // 🍂option circleEditorClass: class = CircleEditor // Class to be used as Circle editor. circleEditorClass: undefined, // 🍂option lineGuideOptions: hash = {} // Options to be passed to the line guides. lineGuideOptions: {}, // 🍂option skipMiddleMarkers: boolean = false // Set this to true if you don't want middle markers. skipMiddleMarkers: false }, initialize: function (map, options) { L.setOptions(this, options); this._lastZIndex = this.options.zIndex; this.map = map; this.editLayer = this.createEditLayer(); this.featuresLayer = this.createFeaturesLayer(); this.forwardLineGuide = this.createLineGuide(); this.backwardLineGuide = this.createLineGuide(); }, fireAndForward: function (type, e) { e = e || {}; e.editTools = this; this.fire(type, e); this.map.fire(type, e); }, createLineGuide: function () { var options = L.extend({dashArray: '5,10', weight: 1, interactive: false}, this.options.lineGuideOptions); return L.polyline([], options); }, createVertexIcon: function (options) { return L.Browser.mobile && L.Browser.touch ? new L.Editable.TouchVertexIcon(options) : new L.Editable.VertexIcon(options); }, createEditLayer: function () { return this.options.editLayer || new L.LayerGroup().addTo(this.map); }, createFeaturesLayer: function () { return this.options.featuresLayer || new L.LayerGroup().addTo(this.map); }, moveForwardLineGuide: function (latlng) { if (this.forwardLineGuide._latlngs.length) { this.forwardLineGuide._latlngs[1] = latlng; this.forwardLineGuide._bounds.extend(latlng); this.forwardLineGuide.redraw(); } }, moveBackwardLineGuide: function (latlng) { if (this.backwardLineGuide._latlngs.length) { this.backwardLineGuide._latlngs[1] = latlng; this.backwardLineGuide._bounds.extend(latlng); this.backwardLineGuide.redraw(); } }, anchorForwardLineGuide: function (latlng) { this.forwardLineGuide._latlngs[0] = latlng; this.forwardLineGuide._bounds.extend(latlng); this.forwardLineGuide.redraw(); }, anchorBackwardLineGuide: function (latlng) { this.backwardLineGuide._latlngs[0] = latlng; this.backwardLineGuide._bounds.extend(latlng); this.backwardLineGuide.redraw(); }, attachForwardLineGuide: function () { this.editLayer.addLayer(this.forwardLineGuide); }, attachBackwardLineGuide: function () { this.editLayer.addLayer(this.backwardLineGuide); }, detachForwardLineGuide: function () { this.forwardLineGuide.setLatLngs([]); this.editLayer.removeLayer(this.forwardLineGuide); }, detachBackwardLineGuide: function () { this.backwardLineGuide.setLatLngs([]); this.editLayer.removeLayer(this.backwardLineGuide); }, blockEvents: function () { // Hack: force map not to listen to other layers events while drawing. if (!this._oldTargets) { this._oldTargets = this.map._targets; this.map._targets = {}; } }, unblockEvents: function () { if (this._oldTargets) { // Reset, but keep targets created while drawing. this.map._targets = L.extend(this.map._targets, this._oldTargets); delete this._oldTargets; } }, registerForDrawing: function (editor) { if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor); this.blockEvents(); editor.reset(); // Make sure editor tools still receive events. this._drawingEditor = editor; this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor); this.map.on('mousedown', this.onMousedown, this); this.map.on('mouseup', this.onMouseup, this); L.DomUtil.addClass(this.map._container, this.options.drawingCSSClass); this.defaultMapCursor = this.map._container.style.cursor; this.map._container.style.cursor = this.options.drawingCursor; }, unregisterForDrawing: function (editor) { this.unblockEvents(); L.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass); this.map._container.style.cursor = this.defaultMapCursor; editor = editor || this._drawingEditor; if (!editor) return; this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor); this.map.off('mousedown', this.onMousedown, this); this.map.off('mouseup', this.onMouseup, this); if (editor !== this._drawingEditor) return; delete this._drawingEditor; if (editor._drawing) editor.cancelDrawing(); }, onMousedown: function (e) { this._mouseDown = e; this._drawingEditor.onDrawingMouseDown(e); }, onMouseup: function (e) { if (this._mouseDown) { var editor = this._drawingEditor, mouseDown = this._mouseDown; this._mouseDown = null; editor.onDrawingMouseUp(e); if (this._drawingEditor !== editor) return; // onDrawingMouseUp may call unregisterFromDrawing. var origin = L.point(mouseDown.originalEvent.clientX, mouseDown.originalEvent.clientY); var distance = L.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(origin); if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) this._drawingEditor.onDrawingClick(e); } }, // 🍂section Public methods // You will generally access them by the `map.editTools` // instance: // // `map.editTools.startPolyline();` // 🍂method drawing(): boolean // Return true if any drawing action is ongoing. drawing: function () { return this._drawingEditor && this._drawingEditor.drawing(); }, // 🍂method stopDrawing() // When you need to stop any ongoing drawing, without needing to know which editor is active. stopDrawing: function () { this.unregisterForDrawing(); }, // 🍂method commitDrawing() // When you need to commit any ongoing drawing, without needing to know which editor is active. commitDrawing: function (e) { if (!this._drawingEditor) return; this._drawingEditor.commitDrawing(e); }, connectCreatedToMap: function (layer) { return this.featuresLayer.addLayer(layer); }, // 🍂method startPolyline(latlng: L.LatLng, options: hash): L.Polyline // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click. // If `options` is given, it will be passed to the Polyline class constructor. startPolyline: function (latlng, options) { var line = this.createPolyline([], options); line.enableEdit(this.map).newShape(latlng); return line; }, // 🍂method startPolygon(latlng: L.LatLng, options: hash): L.Polygon // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click. // If `options` is given, it will be passed to the Polygon class constructor. startPolygon: function (latlng, options) { var polygon = this.createPolygon([], options); polygon.enableEdit(this.map).newShape(latlng); return polygon; }, // 🍂method startMarker(latlng: L.LatLng, options: hash): L.Marker // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point. // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch). // If `options` is given, it will be passed to the Marker class constructor. startMarker: function (latlng, options) { latlng = latlng || this.map.getCenter().clone(); var marker = this.createMarker(latlng, options); marker.enableEdit(this.map).startDrawing(); return marker; }, // 🍂method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag. // If `options` is given, it will be passed to the Rectangle class constructor. startRectangle: function(latlng, options) { var corner = latlng || L.latLng([0, 0]); var bounds = new L.LatLngBounds(corner, corner); var rectangle = this.createRectangle(bounds, options); rectangle.enableEdit(this.map).startDrawing(); return rectangle; }, // 🍂method startCircle(latlng: L.LatLng, options: hash): L.Circle // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag. // If `options` is given, it will be passed to the Circle class constructor. startCircle: function (latlng, options) { latlng = latlng || this.map.getCenter().clone(); var circle = this.createCircle(latlng, options); circle.enableEdit(this.map).startDrawing(); return circle; }, startHole: function (editor, latlng) { editor.newHole(latlng); }, createLayer: function (klass, latlngs, options) { options = L.Util.extend({editOptions: {editTools: this}}, options); var layer = new klass(latlngs, options); // 🍂namespace Editable // 🍂event editable:created: LayerEvent // Fired when a new feature (Marker, Polyline…) is created. this.fireAndForward('editable:created', {layer: layer}); return layer; }, createPolyline: function (latlngs, options) { return this.createLayer(options && options.polylineClass || this.options.polylineClass, latlngs, options); }, createPolygon: function (latlngs, options) { return this.createLayer(options && options.polygonClass || this.options.polygonClass, latlngs, options); }, createMarker: function (latlng, options) { return this.createLayer(options && options.markerClass || this.options.markerClass, latlng, options); }, createRectangle: function (bounds, options) { return this.createLayer(options && options.rectangleClass || this.options.rectangleClass, bounds, options); }, createCircle: function (latlng, options) { return this.createLayer(options && options.circleClass || this.options.circleClass, latlng, options); } }); L.extend(L.Editable, { makeCancellable: function (e) { e.cancel = function () { e._cancelled = true; }; } }); // 🍂namespace Map; 🍂class Map // Leaflet.Editable add options and events to the `L.Map` object. // See `Editable` events for the list of events fired on the Map. // 🍂example // // ```js // var map = L.map('map', { // editable: true, // editOptions: { // … // } // }); // ``` // 🍂section Editable Map Options L.Map.mergeOptions({ // 🍂namespace Map // 🍂section Map Options // 🍂option editToolsClass: class = L.Editable // Class to be used as vertex, for path editing. editToolsClass: L.Editable, // 🍂option editable: boolean = false // Whether to create a L.Editable instance at map init. editable: false, // 🍂option editOptions: hash = {} // Options to pass to L.Editable when instantiating. editOptions: {} }); L.Map.addInitHook(function () { this.whenReady(function () { if (this.options.editable) { this.editTools = new this.options.editToolsClass(this, this.options.editOptions); } }); }); L.Editable.VertexIcon = L.DivIcon.extend({ options: { iconSize: new L.Point(8, 8) } }); L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({ options: { iconSize: new L.Point(20, 20) } }); // 🍂namespace Editable; 🍂class VertexMarker; Handler for dragging path vertices. L.Editable.VertexMarker = L.Marker.extend({ options: { draggable: true, className: 'leaflet-div-icon leaflet-vertex-icon' }, // 🍂section Public methods // The marker used to handle path vertex. You will usually interact with a `VertexMarker` // instance when listening for events like `editable:vertex:ctrlclick`. initialize: function (latlng, latlngs, editor, options) { // We don't use this._latlng, because on drag Leaflet replace it while // we want to keep reference. this.latlng = latlng; this.latlngs = latlngs; this.editor = editor; L.Marker.prototype.initialize.call(this, latlng, options); this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className}); this.latlng.__vertex = this; this.editor.editLayer.addLayer(this); this.setZIndexOffset(editor.tools._lastZIndex + 1); }, onAdd: function (map) { L.Marker.prototype.onAdd.call(this, map); this.on('drag', this.onDrag); this.on('dragstart', this.onDragStart); this.on('dragend', this.onDragEnd); this.on('mouseup', this.onMouseup); this.on('click', this.onClick); this.on('contextmenu', this.onContextMenu); this.on('mousedown touchstart', this.onMouseDown); this.on('mouseover', this.onMouseOver); this.on('mouseout', this.onMouseOut); this.addMiddleMarkers(); }, onRemove: function (map) { if (this.middleMarker) this.middleMarker.delete(); delete this.latlng.__vertex; this.off('drag', this.onDrag); this.off('dragstart', this.onDragStart); this.off('dragend', this.onDragEnd); this.off('mouseup', this.onMouseup); this.off('click', this.onClick); this.off('contextmenu', this.onContextMenu); this.off('mousedown touchstart', this.onMouseDown); this.off('mouseover', this.onMouseOver); this.off('mouseout', this.onMouseOut); L.Marker.prototype.onRemove.call(this, map); }, onDrag: function (e) { e.vertex = this; this.editor.onVertexMarkerDrag(e); var iconPos = L.DomUtil.getPosition(this._icon), latlng = this._map.layerPointToLatLng(iconPos); this.latlng.update(latlng); this._latlng = this.latlng; // Push back to Leaflet our reference. this.editor.refresh(); if (this.middleMarker) this.middleMarker.updateLatLng(); var next = this.getNext(); if (next && next.middleMarker) next.middleMarker.updateLatLng(); }, onDragStart: function (e) { e.vertex = this; this.editor.onVertexMarkerDragStart(e); }, onDragEnd: function (e) { e.vertex = this; this.editor.onVertexMarkerDragEnd(e); }, onClick: function (e) { e.vertex = this; this.editor.onVertexMarkerClick(e); }, onMouseup: function (e) { L.DomEvent.stop(e); e.vertex = this; this.editor.map.fire('mouseup', e); }, onContextMenu: function (e) { e.vertex = this; this.editor.onVertexMarkerContextMenu(e); }, onMouseDown: function (e) { e.vertex = this; this.editor.onVertexMarkerMouseDown(e); }, onMouseOver: function (e) { e.vertex = this; this.editor.onVertexMarkerMouseOver(e); }, onMouseOut: function (e) { e.vertex = this; this.editor.onVertexMarkerMouseOut(e); }, // 🍂method delete() // Delete a vertex and the related LatLng. delete: function () { var next = this.getNext(); // Compute before changing latlng this.latlngs.splice(this.getIndex(), 1); this.editor.editLayer.removeLayer(this); this.editor.onVertexDeleted({latlng: this.latlng, vertex: this}); if (!this.latlngs.length) this.editor.deleteShape(this.latlngs); if (next) next.resetMiddleMarker(); this.editor.refresh(); }, // 🍂method getIndex(): int // Get the index of the current vertex among others of the same LatLngs group. getIndex: function () { return this.latlngs.indexOf(this.latlng); }, // 🍂method getLastIndex(): int // Get last vertex index of the LatLngs group of the current vertex. getLastIndex: function () { return this.latlngs.length - 1; }, // 🍂method getPrevious(): VertexMarker // Get the previous VertexMarker in the same LatLngs group. getPrevious: function () { if (this.latlngs.length < 2) return; var index = this.getIndex(), previousIndex = index - 1; if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex(); var previous = this.latlngs[previousIndex]; if (previous) return previous.__vertex; }, // 🍂method getNext(): VertexMarker // Get the next VertexMarker in the same LatLngs group. getNext: function () { if (this.latlngs.length < 2) return; var index = this.getIndex(), nextIndex = index + 1; if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0; var next = this.latlngs[nextIndex]; if (next) return next.__vertex; }, addMiddleMarker: function (previous) { if (!this.editor.hasMiddleMarkers()) return; previous = previous || this.getPrevious(); if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor); }, addMiddleMarkers: function () { if (!this.editor.hasMiddleMarkers()) return; var previous = this.getPrevious(); if (previous) this.addMiddleMarker(previous); var next = this.getNext(); if (next) next.resetMiddleMarker(); }, resetMiddleMarker: function () { if (this.middleMarker) this.middleMarker.delete(); this.addMiddleMarker(); }, // 🍂method split() // Split the vertex LatLngs group at its index, if possible. split: function () { if (!this.editor.splitShape) return; // Only for PolylineEditor this.editor.splitShape(this.latlngs, this.getIndex()); }, // 🍂method continue() // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline. continue: function () { if (!this.editor.continueBackward) return; // Only for PolylineEditor var index = this.getIndex(); if (index === 0) this.editor.continueBackward(this.latlngs); else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs); } }); L.Editable.mergeOptions({ // 🍂namespace Editable // 🍂option vertexMarkerClass: class = VertexMarker // Class to be used as vertex, for path editing. vertexMarkerClass: L.Editable.VertexMarker }); L.Editable.MiddleMarker = L.Marker.extend({ options: { opacity: 0.5, className: 'leaflet-div-icon leaflet-middle-icon', draggable: true }, initialize: function (left, right, latlngs, editor, options) { this.left = left; this.right = right; this.editor = editor; this.latlngs = latlngs; L.Marker.prototype.initialize.call(this, this.computeLatLng(), options); this._opacity = this.options.opacity; this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className}); this.editor.editLayer.addLayer(this); this.setVisibility(); }, setVisibility: function () { var leftPoint = this._map.latLngToContainerPoint(this.left.latlng), rightPoint = this._map.latLngToContainerPoint(this.right.latlng), size = L.point(this.options.icon.options.iconSize); if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide(); else this.show(); }, show: function () { this.setOpacity(this._opacity); }, hide: function () { this.setOpacity(0); }, updateLatLng: function () { this.setLatLng(this.computeLatLng()); this.setVisibility(); }, computeLatLng: function () { var leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng), rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng), y = (leftPoint.y + rightPoint.y) / 2, x = (leftPoint.x + rightPoint.x) / 2; return this.editor.map.containerPointToLatLng([x, y]); }, onAdd: function (map) { L.Marker.prototype.onAdd.call(this, map); L.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this); map.on('zoomend', this.setVisibility, this); }, onRemove: function (map) { delete this.right.middleMarker; L.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this); map.off('zoomend', this.setVisibility, this); L.Marker.prototype.onRemove.call(this, map); }, onMouseDown: function (e) { var iconPos = L.DomUtil.getPosition(this._icon), latlng = this.editor.map.layerPointToLatLng(iconPos); e = { originalEvent: e, latlng: latlng }; if (this.options.opacity === 0) return; L.Editable.makeCancellable(e); this.editor.onMiddleMarkerMouseDown(e); if (e._cancelled) return; this.latlngs.splice(this.index(), 0, e.latlng); this.editor.refresh(); var icon = this._icon; var marker = this.editor.addVertexMarker(e.latlng, this.latlngs); this.editor.onNewVertex(marker); /* Hack to workaround browser not firing touchend when element is no more on DOM */ var parent = marker._icon.parentNode; parent.removeChild(marker._icon); marker._icon = icon; parent.appendChild(marker._icon); marker._initIcon(); marker._initInteraction(); marker.setOpacity(1); /* End hack */ // Transfer ongoing dragging to real marker L.Draggable._dragging = false; marker.dragging._draggable._onDown(e.originalEvent); this.delete(); }, delete: function () { this.editor.editLayer.removeLayer(this); }, index: function () { return this.latlngs.indexOf(this.right.latlng); } }); L.Editable.mergeOptions({ // 🍂namespace Editable // 🍂option middleMarkerClass: class = VertexMarker // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path. middleMarkerClass: L.Editable.MiddleMarker }); // 🍂namespace Editable; 🍂class BaseEditor; 🍂aka L.Editable.BaseEditor // When editing a feature (Marker, Polyline…), an editor is attached to it. This // editor basically knows how to handle the edition. L.Editable.BaseEditor = L.Handler.extend({ initialize: function (map, feature, options) { L.setOptions(this, options); this.map = map; this.feature = feature; this.feature.editor = this; this.editLayer = new L.LayerGroup(); this.tools = this.options.editTools || map.editTools; }, // 🍂method enable(): this // Set up the drawing tools for the feature to be editable. addHooks: function () { if (this.isConnected()) this.onFeatureAdd(); else this.feature.once('add', this.onFeatureAdd, this); this.onEnable(); this.feature.on(this._getEvents(), this); }, // 🍂method disable(): this // Remove the drawing tools for the feature. removeHooks: function () { this.feature.off(this._getEvents(), this); if (this.feature.dragging) this.feature.dragging.disable(); this.editLayer.clearLayers(); this.tools.editLayer.removeLayer(this.editLayer); this.onDisable(); if (this._drawing) this.cancelDrawing(); }, // 🍂method drawing(): boolean // Return true if any drawing action is ongoing with this editor. drawing: function () { return !!this._drawing; }, reset: function () {}, onFeatureAdd: function () { this.tools.editLayer.addLayer(this.editLayer); if (this.feature.dragging) this.feature.dragging.enable(); }, hasMiddleMarkers: function () { return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers; }, fireAndForward: function (type, e) { e = e || {}; e.layer = this.feature; this.feature.fire(type, e); this.tools.fireAndForward(type, e); }, onEnable: function () { // 🍂namespace Editable // 🍂event editable:enable: Event // Fired when an existing feature is ready to be edited. this.fireAndForward('editable:enable'); }, onDisable: function () { // 🍂namespace Editable // 🍂event editable:disable: Event // Fired when an existing feature is not ready anymore to be edited. this.fireAndForward('editable:disable'); }, onEditing: function () { // 🍂namespace Editable // 🍂event editable:editing: Event // Fired as soon as any change is made to the feature geometry. this.fireAndForward('editable:editing'); }, onStartDrawing: function () { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:start: Event // Fired when a feature is to be drawn. this.fireAndForward('editable:drawing:start'); }, onEndDrawing: function () { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:end: Event // Fired when a feature is not drawn anymore. this.fireAndForward('editable:drawing:end'); }, onCancelDrawing: function () { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:cancel: Event // Fired when user cancel drawing while a feature is being drawn. this.fireAndForward('editable:drawing:cancel'); }, onCommitDrawing: function (e) { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:commit: Event // Fired when user finish drawing a feature. this.fireAndForward('editable:drawing:commit', e); }, onDrawingMouseDown: function (e) { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:mousedown: Event // Fired when user `mousedown` while drawing. this.fireAndForward('editable:drawing:mousedown', e); }, onDrawingMouseUp: function (e) { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:mouseup: Event // Fired when user `mouseup` while drawing. this.fireAndForward('editable:drawing:mouseup', e); }, startDrawing: function () { if (!this._drawing) this._drawing = L.Editable.FORWARD; this.tools.registerForDrawing(this); this.onStartDrawing(); }, commitDrawing: function (e) { this.onCommitDrawing(e); this.endDrawing(); }, cancelDrawing: function () { // If called during a vertex drag, the vertex will be removed before // the mouseup fires on it. This is a workaround. Maybe better fix is // To have L.Draggable reset it's status on disable (Leaflet side). L.Draggable._dragging = false; this.onCancelDrawing(); this.endDrawing(); }, endDrawing: function () { this._drawing = false; this.tools.unregisterForDrawing(this); this.onEndDrawing(); }, onDrawingClick: function (e) { if (!this.drawing()) return; L.Editable.makeCancellable(e); // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:click: CancelableEvent // Fired when user `click` while drawing, before any internal action is being processed. this.fireAndForward('editable:drawing:click', e); if (e._cancelled) return; if (!this.isConnected()) this.connect(e); this.processDrawingClick(e); }, isConnected: function () { return this.map.hasLayer(this.feature); }, connect: function () { this.tools.connectCreatedToMap(this.feature); this.tools.editLayer.addLayer(this.editLayer); }, onMove: function (e) { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:move: Event // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex. this.fireAndForward('editable:drawing:move', e); }, onDrawingMouseMove: function (e) { this.onMove(e); }, _getEvents: function () { return { dragstart: this.onDragStart, drag: this.onDrag, dragend: this.onDragEnd, remove: this.disable }; }, onDragStart: function (e) { this.onEditing(); // 🍂namespace Editable // 🍂event editable:dragstart: Event // Fired before a path feature is dragged. this.fireAndForward('editable:dragstart', e); }, onDrag: function (e) { this.onMove(e); // 🍂namespace Editable // 🍂event editable:drag: Event // Fired when a path feature is being dragged. this.fireAndForward('editable:drag', e); }, onDragEnd: function (e) { // 🍂namespace Editable // 🍂event editable:dragend: Event // Fired after a path feature has been dragged. this.fireAndForward('editable:dragend', e); } }); // 🍂namespace Editable; 🍂class MarkerEditor; 🍂aka L.Editable.MarkerEditor // 🍂inherits BaseEditor // Editor for Marker. L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({ onDrawingMouseMove: function (e) { L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e); if (this._drawing) this.feature.setLatLng(e.latlng); }, processDrawingClick: function (e) { // 🍂namespace Editable // 🍂section Drawing events // 🍂event editable:drawing:clicked: Event // Fired when user `click` while drawing, after all internal actions. this.fireAndForward('editable:drawing:clicked', e); this.commitDrawing(e); }, connect: function (e) { // On touch, the latlng has not been updated because there is // no mousemove. if (e) this.feature._latlng = e.latlng; L.Editable.BaseEditor.prototype.connect.call(this, e); } }); // 🍂namespace Editable; 🍂class PathEditor; 🍂aka L.Editable.PathEditor // 🍂inherits BaseEditor // Base class for all path editors. L.Editable.PathEditor = L.Editable.BaseEditor.extend({ CLOSED: false, MIN_VERTEX: 2, addHooks: function () { L.Editable.BaseEditor.prototype.addHooks.call(this); if (this.feature) this.initVertexMarkers(); return this; }, initVertexMarkers: function (latlngs) { if (!this.enabled()) return; latlngs = latlngs || this.getLatLngs(); if (isFlat(latlngs)) this.addVertexMarkers(latlngs); else for (var i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]); }, getLatLngs: function () { return this.feature.getLatLngs(); }, // 🍂method reset() // Rebuild edit elements (Vertex, MiddleMarker, etc.). reset: function () { this.editLayer.clearLayers(); this.initVertexMarkers(); }, addVertexMarker: function (latlng, latlngs) { return new this.tools.options.vertexMarkerClass(latlng, latlngs, this); }, onNewVertex: function (vertex) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:new: VertexEvent // Fired when a new vertex is created. this.fireAndForward('editable:vertex:new', {latlng: vertex.latlng, vertex: vertex}); }, addVertexMarkers: function (latlngs) { for (var i = 0; i < latlngs.length; i++) { this.addVertexMarker(latlngs[i], latlngs); } }, refreshVertexMarkers: function (latlngs) { latlngs = latlngs || this.getDefaultLatLngs(); for (var i = 0; i < latlngs.length; i++) { latlngs[i].__vertex.update(); } }, addMiddleMarker: function (left, right, latlngs) { return new this.tools.options.middleMarkerClass(left, right, latlngs, this); }, onVertexMarkerClick: function (e) { L.Editable.makeCancellable(e); // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:click: CancelableVertexEvent // Fired when a `click` is issued on a vertex, before any internal action is being processed. this.fireAndForward('editable:vertex:click', e); if (e._cancelled) return; if (this.tools.drawing() && this.tools._drawingEditor !== this) return; var index = e.vertex.getIndex(), commit; if (e.originalEvent.ctrlKey) { this.onVertexMarkerCtrlClick(e); } else if (e.originalEvent.altKey) { this.onVertexMarkerAltClick(e); } else if (e.originalEvent.shiftKey) { this.onVertexMarkerShiftClick(e); } else if (e.originalEvent.metaKey) { this.onVertexMarkerMetaKeyClick(e); } else if (index === e.vertex.getLastIndex() && this._drawing === L.Editable.FORWARD) { if (index >= this.MIN_VERTEX - 1) commit = true; } else if (index === 0 && this._drawing === L.Editable.BACKWARD && this._drawnLatLngs.length >= this.MIN_VERTEX) { commit = true; } else if (index === 0 && this._drawing === L.Editable.FORWARD && this._drawnLatLngs.length >= this.MIN_VERTEX && this.CLOSED) { commit = true; // Allow to close on first point also for polygons } else { this.onVertexRawMarkerClick(e); } // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:clicked: VertexEvent // Fired when a `click` is issued on a vertex, after all internal actions. this.fireAndForward('editable:vertex:clicked', e); if (commit) this.commitDrawing(e); }, onVertexRawMarkerClick: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:rawclick: CancelableVertexEvent // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode. this.fireAndForward('editable:vertex:rawclick', e); if (e._cancelled) return; if (!this.vertexCanBeDeleted(e.vertex)) return; e.vertex.delete(); }, vertexCanBeDeleted: function (vertex) { return vertex.latlngs.length > this.MIN_VERTEX; }, onVertexDeleted: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:deleted: VertexEvent // Fired after a vertex has been deleted by user. this.fireAndForward('editable:vertex:deleted', e); }, onVertexMarkerCtrlClick: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:ctrlclick: VertexEvent // Fired when a `click` with `ctrlKey` is issued on a vertex. this.fireAndForward('editable:vertex:ctrlclick', e); }, onVertexMarkerShiftClick: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:shiftclick: VertexEvent // Fired when a `click` with `shiftKey` is issued on a vertex. this.fireAndForward('editable:vertex:shiftclick', e); }, onVertexMarkerMetaKeyClick: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:metakeyclick: VertexEvent // Fired when a `click` with `metaKey` is issued on a vertex. this.fireAndForward('editable:vertex:metakeyclick', e); }, onVertexMarkerAltClick: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:altclick: VertexEvent // Fired when a `click` with `altKey` is issued on a vertex. this.fireAndForward('editable:vertex:altclick', e); }, onVertexMarkerContextMenu: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:contextmenu: VertexEvent // Fired when a `contextmenu` is issued on a vertex. this.fireAndForward('editable:vertex:contextmenu', e); }, onVertexMarkerMouseDown: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:mousedown: VertexEvent // Fired when user `mousedown` a vertex. this.fireAndForward('editable:vertex:mousedown', e); }, onVertexMarkerMouseOver: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:mouseover: VertexEvent // Fired when a user's mouse enters the vertex this.fireAndForward('editable:vertex:mouseover', e); }, onVertexMarkerMouseOut: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:mouseout: VertexEvent // Fired when a user's mouse leaves the vertex this.fireAndForward('editable:vertex:mouseout', e); }, onMiddleMarkerMouseDown: function (e) { // 🍂namespace Editable // 🍂section MiddleMarker events // 🍂event editable:middlemarker:mousedown: VertexEvent // Fired when user `mousedown` a middle marker. this.fireAndForward('editable:middlemarker:mousedown', e); }, onVertexMarkerDrag: function (e) { this.onMove(e); if (this.feature._bounds) this.extendBounds(e); // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:drag: VertexEvent // Fired when a vertex is dragged by user. this.fireAndForward('editable:vertex:drag', e); }, onVertexMarkerDragStart: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:dragstart: VertexEvent // Fired before a vertex is dragged by user. this.fireAndForward('editable:vertex:dragstart', e); }, onVertexMarkerDragEnd: function (e) { // 🍂namespace Editable // 🍂section Vertex events // 🍂event editable:vertex:dragend: VertexEvent // Fired after a vertex is dragged by user. this.fireAndForward('editable:vertex:dragend', e); }, setDrawnLatLngs: function (latlngs) { this._drawnLatLngs = latlngs || this.getDefaultLatLngs(); }, startDrawing: function () { if (!this._drawnLatLngs) this.setDrawnLatLngs(); L.Editable.BaseEditor.prototype.startDrawing.call(this); }, startDrawingForward: function () { this.startDrawing(); }, endDrawing: function () { this.tools.detachForwardLineGuide(); this.tools.detachBackwardLineGuide(); if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) this.deleteShape(this._drawnLatLngs); L.Editable.BaseEditor.prototype.endDrawing.call(this); delete this._drawnLatLngs; }, addLatLng: function (latlng) { if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng); else this._drawnLatLngs.unshift(latlng); this.feature._bounds.extend(latlng); var vertex = this.addVertexMarker(latlng, this._drawnLatLngs); this.onNewVertex(vertex); this.refresh(); }, newPointForward: function (latlng) { this.addLatLng(latlng); this.tools.attachForwardLineGuide(); this.tools.anchorForwardLineGuide(latlng); }, newPointBackward: function (latlng) { this.addLatLng(latlng); this.tools.anchorBackwardLineGuide(latlng); }, // 🍂namespace PathEditor // 🍂method push() // Programmatically add a point while drawing. push: function (latlng) { if (!latlng) return console.error('L.Editable.PathEditor.push expect a valid latlng as parameter'); if (this._drawing === L.Editable.FORWARD) this.newPointForward(latlng); else this.newPointBackward(latlng); }, removeLatLng: function (latlng) { latlng.__vertex.delete(); this.refresh(); }, // 🍂method pop(): L.LatLng or null // Programmatically remove last point (if any) while drawing. pop: function () { if (this._drawnLatLngs.length <= 1) return; var latlng; if (this._drawing === L.Editable.FORWARD) latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1]; else latlng = this._drawnLatLngs[0]; this.removeLatLng(latlng); if (this._drawing === L.Editable.FORWARD) this.tools.anchorForwardLineGuide(this._drawnLatLngs[this._drawnLatLngs.length - 1]); else this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]); return latlng; }, processDrawingClick: function (e) { if (e.vertex && e.vertex.editor === this) return; if (this._drawing === L.Editable.FORWARD) this.newPointForward(e.latlng); else this.newPointBackward(e.latlng); this.fireAndForward('editable:drawing:clicked', e); }, onDrawingMouseMove: function (e) { L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e); if (this._drawing) { this.tools.moveForwardLineGuide(e.latlng); this.tools.moveBackwardLineGuide(e.latlng); } }, refresh: function () { this.feature.redraw(); this.onEditing(); }, // 🍂namespace PathEditor // 🍂method newShape(latlng?: L.LatLng) // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it; // if optional `latlng` is given, start a path at this point. newShape: function (latlng) { var shape = this.addNewEmptyShape(); if (!shape) return; this.setDrawnLatLngs(shape[0] || shape); // Polygon or polyline this.startDrawingForward(); // 🍂namespace Editable // 🍂section Shape events // 🍂event editable:shape:new: ShapeEvent // Fired when a new shape is created in a multi (Polygon or Polyline). this.fireAndForward('editable:shape:new', {shape: shape}); if (latlng) this.newPointForward(latlng); }, deleteShape: function (shape, latlngs) { var e = {shape: shape}; L.Editable.makeCancellable(e); // 🍂namespace Editable // 🍂section Shape events // 🍂event editable:shape:delete: CancelableShapeEvent // Fired before a new shape is deleted in a multi (Polygon or Polyline). this.fireAndForward('editable:shape:delete', e); if (e._cancelled) return; shape = this._deleteShape(shape, latlngs); if (this.ensureNotFlat) this.ensureNotFlat(); // Polygon. this.feature.setLatLngs(this.getLatLngs()); // Force bounds reset. this.refresh(); this.reset(); // 🍂namespace Editable // 🍂section Shape events // 🍂event editable:shape:deleted: ShapeEvent // Fired after a new shape is deleted in a multi (Polygon or Polyline). this.fireAndForward('editable:shape:deleted', {shape: shape}); return shape; }, _deleteShape: function (shape, latlngs) { latlngs = latlngs || this.getLatLngs(); if (!latlngs.length) return; var self = this, inplaceDelete = function (latlngs, shape) { // Called when deleting a flat latlngs shape = latlngs.splice(0, Number.MAX_VALUE); return shape; }, spliceDelete = function (latlngs, shape) { // Called when removing a latlngs inside an array latlngs.splice(latlngs.indexOf(shape), 1); if (!latlngs.length) self._deleteShape(latlngs); return shape; }; if (latlngs === shape) return inplaceDelete(latlngs, shape); for (var i = 0; i < latlngs.length; i++) { if (latlngs[i] === shape) return spliceDelete(latlngs, shape); else if (latlngs[i].indexOf(shape) !== -1) return spliceDelete(latlngs[i], shape); } }, // 🍂namespace PathEditor // 🍂method deleteShapeAt(latlng: L.LatLng): Array // Remove a path shape at the given `latlng`. deleteShapeAt: function (latlng) { var shape = this.feature.shapeAt(latlng); if (shape) return this.deleteShape(shape); }, // 🍂method appendShape(shape: Array) // Append a new shape to the Polygon or Polyline. appendShape: function (shape) { this.insertShape(shape); }, // 🍂method prependShape(shape: Array) // Prepend a new shape to the Polygon or Polyline. prependShape: function (shape) { this.insertShape(shape, 0); }, // 🍂method insertShape(shape: Array, index: int) // Insert a new shape to the Polygon or Polyline at given index (default is to append). insertShape: function (shape, index) { this.ensureMulti(); shape = this.formatShape(shape); if (typeof index === 'undefined') index = this.feature._latlngs.length; this.feature._latlngs.splice(index, 0, shape); this.feature.redraw(); if (this._enabled) this.reset(); }, extendBounds: function (e) { this.feature._bounds.extend(e.vertex.latlng); }, onDragStart: function (e) { this.editLayer.clearLayers(); L.Editable.BaseEditor.prototype.onDragStart.call(this, e); }, onDragEnd: function (e) { this.initVertexMarkers(); L.Editable.BaseEditor.prototype.onDragEnd.call(this, e); } }); // 🍂namespace Editable; 🍂class PolylineEditor; 🍂aka L.Editable.PolylineEditor // 🍂inherits PathEditor L.Editable.PolylineEditor = L.Editable.PathEditor.extend({ startDrawingBackward: function () { this._drawing = L.Editable.BACKWARD; this.startDrawing(); }, // 🍂method continueBackward(latlngs?: Array) // Set up drawing tools to continue the line backward. continueBackward: function (latlngs) { if (this.drawing()) return; latlngs = latlngs || this.getDefaultLatLngs(); this.setDrawnLatLngs(latlngs); if (latlngs.length > 0) { this.tools.attachBackwardLineGuide(); this.tools.anchorBackwardLineGuide(latlngs[0]); } this.startDrawingBackward(); }, // 🍂method continueForward(latlngs?: Array) // Set up drawing tools to continue the line forward. continueForward: function (latlngs) { if (this.drawing()) return; latlngs = latlngs || this.getDefaultLatLngs(); this.setDrawnLatLngs(latlngs); if (latlngs.length > 0) { this.tools.attachForwardLineGuide(); this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]); } this.startDrawingForward(); }, getDefaultLatLngs: function (latlngs) { latlngs = latlngs || this.feature._latlngs; if (!latlngs.length || latlngs[0] instanceof L.LatLng) return latlngs; else return this.getDefaultLatLngs(latlngs[0]); }, ensureMulti: function () { if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) { this.feature._latlngs = [this.feature._latlngs]; } }, addNewEmptyShape: function () { if (this.feature._latlngs.length) { var shape = []; this.appendShape(shape); return shape; } else { return this.feature._latlngs; } }, formatShape: function (shape) { if (isFlat(shape)) return shape; else if (shape[0]) return this.formatShape(shape[0]); }, // 🍂method splitShape(latlngs?: Array, index: int) // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`. splitShape: function (shape, index) { if (!index || index >= shape.length - 1) return; this.ensureMulti(); var shapeIndex = this.feature._latlngs.indexOf(shape); if (shapeIndex === -1) return; var first = shape.slice(0, index + 1), second = shape.slice(index); // We deal with reference, we don't want twice the same latlng around. second[0] = L.latLng(second[0].lat, second[0].lng, second[0].alt); this.feature._latlngs.splice(shapeIndex, 1, first, second); this.refresh(); this.reset(); } }); // 🍂namespace Editable; 🍂class PolygonEditor; 🍂aka L.Editable.PolygonEditor // 🍂inherits PathEditor L.Editable.PolygonEditor = L.Editable.PathEditor.extend({ CLOSED: true, MIN_VERTEX: 3, newPointForward: function (latlng) { L.Editable.PathEditor.prototype.newPointForward.call(this, latlng); if (!this.tools.backwardLineGuide._latlngs.length) this.tools.anchorBackwardLineGuide(latlng); if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide(); }, addNewEmptyHole: function (latlng) { this.ensureNotFlat(); var latlngs = this.feature.shapeAt(latlng); if (!latlngs) return; var holes = []; latlngs.push(holes); return holes; }, // 🍂method newHole(latlng?: L.LatLng, index: int) // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created. newHole: function (latlng) { var holes = this.addNewEmptyHole(latlng); if (!holes) return; this.setDrawnLatLngs(holes); this.startDrawingForward(); if (latlng) this.newPointForward(latlng); }, addNewEmptyShape: function () { if (this.feature._latlngs.length && this.feature._latlngs[0].length) { var shape = []; this.appendShape(shape); return shape; } else { return this.feature._latlngs; } }, ensureMulti: function () { if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) { this.feature._latlngs = [this.feature._latlngs]; } }, ensureNotFlat: function () { if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs]; }, vertexCanBeDeleted: function (vertex) { var parent = this.feature.parentShape(vertex.latlngs), idx = L.Util.indexOf(parent, vertex.latlngs); if (idx > 0) return true; // Holes can be totally deleted without removing the layer itself. return L.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex); }, getDefaultLatLngs: function () { if (!this.feature._latlngs.length) this.feature._latlngs.push([]); return this.feature._latlngs[0]; }, formatShape: function (shape) { // [[1, 2], [3, 4]] => must be nested // [] => must be nested // [[]] => is already nested if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape]; else return shape; } }); // 🍂namespace Editable; 🍂class RectangleEditor; 🍂aka L.Editable.RectangleEditor // 🍂inherits PathEditor L.Editable.RectangleEditor = L.Editable.PathEditor.extend({ CLOSED: true, MIN_VERTEX: 4, options: { skipMiddleMarkers: true }, extendBounds: function (e) { var index = e.vertex.getIndex(), next = e.vertex.getNext(), previous = e.vertex.getPrevious(), oppositeIndex = (index + 2) % 4, opposite = e.vertex.latlngs[oppositeIndex], bounds = new L.LatLngBounds(e.latlng, opposite); // Update latlngs by hand to preserve order. previous.latlng.update([e.latlng.lat, opposite.lng]); next.latlng.update([opposite.lat, e.latlng.lng]); this.updateBounds(bounds); this.refreshVertexMarkers(); }, onDrawingMouseDown: function (e) { L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e); this.connect(); var latlngs = this.getDefaultLatLngs(); // L.Polygon._convertLatLngs removes last latlng if it equals first point, // which is the case here as all latlngs are [0, 0] if (latlngs.length === 3) latlngs.push(e.latlng); var bounds = new L.LatLngBounds(e.latlng, e.latlng); this.updateBounds(bounds); this.updateLatLngs(bounds); this.refresh(); this.reset(); // Stop dragging map. // L.Draggable has two workflows: // - mousedown => mousemove => mouseup // - touchstart => touchmove => touchend // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only // can deal with mousedown, but then when in a touch device, we are dealing with // simulated events (actually simulated by L.Map.Tap), which are no more taken // into account by L.Draggable. // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103 e.originalEvent._simulated = false; this.map.dragging._draggable._onUp(e.originalEvent); // Now transfer ongoing drag action to the bottom right corner. // Should we refine which corner will handle the drag according to // drag direction? latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent); }, onDrawingMouseUp: function (e) { this.commitDrawing(e); e.originalEvent._simulated = false; L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e); }, onDrawingMouseMove: function (e) { e.originalEvent._simulated = false; L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e); }, getDefaultLatLngs: function (latlngs) { return latlngs || this.feature._latlngs[0]; }, updateBounds: function (bounds) { this.feature._bounds = bounds; }, updateLatLngs: function (bounds) { var latlngs = this.getDefaultLatLngs(), newLatlngs = this.feature._boundsToLatLngs(bounds); // Keep references. for (var i = 0; i < latlngs.length; i++) { latlngs[i].update(newLatlngs[i]); } } }); // 🍂namespace Editable; 🍂class CircleEditor; 🍂aka L.Editable.CircleEditor // 🍂inherits PathEditor L.Editable.CircleEditor = L.Editable.PathEditor.extend({ MIN_VERTEX: 2, options: { skipMiddleMarkers: true }, initialize: function (map, feature, options) { L.Editable.PathEditor.prototype.initialize.call(this, map, feature, options); this._resizeLatLng = this.computeResizeLatLng(); }, computeResizeLatLng: function () { // While circle is not added to the map, _radius is not set. var delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4), point = this.map.project(this.feature._latlng); return this.map.unproject([point.x + delta, point.y - delta]); }, updateResizeLatLng: function () { this._resizeLatLng.update(this.computeResizeLatLng()); this._resizeLatLng.__vertex.update(); }, getLatLngs: function () { return [this.feature._latlng, this._resizeLatLng]; }, getDefaultLatLngs: function () { return this.getLatLngs(); }, onVertexMarkerDrag: function (e) { if (e.vertex.getIndex() === 1) this.resize(e); else this.updateResizeLatLng(e); L.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e); }, resize: function (e) { var radius = this.feature._latlng.distanceTo(e.latlng); this.feature.setRadius(radius); }, onDrawingMouseDown: function (e) { L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e); this._resizeLatLng.update(e.latlng); this.feature._latlng.update(e.latlng); this.connect(); // Stop dragging map. e.originalEvent._simulated = false; this.map.dragging._draggable._onUp(e.originalEvent); // Now transfer ongoing drag action to the radius handler. this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent); }, onDrawingMouseUp: function (e) { this.commitDrawing(e); e.originalEvent._simulated = false; L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e); }, onDrawingMouseMove: function (e) { e.originalEvent._simulated = false; L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e); }, onDrag: function (e) { L.Editable.PathEditor.prototype.onDrag.call(this, e); this.feature.dragging.updateLatLng(this._resizeLatLng); } }); // 🍂namespace Editable; 🍂class EditableMixin // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle` // and `L.Marker`. It adds some methods to them. // *When editing is enabled, the editor is accessible on the instance with the // `editor` property.* var EditableMixin = { createEditor: function (map) { map = map || this._map; var tools = (this.options.editOptions || {}).editTools || map.editTools; if (!tools) throw Error('Unable to detect Editable instance.'); var Klass = this.options.editorClass || this.getEditorClass(tools); return new Klass(map, this, this.options.editOptions); }, // 🍂method enableEdit(map?: L.Map): this.editor // Enable editing, by creating an editor if not existing, and then calling `enable` on it. enableEdit: function (map) { if (!this.editor) this.createEditor(map); this.editor.enable(); return this.editor; }, // 🍂method editEnabled(): boolean // Return true if current instance has an editor attached, and this editor is enabled. editEnabled: function () { return this.editor && this.editor.enabled(); }, // 🍂method disableEdit() // Disable editing, also remove the editor property reference. disableEdit: function () { if (this.editor) { this.editor.disable(); delete this.editor; } }, // 🍂method toggleEdit() // Enable or disable editing, according to current status. toggleEdit: function () { if (this.editEnabled()) this.disableEdit(); else this.enableEdit(); }, _onEditableAdd: function () { if (this.editor) this.enableEdit(); } }; var PolylineMixin = { getEditorClass: function (tools) { return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L.Editable.PolylineEditor; }, shapeAt: function (latlng, latlngs) { // We can have those cases: // - latlngs are just a flat array of latlngs, use this // - latlngs is an array of arrays of latlngs, loop over var shape = null; latlngs = latlngs || this._latlngs; if (!latlngs.length) return shape; else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs; else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i]; return shape; }, isInLatLngs: function (l, latlngs) { if (!latlngs) return false; var i, k, len, part = [], p, w = this._clickTolerance(); this._projectLatlngs(latlngs, part, this._pxBounds); part = part[0]; p = this._map.latLngToLayerPoint(l); if (!this._pxBounds.contains(p)) { return false; } for (i = 1, len = part.length, k = 0; i < len; k = i++) { if (L.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) { return true; } } return false; } }; var PolygonMixin = { getEditorClass: function (tools) { return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L.Editable.PolygonEditor; }, shapeAt: function (latlng, latlngs) { // We can have those cases: // - latlngs are just a flat array of latlngs, use this // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first // - latlngs is an array of arrays of arrays, this is a multi, loop over var shape = null; latlngs = latlngs || this._latlngs; if (!latlngs.length) return shape; else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs; else if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs; else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i]; return shape; }, isInLatLngs: function (l, latlngs) { var inside = false, l1, l2, j, k, len2; for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) { l1 = latlngs[j]; l2 = latlngs[k]; if (((l1.lat > l.lat) !== (l2.lat > l.lat)) && (l.lng < (l2.lng - l1.lng) * (l.lat - l1.lat) / (l2.lat - l1.lat) + l1.lng)) { inside = !inside; } } return inside; }, parentShape: function (shape, latlngs) { latlngs = latlngs || this._latlngs; if (!latlngs) return; var idx = L.Util.indexOf(latlngs, shape); if (idx !== -1) return latlngs; for (var i = 0; i < latlngs.length; i++) { idx = L.Util.indexOf(latlngs[i], shape); if (idx !== -1) return latlngs[i]; } } }; var MarkerMixin = { getEditorClass: function (tools) { return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L.Editable.MarkerEditor; } }; var RectangleMixin = { getEditorClass: function (tools) { return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L.Editable.RectangleEditor; } }; var CircleMixin = { getEditorClass: function (tools) { return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L.Editable.CircleEditor; } }; var keepEditable = function () { // Make sure you can remove/readd an editable layer. this.on('add', this._onEditableAdd); }; var isFlat = L.LineUtil.isFlat || L.LineUtil._flat || L.Polyline._flat; // <=> 1.1 compat. if (L.Polyline) { L.Polyline.include(EditableMixin); L.Polyline.include(PolylineMixin); L.Polyline.addInitHook(keepEditable); } if (L.Polygon) { L.Polygon.include(EditableMixin); L.Polygon.include(PolygonMixin); } if (L.Marker) { L.Marker.include(EditableMixin); L.Marker.include(MarkerMixin); L.Marker.addInitHook(keepEditable); } if (L.Rectangle) { L.Rectangle.include(EditableMixin); L.Rectangle.include(RectangleMixin); } if (L.Circle) { L.Circle.include(EditableMixin); L.Circle.include(CircleMixin); } L.LatLng.prototype.update = function (latlng) { latlng = L.latLng(latlng); this.lat = latlng.lat; this.lng = latlng.lng; } }, window));