From dc0cf50513b22aa36bdbc37da8c672336c452d4d Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 6 Sep 2024 17:01:07 +0200 Subject: [PATCH] chore: bump Leaflet.Editable to 1.3.0 --- package-lock.json | 14 +- package.json | 2 +- .../umap/vendors/editable/Leaflet.Editable.js | 3978 +++++++++-------- .../markercluster/MarkerCluster.Default.css | 2 +- 4 files changed, 2069 insertions(+), 1927 deletions(-) diff --git a/package-lock.json b/package-lock.json index af48e671..074ee935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "jsdom": "^24.0.0", "leaflet": "1.9.4", "leaflet-contextmenu": "^1.4.0", - "leaflet-editable": "^1.2.0", + "leaflet-editable": "^1.3.0", "leaflet-editinosm": "0.2.3", "leaflet-formbuilder": "0.2.10", "leaflet-fullscreen": "1.0.2", @@ -3015,9 +3015,9 @@ "integrity": "sha1-4r2kga8QJggOq6oymX5TH9RPYFw=" }, "node_modules/leaflet-editable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/leaflet-editable/-/leaflet-editable-1.2.0.tgz", - "integrity": "sha512-wG11JwpL8zqIbypTop6xCRGagMuWw68ihYu4uqrqc5Ep0wnEJeyob7NB2Rt5t74Oih4rwJ3OfwaGbzdowOGfYQ==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/leaflet-editable/-/leaflet-editable-1.3.0.tgz", + "integrity": "sha512-elXuUM6L3kjZsg3P6PV7SEYNBX0iDT3oG1vWVnfm5Axd2q8cUqDPQYkru++m7/bE4jL07OKeETdHE543K6zsbQ==" }, "node_modules/leaflet-editinosm": { "version": "0.2.3", @@ -7154,9 +7154,9 @@ "integrity": "sha1-4r2kga8QJggOq6oymX5TH9RPYFw=" }, "leaflet-editable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/leaflet-editable/-/leaflet-editable-1.2.0.tgz", - "integrity": "sha512-wG11JwpL8zqIbypTop6xCRGagMuWw68ihYu4uqrqc5Ep0wnEJeyob7NB2Rt5t74Oih4rwJ3OfwaGbzdowOGfYQ==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/leaflet-editable/-/leaflet-editable-1.3.0.tgz", + "integrity": "sha512-elXuUM6L3kjZsg3P6PV7SEYNBX0iDT3oG1vWVnfm5Axd2q8cUqDPQYkru++m7/bE4jL07OKeETdHE543K6zsbQ==" }, "leaflet-editinosm": { "version": "0.2.3", diff --git a/package.json b/package.json index 28fba350..6b1e0522 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "jsdom": "^24.0.0", "leaflet": "1.9.4", "leaflet-contextmenu": "^1.4.0", - "leaflet-editable": "^1.2.0", + "leaflet-editable": "^1.3.0", "leaflet-editinosm": "0.2.3", "leaflet-formbuilder": "0.2.10", "leaflet-fullscreen": "1.0.2", diff --git a/umap/static/umap/vendors/editable/Leaflet.Editable.js b/umap/static/umap/vendors/editable/Leaflet.Editable.js index c41b4962..6c1915e6 100644 --- a/umap/static/umap/vendors/editable/Leaflet.Editable.js +++ b/umap/static/umap/vendors/editable/Leaflet.Editable.js @@ -1,1946 +1,2088 @@ -'use strict'; -(function (factory, window) { - /*globals define, module, require*/ - - // define an AMD module that relies on 'leaflet' - if (typeof define === 'function' && define.amd) { - define(['leaflet'], factory); +;((factory, window) => { + /*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) { - if (e.originalEvent.which != 1) return; - 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 + } 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) + } +})((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 circleMarkerClass: class = L.CircleMarker + // Class to be used when creating a new CircleMarker. + circleMarkerClass: L.CircleMarker, + + // 🍂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 circleMarkerEditorClass: class = CircleMarkerEditor + // Class to be used as CircleMarker editor. + circleMarkerEditorClass: 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 () { + const options = L.extend( + { dashArray: '5,10', weight: 1, interactive: false }, + this.options.lineGuideOptions + ) + return L.polyline([], options) + }, + + createVertexIcon: (options) => + 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) { + if (e.originalEvent.which != 1) return + this._mouseDown = e + this._drawingEditor.onDrawingMouseDown(e) + }, + + onMouseup: function (e) { + if (this._mouseDown) { + const editor = this._drawingEditor + const mouseDown = this._mouseDown + this._mouseDown = null + editor.onDrawingMouseUp(e) + if (this._drawingEditor !== editor) return // onDrawingMouseUp may call unregisterFromDrawing. + const origin = L.point( + mouseDown.originalEvent.clientX, + mouseDown.originalEvent.clientY + ) + const 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: // - // ```js - // var map = L.map('map', { - // editable: true, - // editOptions: { - // … - // } - // }); - // ``` - // 🍂section Editable Map Options - L.Map.mergeOptions({ + // `map.editTools.startPolyline();` - // 🍂namespace Map - // 🍂section Map Options - // 🍂option editToolsClass: class = L.Editable - // Class to be used as vertex, for path editing. - editToolsClass: L.Editable, + // 🍂method drawing(): boolean + // Return true if any drawing action is ongoing. + drawing: function () { + return this._drawingEditor?.drawing() + }, - // 🍂option editable: boolean = false - // Whether to create a L.Editable instance at map init. - editable: false, + // 🍂method stopDrawing() + // When you need to stop any ongoing drawing, without needing to know which editor is active. + stopDrawing: function () { + this.unregisterForDrawing() + }, - // 🍂option editOptions: hash = {} - // Options to pass to L.Editable when instantiating. - editOptions: {} + // 🍂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) + }, - L.Map.addInitHook(function () { + // 🍂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) { + const line = this.createPolyline([], options) + line.enableEdit(this.map).newShape(latlng) + return line + }, - this.whenReady(function () { - if (this.options.editable) { - this.editTools = new this.options.editToolsClass(this, this.options.editOptions); - } - }); + // 🍂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) { + const 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() + const marker = this.createMarker(latlng, options) + marker.enableEdit(this.map).startDrawing() + return marker + }, - L.Editable.VertexIcon = L.DivIcon.extend({ + // 🍂method startCircleMarker(latlng: L.LatLng, options: hash): L.CircleMarker + // Start adding a CircleMarker. If `latlng` is given, the CircleMarker 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 CircleMarker class constructor. + startCircleMarker: function (latlng, options) { + latlng = latlng || this.map.getCenter().clone() + const marker = this.createCircleMarker(latlng, options) + marker.enableEdit(this.map).startDrawing() + return marker + }, - options: { - iconSize: new L.Point(8, 8) + // 🍂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) { + const corner = latlng || L.latLng([0, 0]) + const bounds = new L.LatLngBounds(corner, corner) + const 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() + const circle = this.createCircle(latlng, options) + circle.enableEdit(this.map).startDrawing() + return circle + }, + + startHole: (editor, latlng) => { + editor.newHole(latlng) + }, + + createLayer: function (klass, latlngs, options) { + options = L.Util.extend({ editOptions: { editTools: this } }, options) + const 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?.polylineClass || this.options.polylineClass, + latlngs, + options + ) + }, + + createPolygon: function (latlngs, options) { + return this.createLayer( + options?.polygonClass || this.options.polygonClass, + latlngs, + options + ) + }, + + createMarker: function (latlng, options) { + return this.createLayer( + options?.markerClass || this.options.markerClass, + latlng, + options + ) + }, + + createCircleMarker: function (latlng, options) { + return this.createLayer( + options?.circleMarkerClass || this.options.circleMarkerClass, + latlng, + options + ) + }, + + createRectangle: function (bounds, options) { + return this.createLayer( + options?.rectangleClass || this.options.rectangleClass, + bounds, + options + ) + }, + + createCircle: function (latlng, options) { + return this.createLayer( + options?.circleClass || this.options.circleClass, + latlng, + options + ) + }, + }) + + L.extend(L.Editable, { + makeCancellable: (e) => { + e.cancel = () => { + 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.connect() + this.setZIndexOffset(editor.tools._lastZIndex + 1) + }, + + connect: function () { + this.editor.editLayer.addLayer(this) + }, + + 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) + const iconPos = L.DomUtil.getPosition(this._icon) + const 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() + const next = this.getNext() + if (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 () { + const 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 + const index = this.getIndex() + let previousIndex = index - 1 + if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex() + const 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 + const index = this.getIndex() + let nextIndex = index + 1 + if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0 + const 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 + const previous = this.getPrevious() + if (previous) this.addMiddleMarker(previous) + const 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 + const 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 () { + const leftPoint = this._map.latLngToContainerPoint(this.left.latlng) + const rightPoint = this._map.latLngToContainerPoint(this.right.latlng) + const 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 () { + const leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng) + const rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng) + const y = (leftPoint.y + rightPoint.y) / 2 + const 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) { + const iconPos = L.DomUtil.getPosition(this._icon) + const 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() + const icon = this._icon + const 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 */ + const 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: () => {}, + + 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') + }, + + onEdited: function () { + // 🍂namespace Editable + // 🍂event editable:edited: Event + // Fired after any change is made to the feature geometry. + this.fireAndForward('editable:edited') + }, + + 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) + this.onEdited() + }, + + 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) + this.onEdited() + }, + }) + + // 🍂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 CircleMarkerEditor; 🍂aka L.Editable.CircleMarkerEditor + // 🍂inherits BaseEditor + // Editor for CircleMarker. + L.Editable.CircleMarkerEditor = 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() + this.map.on('moveend', this.onMoveEnd, this) + } + return this + }, + + removeHooks: function () { + L.Editable.BaseEditor.prototype.removeHooks.call(this) + if (this.feature) { + this.map.off('moveend', this.onMoveEnd, this) + } + }, + + onMoveEnd: function () { + this.initVertexMarkers() + }, + + initVertexMarkers: function (latlngs) { + if (!this.enabled()) return + latlngs = latlngs || this.getLatLngs() + if (isFlat(latlngs)) { + this.addVertexMarkers(latlngs) + } else { + for (const member of latlngs) { + this.initVertexMarkers(member) } + } + }, - }); + getLatLngs: function () { + return this.feature.getLatLngs() + }, - L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({ + // 🍂method reset() + // Rebuild edit elements (Vertex, MiddleMarker, etc.). + reset: function () { + this.editLayer.clearLayers() + this.initVertexMarkers() + }, - options: { - iconSize: new L.Point(20, 20) + addVertexMarker: function (latlng, latlngs) { + if (latlng.__vertex) { + latlng.__vertex.connect() + return latlng.__vertex + } + 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) { + const bounds = this.map.getBounds() + for (const latlng of latlngs) { + if (!bounds.contains(latlng)) continue + this.addVertexMarker(latlng, latlngs) + } + }, + + refreshVertexMarkers: function (latlngs) { + latlngs = latlngs || this.getDefaultLatLngs() + for (const latlng of latlngs) { + latlng.__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 + const index = e.vertex.getIndex() + let 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) + this.onEdited() + }, + + 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) + const 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 + let 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) { + const 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) { + const 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 }) + this.onEdited() + return shape + }, + + _deleteShape: function (shape, latlngs) { + latlngs = latlngs || this.getLatLngs() + if (!latlngs.length) return + const inplaceDelete = (latlngs, shape) => { + // Called when deleting a flat latlngs + return latlngs.splice(0, Number.MAX_VALUE) + } + const spliceDelete = (latlngs, shape) => { + // Called when removing a latlngs inside an array + latlngs.splice(latlngs.indexOf(shape), 1) + if (!latlngs.length) this._deleteShape(latlngs) + return shape + } + if (latlngs === shape) return inplaceDelete(latlngs, shape) + for (const member of latlngs) { + if (member === shape) return spliceDelete(latlngs, shape) + if (member.indexOf(shape) !== -1) return spliceDelete(member, shape) + } + }, + + // 🍂namespace PathEditor + // 🍂method deleteShapeAt(latlng: L.LatLng): Array + // Remove a path shape at the given `latlng`. + deleteShapeAt: function (latlng) { + const 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 (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 + 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) { + const shape = [] + this.appendShape(shape) + return shape + } + return this.feature._latlngs + }, + + formatShape: function (shape) { + if (isFlat(shape)) return shape + 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() + const shapeIndex = this.feature._latlngs.indexOf(shape) + if (shapeIndex === -1) return + const first = shape.slice(0, index + 1) + const 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() + this.onEdited() + }, + }) + + // 🍂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() + const latlngs = this.feature.shapeAt(latlng) + if (!latlngs) return + const 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) { + const 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) { + const shape = [] + this.appendShape(shape) + return shape + } + 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) { + const parent = this.feature.parentShape(vertex.latlngs) + const 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: (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] + 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) { + const index = e.vertex.getIndex() + const next = e.vertex.getNext() + const previous = e.vertex.getPrevious() + const oppositeIndex = (index + 2) % 4 + const opposite = e.vertex.latlngs[oppositeIndex] + const 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() + const 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) + const 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) { + const latlngs = this.getDefaultLatLngs() + const newLatlngs = this.feature._boundsToLatLngs(bounds) + // Keep references. + for (let 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. + const delta = + (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4) + const 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) { + let radius + if (this.map.options.crs) { + radius = this.map.options.crs.distance(this.feature._latlng, e.latlng) + } else { + 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.* + const EditableMixin = { + createEditor: function (map) { + map = map || this._map + const tools = this.options.editOptions?.editTools || map.editTools + if (!tools) throw Error('Unable to detect Editable instance.') + const 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?.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() + }, + } + + const PolylineMixin = { + getEditorClass: (tools) => { + return 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 + let shape = null + latlngs = latlngs || this._latlngs + if (!latlngs.length) return shape + if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs + else { + for (const member of latlngs) { + if (this.isInLatLngs(latlng, member)) return member } + } + return shape + }, - }); + isInLatLngs: function (l, latlngs) { + if (!latlngs) return false + let i + let k + let len + let part = [] + let p + const w = this._clickTolerance() + this._projectLatlngs(latlngs, part, this._pxBounds) + part = part[0] + p = this._map.latLngToLayerPoint(l) - - // 🍂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); + 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 + }, + } - }); + const PolygonMixin = { + getEditorClass: (tools) => { + return tools?.options?.polygonEditorClass || L.Editable.PolygonEditor + }, - 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); + 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 + let shape = null + latlngs = latlngs || this._latlngs + if (!latlngs.length) return shape + if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs + if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) { + shape = latlngs + } else { + for (const member of latlngs) { + if (this.isInLatLngs(latlng, member[0])) return member } + } + return shape + }, - }); + isInLatLngs: (l, latlngs) => { + let inside = false + let l1 + let l2 + let j + let k + let len2 - L.Editable.mergeOptions({ + for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) { + l1 = latlngs[j] + l2 = latlngs[k] - // 🍂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); + 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 } - - }); - - // 🍂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)); + } + + return inside + }, + + parentShape: function (shape, latlngs) { + latlngs = latlngs || this._latlngs + if (!latlngs) return + let idx = L.Util.indexOf(latlngs, shape) + if (idx !== -1) return latlngs + for (const member of latlngs) { + idx = L.Util.indexOf(member, shape) + if (idx !== -1) return member + } + }, + } + + const MarkerMixin = { + getEditorClass: (tools) => { + return tools?.options?.markerEditorClass || L.Editable.MarkerEditor + }, + } + + const CircleMarkerMixin = { + getEditorClass: (tools) => { + return tools?.options?.circleMarkerEditorClass || L.Editable.CircleMarkerEditor + }, + } + + const RectangleMixin = { + getEditorClass: (tools) => { + return tools?.options?.rectangleEditorClass || L.Editable.RectangleEditor + }, + } + + const CircleMixin = { + getEditorClass: (tools) => { + return tools?.options?.circleEditorClass || L.Editable.CircleEditor + }, + } + + const keepEditable = function () { + // Make sure you can remove/readd an editable layer. + this.on('add', this._onEditableAdd) + } + + const 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.CircleMarker) { + L.CircleMarker.include(EditableMixin) + L.CircleMarker.include(CircleMarkerMixin) + L.CircleMarker.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) diff --git a/umap/static/umap/vendors/markercluster/MarkerCluster.Default.css b/umap/static/umap/vendors/markercluster/MarkerCluster.Default.css index db8a1f2b..bbc8c9fb 100755 --- a/umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +++ b/umap/static/umap/vendors/markercluster/MarkerCluster.Default.css @@ -57,4 +57,4 @@ } .marker-cluster span { line-height: 30px; - } + } \ No newline at end of file