1 (function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('d3'), require('leaflet')) :
3 typeof define === 'function' && define.amd ? define(['d3', 'leaflet'], factory) :
4 (global.BrAPIFieldmap = factory(global.d3,global.L));
5 }(this, (function (d3,L) { 'use strict';
7 d3 = d3 && d3.hasOwnProperty('default') ? d3['default'] : d3;
8 L = L && L.hasOwnProperty('default') ? L['default'] : L;
11 Modified to explicitly use window.L
14 * Leaflet.TileLayer.Fallback 1.0.4+e36cde9
15 * Replaces missing Tiles (404 error) by scaled lower zoom Tiles
16 * (c) 2015-2018 Boris Seang
19 (function (root, factory) {
20 if (typeof define === "function" && define.amd) {
21 define(["leaflet"], factory);
22 } else if (typeof module === "object" && module.exports) {
23 factory(require("leaflet"));
27 }(undefined, function (L$$1) {
29 L$$1.TileLayer.Fallback = L$$1.TileLayer.extend({
35 initialize: function (urlTemplate, options) {
36 L$$1.TileLayer.prototype.initialize.call(this, urlTemplate, options);
39 createTile: function (coords, done) {
40 var tile = L$$1.TileLayer.prototype.createTile.call(this, coords, done);
41 tile._originalCoords = coords;
42 tile._originalSrc = tile.src;
47 _createCurrentCoords: function (originalCoords) {
48 var currentCoords = this._wrapCoords(originalCoords);
50 currentCoords.fallback = true;
55 _originalTileOnError: L$$1.TileLayer.prototype._tileOnError,
57 _tileOnError: function (done, tile, e) {
58 var layer = this, // `this` is bound to the Tile Layer in L.TileLayer.prototype.createTile.
59 originalCoords = tile._originalCoords,
60 currentCoords = tile._currentCoords = tile._currentCoords || layer._createCurrentCoords(originalCoords),
61 fallbackZoom = tile._fallbackZoom = tile._fallbackZoom === undefined ? originalCoords.z - 1 : tile._fallbackZoom - 1,
62 scale = tile._fallbackScale = (tile._fallbackScale || 1) * 2,
63 tileSize = layer.getTileSize(),
67 // If no lower zoom tiles are available, fallback to errorTile.
68 if (fallbackZoom < layer.options.minNativeZoom) {
69 return this._originalTileOnError(done, tile, e);
72 // Modify tilePoint for replacement img.
73 currentCoords.z = fallbackZoom;
74 currentCoords.x = Math.floor(currentCoords.x / 2);
75 currentCoords.y = Math.floor(currentCoords.y / 2);
77 // Generate new src path.
78 newUrl = layer.getTileUrl(currentCoords);
80 // Zoom replacement img.
81 style.width = (tileSize.x * scale) + 'px';
82 style.height = (tileSize.y * scale) + 'px';
84 // Compute margins to adjust position.
85 top = (originalCoords.y - currentCoords.y * scale) * tileSize.y;
86 style.marginTop = (-top) + 'px';
87 left = (originalCoords.x - currentCoords.x * scale) * tileSize.x;
88 style.marginLeft = (-left) + 'px';
91 // `clip` is deprecated, but browsers support for `clip-path: inset()` is far behind.
92 // http://caniuse.com/#feat=css-clip-path
93 style.clip = 'rect(' + top + 'px ' + (left + tileSize.x) + 'px ' + (top + tileSize.y) + 'px ' + left + 'px)';
95 layer.fire('tilefallback', {
97 url: tile._originalSrc,
105 getTileUrl: function (coords) {
106 var z = coords.z = coords.fallback ? coords.z : this._getZoomForUrl();
109 r: L$$1.Browser.retina ? '@2x' : '',
110 s: this._getSubdomain(coords),
115 if (this._map && !this._map.options.crs.infinite) {
116 var invertedY = this._globalTileRange.max.y - coords.y;
117 if (this.options.tms) {
118 data['y'] = invertedY;
120 data['-y'] = invertedY;
123 return L$$1.Util.template(this._url, L$$1.extend(data, this.options));
130 // Supply with a factory for consistency with Leaflet.
131 L$$1.tileLayer.fallback = function (urlTemplate, options) {
132 return new L$$1.TileLayer.Fallback(urlTemplate, options);
139 (function (factory, window) {
140 /*globals define, module, require*/
142 // define an AMD module that relies on 'leaflet'
143 if (typeof define === 'function' && define.amd) {
144 define(['leaflet'], factory);
147 // define a Common JS module that relies on 'leaflet'
148 } else if (typeof exports === 'object') {
149 module.exports = factory(require('leaflet'));
152 // attach your plugin to the global 'L' variable
153 if(typeof window !== 'undefined' && window.L){
158 // 🍂miniclass CancelableEvent (Event objects)
160 // Cancel any subsequent action.
162 // 🍂miniclass VertexEvent (Event objects)
163 // 🍂property vertex: VertexMarker
164 // The vertex that fires the event.
166 // 🍂miniclass ShapeEvent (Event objects)
167 // 🍂property shape: Array
168 // The shape (LatLngs array) subject of the action.
170 // 🍂miniclass CancelableVertexEvent (Event objects)
171 // 🍂inherits VertexEvent
172 // 🍂inherits CancelableEvent
174 // 🍂miniclass CancelableShapeEvent (Event objects)
175 // 🍂inherits ShapeEvent
176 // 🍂inherits CancelableEvent
178 // 🍂miniclass LayerEvent (Event objects)
179 // 🍂property layer: object
180 // The Layer (Marker, Polyline…) subject of the action.
182 // 🍂namespace Editable; 🍂class Editable; 🍂aka L.Editable
183 // Main edition handler. By default, it is attached to the map
184 // as `map.editTools` property.
185 // Leaflet.Editable is made to be fully extendable. You have three ways to customize
186 // the behaviour: using options, listening to events, or extending.
187 L$$1.Editable = L$$1.Evented.extend({
196 // You can pass them when creating a map using the `editOptions` key.
197 // 🍂option zIndex: int = 1000
198 // The default zIndex of the editing tools.
201 // 🍂option polygonClass: class = L.Polygon
202 // Class to be used when creating a new Polygon.
203 polygonClass: L$$1.Polygon,
205 // 🍂option polylineClass: class = L.Polyline
206 // Class to be used when creating a new Polyline.
207 polylineClass: L$$1.Polyline,
209 // 🍂option markerClass: class = L.Marker
210 // Class to be used when creating a new Marker.
211 markerClass: L$$1.Marker,
213 // 🍂option rectangleClass: class = L.Rectangle
214 // Class to be used when creating a new Rectangle.
215 rectangleClass: L$$1.Rectangle,
217 // 🍂option circleClass: class = L.Circle
218 // Class to be used when creating a new Circle.
219 circleClass: L$$1.Circle,
221 // 🍂option drawingCSSClass: string = 'leaflet-editable-drawing'
222 // CSS class to be added to the map container while drawing.
223 drawingCSSClass: 'leaflet-editable-drawing',
225 // 🍂option drawingCursor: const = 'crosshair'
226 // Cursor mode set to the map while drawing.
227 drawingCursor: 'crosshair',
229 // 🍂option editLayer: Layer = new L.LayerGroup()
230 // Layer used to store edit tools (vertex, line guide…).
231 editLayer: undefined,
233 // 🍂option featuresLayer: Layer = new L.LayerGroup()
234 // Default layer used to store drawn features (Marker, Polyline…).
235 featuresLayer: undefined,
237 // 🍂option polylineEditorClass: class = PolylineEditor
238 // Class to be used as Polyline editor.
239 polylineEditorClass: undefined,
241 // 🍂option polygonEditorClass: class = PolygonEditor
242 // Class to be used as Polygon editor.
243 polygonEditorClass: undefined,
245 // 🍂option markerEditorClass: class = MarkerEditor
246 // Class to be used as Marker editor.
247 markerEditorClass: undefined,
249 // 🍂option rectangleEditorClass: class = RectangleEditor
250 // Class to be used as Rectangle editor.
251 rectangleEditorClass: undefined,
253 // 🍂option circleEditorClass: class = CircleEditor
254 // Class to be used as Circle editor.
255 circleEditorClass: undefined,
257 // 🍂option lineGuideOptions: hash = {}
258 // Options to be passed to the line guides.
259 lineGuideOptions: {},
261 // 🍂option skipMiddleMarkers: boolean = false
262 // Set this to true if you don't want middle markers.
263 skipMiddleMarkers: false
267 initialize: function (map, options) {
268 L$$1.setOptions(this, options);
269 this._lastZIndex = this.options.zIndex;
271 this.editLayer = this.createEditLayer();
272 this.featuresLayer = this.createFeaturesLayer();
273 this.forwardLineGuide = this.createLineGuide();
274 this.backwardLineGuide = this.createLineGuide();
277 fireAndForward: function (type, e) {
281 this.map.fire(type, e);
284 createLineGuide: function () {
285 var options = L$$1.extend({dashArray: '5,10', weight: 1, interactive: false}, this.options.lineGuideOptions);
286 return L$$1.polyline([], options);
289 createVertexIcon: function (options) {
290 return L$$1.Browser.mobile && L$$1.Browser.touch ? new L$$1.Editable.TouchVertexIcon(options) : new L$$1.Editable.VertexIcon(options);
293 createEditLayer: function () {
294 return this.options.editLayer || new L$$1.LayerGroup().addTo(this.map);
297 createFeaturesLayer: function () {
298 return this.options.featuresLayer || new L$$1.LayerGroup().addTo(this.map);
301 moveForwardLineGuide: function (latlng) {
302 if (this.forwardLineGuide._latlngs.length) {
303 this.forwardLineGuide._latlngs[1] = latlng;
304 this.forwardLineGuide._bounds.extend(latlng);
305 this.forwardLineGuide.redraw();
309 moveBackwardLineGuide: function (latlng) {
310 if (this.backwardLineGuide._latlngs.length) {
311 this.backwardLineGuide._latlngs[1] = latlng;
312 this.backwardLineGuide._bounds.extend(latlng);
313 this.backwardLineGuide.redraw();
317 anchorForwardLineGuide: function (latlng) {
318 this.forwardLineGuide._latlngs[0] = latlng;
319 this.forwardLineGuide._bounds.extend(latlng);
320 this.forwardLineGuide.redraw();
323 anchorBackwardLineGuide: function (latlng) {
324 this.backwardLineGuide._latlngs[0] = latlng;
325 this.backwardLineGuide._bounds.extend(latlng);
326 this.backwardLineGuide.redraw();
329 attachForwardLineGuide: function () {
330 this.editLayer.addLayer(this.forwardLineGuide);
333 attachBackwardLineGuide: function () {
334 this.editLayer.addLayer(this.backwardLineGuide);
337 detachForwardLineGuide: function () {
338 this.forwardLineGuide.setLatLngs([]);
339 this.editLayer.removeLayer(this.forwardLineGuide);
342 detachBackwardLineGuide: function () {
343 this.backwardLineGuide.setLatLngs([]);
344 this.editLayer.removeLayer(this.backwardLineGuide);
347 blockEvents: function () {
348 // Hack: force map not to listen to other layers events while drawing.
349 if (!this._oldTargets) {
350 this._oldTargets = this.map._targets;
351 this.map._targets = {};
355 unblockEvents: function () {
356 if (this._oldTargets) {
357 // Reset, but keep targets created while drawing.
358 this.map._targets = L$$1.extend(this.map._targets, this._oldTargets);
359 delete this._oldTargets;
363 registerForDrawing: function (editor) {
364 if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor);
366 editor.reset(); // Make sure editor tools still receive events.
367 this._drawingEditor = editor;
368 this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor);
369 this.map.on('mousedown', this.onMousedown, this);
370 this.map.on('mouseup', this.onMouseup, this);
371 L$$1.DomUtil.addClass(this.map._container, this.options.drawingCSSClass);
372 this.defaultMapCursor = this.map._container.style.cursor;
373 this.map._container.style.cursor = this.options.drawingCursor;
376 unregisterForDrawing: function (editor) {
377 this.unblockEvents();
378 L$$1.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass);
379 this.map._container.style.cursor = this.defaultMapCursor;
380 editor = editor || this._drawingEditor;
382 this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor);
383 this.map.off('mousedown', this.onMousedown, this);
384 this.map.off('mouseup', this.onMouseup, this);
385 if (editor !== this._drawingEditor) return;
386 delete this._drawingEditor;
387 if (editor._drawing) editor.cancelDrawing();
390 onMousedown: function (e) {
391 if (e.originalEvent.which != 1) return;
393 this._drawingEditor.onDrawingMouseDown(e);
396 onMouseup: function (e) {
397 if (this._mouseDown) {
398 var editor = this._drawingEditor,
399 mouseDown = this._mouseDown;
400 this._mouseDown = null;
401 editor.onDrawingMouseUp(e);
402 if (this._drawingEditor !== editor) return; // onDrawingMouseUp may call unregisterFromDrawing.
403 var origin = L$$1.point(mouseDown.originalEvent.clientX, mouseDown.originalEvent.clientY);
404 var distance = L$$1.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(origin);
405 if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) this._drawingEditor.onDrawingClick(e);
409 // 🍂section Public methods
410 // You will generally access them by the `map.editTools`
413 // `map.editTools.startPolyline();`
415 // 🍂method drawing(): boolean
416 // Return true if any drawing action is ongoing.
417 drawing: function () {
418 return this._drawingEditor && this._drawingEditor.drawing();
421 // 🍂method stopDrawing()
422 // When you need to stop any ongoing drawing, without needing to know which editor is active.
423 stopDrawing: function () {
424 this.unregisterForDrawing();
427 // 🍂method commitDrawing()
428 // When you need to commit any ongoing drawing, without needing to know which editor is active.
429 commitDrawing: function (e) {
430 if (!this._drawingEditor) return;
431 this._drawingEditor.commitDrawing(e);
434 connectCreatedToMap: function (layer) {
435 return this.featuresLayer.addLayer(layer);
438 // 🍂method startPolyline(latlng: L.LatLng, options: hash): L.Polyline
439 // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click.
440 // If `options` is given, it will be passed to the Polyline class constructor.
441 startPolyline: function (latlng, options) {
442 var line = this.createPolyline([], options);
443 line.enableEdit(this.map).newShape(latlng);
447 // 🍂method startPolygon(latlng: L.LatLng, options: hash): L.Polygon
448 // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click.
449 // If `options` is given, it will be passed to the Polygon class constructor.
450 startPolygon: function (latlng, options) {
451 var polygon = this.createPolygon([], options);
452 polygon.enableEdit(this.map).newShape(latlng);
456 // 🍂method startMarker(latlng: L.LatLng, options: hash): L.Marker
457 // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point.
458 // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch).
459 // If `options` is given, it will be passed to the Marker class constructor.
460 startMarker: function (latlng, options) {
461 latlng = latlng || this.map.getCenter().clone();
462 var marker = this.createMarker(latlng, options);
463 marker.enableEdit(this.map).startDrawing();
467 // 🍂method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle
468 // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag.
469 // If `options` is given, it will be passed to the Rectangle class constructor.
470 startRectangle: function(latlng, options) {
471 var corner = latlng || L$$1.latLng([0, 0]);
472 var bounds = new L$$1.LatLngBounds(corner, corner);
473 var rectangle = this.createRectangle(bounds, options);
474 rectangle.enableEdit(this.map).startDrawing();
478 // 🍂method startCircle(latlng: L.LatLng, options: hash): L.Circle
479 // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag.
480 // If `options` is given, it will be passed to the Circle class constructor.
481 startCircle: function (latlng, options) {
482 latlng = latlng || this.map.getCenter().clone();
483 var circle = this.createCircle(latlng, options);
484 circle.enableEdit(this.map).startDrawing();
488 startHole: function (editor, latlng) {
489 editor.newHole(latlng);
492 createLayer: function (klass, latlngs, options) {
493 options = L$$1.Util.extend({editOptions: {editTools: this}}, options);
494 var layer = new klass(latlngs, options);
495 // 🍂namespace Editable
496 // 🍂event editable:created: LayerEvent
497 // Fired when a new feature (Marker, Polyline…) is created.
498 this.fireAndForward('editable:created', {layer: layer});
502 createPolyline: function (latlngs, options) {
503 return this.createLayer(options && options.polylineClass || this.options.polylineClass, latlngs, options);
506 createPolygon: function (latlngs, options) {
507 return this.createLayer(options && options.polygonClass || this.options.polygonClass, latlngs, options);
510 createMarker: function (latlng, options) {
511 return this.createLayer(options && options.markerClass || this.options.markerClass, latlng, options);
514 createRectangle: function (bounds, options) {
515 return this.createLayer(options && options.rectangleClass || this.options.rectangleClass, bounds, options);
518 createCircle: function (latlng, options) {
519 return this.createLayer(options && options.circleClass || this.options.circleClass, latlng, options);
524 L$$1.extend(L$$1.Editable, {
526 makeCancellable: function (e) {
527 e.cancel = function () {
534 // 🍂namespace Map; 🍂class Map
535 // Leaflet.Editable add options and events to the `L.Map` object.
536 // See `Editable` events for the list of events fired on the Map.
540 // var map = L.map('map', {
547 // 🍂section Editable Map Options
548 L$$1.Map.mergeOptions({
551 // 🍂section Map Options
552 // 🍂option editToolsClass: class = L.Editable
553 // Class to be used as vertex, for path editing.
554 editToolsClass: L$$1.Editable,
556 // 🍂option editable: boolean = false
557 // Whether to create a L.Editable instance at map init.
560 // 🍂option editOptions: hash = {}
561 // Options to pass to L.Editable when instantiating.
566 L$$1.Map.addInitHook(function () {
568 this.whenReady(function () {
569 if (this.options.editable) {
570 this.editTools = new this.options.editToolsClass(this, this.options.editOptions);
576 L$$1.Editable.VertexIcon = L$$1.DivIcon.extend({
579 iconSize: new L$$1.Point(8, 8)
584 L$$1.Editable.TouchVertexIcon = L$$1.Editable.VertexIcon.extend({
587 iconSize: new L$$1.Point(20, 20)
593 // 🍂namespace Editable; 🍂class VertexMarker; Handler for dragging path vertices.
594 L$$1.Editable.VertexMarker = L$$1.Marker.extend({
598 className: 'leaflet-div-icon leaflet-vertex-icon'
602 // 🍂section Public methods
603 // The marker used to handle path vertex. You will usually interact with a `VertexMarker`
604 // instance when listening for events like `editable:vertex:ctrlclick`.
606 initialize: function (latlng, latlngs, editor, options) {
607 // We don't use this._latlng, because on drag Leaflet replace it while
608 // we want to keep reference.
609 this.latlng = latlng;
610 this.latlngs = latlngs;
611 this.editor = editor;
612 L$$1.Marker.prototype.initialize.call(this, latlng, options);
613 this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
614 this.latlng.__vertex = this;
615 this.editor.editLayer.addLayer(this);
616 this.setZIndexOffset(editor.tools._lastZIndex + 1);
619 onAdd: function (map) {
620 L$$1.Marker.prototype.onAdd.call(this, map);
621 this.on('drag', this.onDrag);
622 this.on('dragstart', this.onDragStart);
623 this.on('dragend', this.onDragEnd);
624 this.on('mouseup', this.onMouseup);
625 this.on('click', this.onClick);
626 this.on('contextmenu', this.onContextMenu);
627 this.on('mousedown touchstart', this.onMouseDown);
628 this.on('mouseover', this.onMouseOver);
629 this.on('mouseout', this.onMouseOut);
630 this.addMiddleMarkers();
633 onRemove: function (map) {
634 if (this.middleMarker) this.middleMarker.delete();
635 delete this.latlng.__vertex;
636 this.off('drag', this.onDrag);
637 this.off('dragstart', this.onDragStart);
638 this.off('dragend', this.onDragEnd);
639 this.off('mouseup', this.onMouseup);
640 this.off('click', this.onClick);
641 this.off('contextmenu', this.onContextMenu);
642 this.off('mousedown touchstart', this.onMouseDown);
643 this.off('mouseover', this.onMouseOver);
644 this.off('mouseout', this.onMouseOut);
645 L$$1.Marker.prototype.onRemove.call(this, map);
648 onDrag: function (e) {
650 this.editor.onVertexMarkerDrag(e);
651 var iconPos = L$$1.DomUtil.getPosition(this._icon),
652 latlng = this._map.layerPointToLatLng(iconPos);
653 this.latlng.update(latlng);
654 this._latlng = this.latlng; // Push back to Leaflet our reference.
655 this.editor.refresh();
656 if (this.middleMarker) this.middleMarker.updateLatLng();
657 var next = this.getNext();
658 if (next && next.middleMarker) next.middleMarker.updateLatLng();
661 onDragStart: function (e) {
663 this.editor.onVertexMarkerDragStart(e);
666 onDragEnd: function (e) {
668 this.editor.onVertexMarkerDragEnd(e);
671 onClick: function (e) {
673 this.editor.onVertexMarkerClick(e);
676 onMouseup: function (e) {
677 L$$1.DomEvent.stop(e);
679 this.editor.map.fire('mouseup', e);
682 onContextMenu: function (e) {
684 this.editor.onVertexMarkerContextMenu(e);
687 onMouseDown: function (e) {
689 this.editor.onVertexMarkerMouseDown(e);
692 onMouseOver: function (e) {
694 this.editor.onVertexMarkerMouseOver(e);
697 onMouseOut: function (e) {
699 this.editor.onVertexMarkerMouseOut(e);
703 // Delete a vertex and the related LatLng.
704 delete: function () {
705 var next = this.getNext(); // Compute before changing latlng
706 this.latlngs.splice(this.getIndex(), 1);
707 this.editor.editLayer.removeLayer(this);
708 this.editor.onVertexDeleted({latlng: this.latlng, vertex: this});
709 if (!this.latlngs.length) this.editor.deleteShape(this.latlngs);
710 if (next) next.resetMiddleMarker();
711 this.editor.refresh();
714 // 🍂method getIndex(): int
715 // Get the index of the current vertex among others of the same LatLngs group.
716 getIndex: function () {
717 return this.latlngs.indexOf(this.latlng);
720 // 🍂method getLastIndex(): int
721 // Get last vertex index of the LatLngs group of the current vertex.
722 getLastIndex: function () {
723 return this.latlngs.length - 1;
726 // 🍂method getPrevious(): VertexMarker
727 // Get the previous VertexMarker in the same LatLngs group.
728 getPrevious: function () {
729 if (this.latlngs.length < 2) return;
730 var index = this.getIndex(),
731 previousIndex = index - 1;
732 if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex();
733 var previous = this.latlngs[previousIndex];
734 if (previous) return previous.__vertex;
737 // 🍂method getNext(): VertexMarker
738 // Get the next VertexMarker in the same LatLngs group.
739 getNext: function () {
740 if (this.latlngs.length < 2) return;
741 var index = this.getIndex(),
742 nextIndex = index + 1;
743 if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0;
744 var next = this.latlngs[nextIndex];
745 if (next) return next.__vertex;
748 addMiddleMarker: function (previous) {
749 if (!this.editor.hasMiddleMarkers()) return;
750 previous = previous || this.getPrevious();
751 if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor);
754 addMiddleMarkers: function () {
755 if (!this.editor.hasMiddleMarkers()) return;
756 var previous = this.getPrevious();
757 if (previous) this.addMiddleMarker(previous);
758 var next = this.getNext();
759 if (next) next.resetMiddleMarker();
762 resetMiddleMarker: function () {
763 if (this.middleMarker) this.middleMarker.delete();
764 this.addMiddleMarker();
768 // Split the vertex LatLngs group at its index, if possible.
770 if (!this.editor.splitShape) return; // Only for PolylineEditor
771 this.editor.splitShape(this.latlngs, this.getIndex());
774 // 🍂method continue()
775 // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline.
776 continue: function () {
777 if (!this.editor.continueBackward) return; // Only for PolylineEditor
778 var index = this.getIndex();
779 if (index === 0) this.editor.continueBackward(this.latlngs);
780 else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs);
785 L$$1.Editable.mergeOptions({
787 // 🍂namespace Editable
788 // 🍂option vertexMarkerClass: class = VertexMarker
789 // Class to be used as vertex, for path editing.
790 vertexMarkerClass: L$$1.Editable.VertexMarker
794 L$$1.Editable.MiddleMarker = L$$1.Marker.extend({
798 className: 'leaflet-div-icon leaflet-middle-icon',
802 initialize: function (left, right, latlngs, editor, options) {
805 this.editor = editor;
806 this.latlngs = latlngs;
807 L$$1.Marker.prototype.initialize.call(this, this.computeLatLng(), options);
808 this._opacity = this.options.opacity;
809 this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
810 this.editor.editLayer.addLayer(this);
811 this.setVisibility();
814 setVisibility: function () {
815 var leftPoint = this._map.latLngToContainerPoint(this.left.latlng),
816 rightPoint = this._map.latLngToContainerPoint(this.right.latlng),
817 size = L$$1.point(this.options.icon.options.iconSize);
818 if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide();
823 this.setOpacity(this._opacity);
830 updateLatLng: function () {
831 this.setLatLng(this.computeLatLng());
832 this.setVisibility();
835 computeLatLng: function () {
836 var leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng),
837 rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng),
838 y = (leftPoint.y + rightPoint.y) / 2,
839 x = (leftPoint.x + rightPoint.x) / 2;
840 return this.editor.map.containerPointToLatLng([x, y]);
843 onAdd: function (map) {
844 L$$1.Marker.prototype.onAdd.call(this, map);
845 L$$1.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this);
846 map.on('zoomend', this.setVisibility, this);
849 onRemove: function (map) {
850 delete this.right.middleMarker;
851 L$$1.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this);
852 map.off('zoomend', this.setVisibility, this);
853 L$$1.Marker.prototype.onRemove.call(this, map);
856 onMouseDown: function (e) {
857 var iconPos = L$$1.DomUtil.getPosition(this._icon),
858 latlng = this.editor.map.layerPointToLatLng(iconPos);
863 if (this.options.opacity === 0) return;
864 L$$1.Editable.makeCancellable(e);
865 this.editor.onMiddleMarkerMouseDown(e);
866 if (e._cancelled) return;
867 this.latlngs.splice(this.index(), 0, e.latlng);
868 this.editor.refresh();
869 var icon = this._icon;
870 var marker = this.editor.addVertexMarker(e.latlng, this.latlngs);
871 this.editor.onNewVertex(marker);
872 /* Hack to workaround browser not firing touchend when element is no more on DOM */
873 var parent = marker._icon.parentNode;
874 parent.removeChild(marker._icon);
876 parent.appendChild(marker._icon);
878 marker._initInteraction();
879 marker.setOpacity(1);
881 // Transfer ongoing dragging to real marker
882 L$$1.Draggable._dragging = false;
883 marker.dragging._draggable._onDown(e.originalEvent);
887 delete: function () {
888 this.editor.editLayer.removeLayer(this);
892 return this.latlngs.indexOf(this.right.latlng);
897 L$$1.Editable.mergeOptions({
899 // 🍂namespace Editable
900 // 🍂option middleMarkerClass: class = VertexMarker
901 // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path.
902 middleMarkerClass: L$$1.Editable.MiddleMarker
906 // 🍂namespace Editable; 🍂class BaseEditor; 🍂aka L.Editable.BaseEditor
907 // When editing a feature (Marker, Polyline…), an editor is attached to it. This
908 // editor basically knows how to handle the edition.
909 L$$1.Editable.BaseEditor = L$$1.Handler.extend({
911 initialize: function (map, feature, options) {
912 L$$1.setOptions(this, options);
914 this.feature = feature;
915 this.feature.editor = this;
916 this.editLayer = new L$$1.LayerGroup();
917 this.tools = this.options.editTools || map.editTools;
920 // 🍂method enable(): this
921 // Set up the drawing tools for the feature to be editable.
922 addHooks: function () {
923 if (this.isConnected()) this.onFeatureAdd();
924 else this.feature.once('add', this.onFeatureAdd, this);
926 this.feature.on(this._getEvents(), this);
929 // 🍂method disable(): this
930 // Remove the drawing tools for the feature.
931 removeHooks: function () {
932 this.feature.off(this._getEvents(), this);
933 if (this.feature.dragging) this.feature.dragging.disable();
934 this.editLayer.clearLayers();
935 this.tools.editLayer.removeLayer(this.editLayer);
937 if (this._drawing) this.cancelDrawing();
940 // 🍂method drawing(): boolean
941 // Return true if any drawing action is ongoing with this editor.
942 drawing: function () {
943 return !!this._drawing;
946 reset: function () {},
948 onFeatureAdd: function () {
949 this.tools.editLayer.addLayer(this.editLayer);
950 if (this.feature.dragging) this.feature.dragging.enable();
953 hasMiddleMarkers: function () {
954 return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers;
957 fireAndForward: function (type, e) {
959 e.layer = this.feature;
960 this.feature.fire(type, e);
961 this.tools.fireAndForward(type, e);
964 onEnable: function () {
965 // 🍂namespace Editable
966 // 🍂event editable:enable: Event
967 // Fired when an existing feature is ready to be edited.
968 this.fireAndForward('editable:enable');
971 onDisable: function () {
972 // 🍂namespace Editable
973 // 🍂event editable:disable: Event
974 // Fired when an existing feature is not ready anymore to be edited.
975 this.fireAndForward('editable:disable');
978 onEditing: function () {
979 // 🍂namespace Editable
980 // 🍂event editable:editing: Event
981 // Fired as soon as any change is made to the feature geometry.
982 this.fireAndForward('editable:editing');
985 onStartDrawing: function () {
986 // 🍂namespace Editable
987 // 🍂section Drawing events
988 // 🍂event editable:drawing:start: Event
989 // Fired when a feature is to be drawn.
990 this.fireAndForward('editable:drawing:start');
993 onEndDrawing: function () {
994 // 🍂namespace Editable
995 // 🍂section Drawing events
996 // 🍂event editable:drawing:end: Event
997 // Fired when a feature is not drawn anymore.
998 this.fireAndForward('editable:drawing:end');
1001 onCancelDrawing: function () {
1002 // 🍂namespace Editable
1003 // 🍂section Drawing events
1004 // 🍂event editable:drawing:cancel: Event
1005 // Fired when user cancel drawing while a feature is being drawn.
1006 this.fireAndForward('editable:drawing:cancel');
1009 onCommitDrawing: function (e) {
1010 // 🍂namespace Editable
1011 // 🍂section Drawing events
1012 // 🍂event editable:drawing:commit: Event
1013 // Fired when user finish drawing a feature.
1014 this.fireAndForward('editable:drawing:commit', e);
1017 onDrawingMouseDown: function (e) {
1018 // 🍂namespace Editable
1019 // 🍂section Drawing events
1020 // 🍂event editable:drawing:mousedown: Event
1021 // Fired when user `mousedown` while drawing.
1022 this.fireAndForward('editable:drawing:mousedown', e);
1025 onDrawingMouseUp: function (e) {
1026 // 🍂namespace Editable
1027 // 🍂section Drawing events
1028 // 🍂event editable:drawing:mouseup: Event
1029 // Fired when user `mouseup` while drawing.
1030 this.fireAndForward('editable:drawing:mouseup', e);
1033 startDrawing: function () {
1034 if (!this._drawing) this._drawing = L$$1.Editable.FORWARD;
1035 this.tools.registerForDrawing(this);
1036 this.onStartDrawing();
1039 commitDrawing: function (e) {
1040 this.onCommitDrawing(e);
1044 cancelDrawing: function () {
1045 // If called during a vertex drag, the vertex will be removed before
1046 // the mouseup fires on it. This is a workaround. Maybe better fix is
1047 // To have L.Draggable reset it's status on disable (Leaflet side).
1048 L$$1.Draggable._dragging = false;
1049 this.onCancelDrawing();
1053 endDrawing: function () {
1054 this._drawing = false;
1055 this.tools.unregisterForDrawing(this);
1056 this.onEndDrawing();
1059 onDrawingClick: function (e) {
1060 if (!this.drawing()) return;
1061 L$$1.Editable.makeCancellable(e);
1062 // 🍂namespace Editable
1063 // 🍂section Drawing events
1064 // 🍂event editable:drawing:click: CancelableEvent
1065 // Fired when user `click` while drawing, before any internal action is being processed.
1066 this.fireAndForward('editable:drawing:click', e);
1067 if (e._cancelled) return;
1068 if (!this.isConnected()) this.connect(e);
1069 this.processDrawingClick(e);
1072 isConnected: function () {
1073 return this.map.hasLayer(this.feature);
1076 connect: function () {
1077 this.tools.connectCreatedToMap(this.feature);
1078 this.tools.editLayer.addLayer(this.editLayer);
1081 onMove: function (e) {
1082 // 🍂namespace Editable
1083 // 🍂section Drawing events
1084 // 🍂event editable:drawing:move: Event
1085 // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex.
1086 this.fireAndForward('editable:drawing:move', e);
1089 onDrawingMouseMove: function (e) {
1093 _getEvents: function () {
1095 dragstart: this.onDragStart,
1097 dragend: this.onDragEnd,
1098 remove: this.disable
1102 onDragStart: function (e) {
1104 // 🍂namespace Editable
1105 // 🍂event editable:dragstart: Event
1106 // Fired before a path feature is dragged.
1107 this.fireAndForward('editable:dragstart', e);
1110 onDrag: function (e) {
1112 // 🍂namespace Editable
1113 // 🍂event editable:drag: Event
1114 // Fired when a path feature is being dragged.
1115 this.fireAndForward('editable:drag', e);
1118 onDragEnd: function (e) {
1119 // 🍂namespace Editable
1120 // 🍂event editable:dragend: Event
1121 // Fired after a path feature has been dragged.
1122 this.fireAndForward('editable:dragend', e);
1127 // 🍂namespace Editable; 🍂class MarkerEditor; 🍂aka L.Editable.MarkerEditor
1128 // 🍂inherits BaseEditor
1129 // Editor for Marker.
1130 L$$1.Editable.MarkerEditor = L$$1.Editable.BaseEditor.extend({
1132 onDrawingMouseMove: function (e) {
1133 L$$1.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
1134 if (this._drawing) this.feature.setLatLng(e.latlng);
1137 processDrawingClick: function (e) {
1138 // 🍂namespace Editable
1139 // 🍂section Drawing events
1140 // 🍂event editable:drawing:clicked: Event
1141 // Fired when user `click` while drawing, after all internal actions.
1142 this.fireAndForward('editable:drawing:clicked', e);
1143 this.commitDrawing(e);
1146 connect: function (e) {
1147 // On touch, the latlng has not been updated because there is
1149 if (e) this.feature._latlng = e.latlng;
1150 L$$1.Editable.BaseEditor.prototype.connect.call(this, e);
1155 // 🍂namespace Editable; 🍂class PathEditor; 🍂aka L.Editable.PathEditor
1156 // 🍂inherits BaseEditor
1157 // Base class for all path editors.
1158 L$$1.Editable.PathEditor = L$$1.Editable.BaseEditor.extend({
1163 addHooks: function () {
1164 L$$1.Editable.BaseEditor.prototype.addHooks.call(this);
1165 if (this.feature) this.initVertexMarkers();
1169 initVertexMarkers: function (latlngs) {
1170 if (!this.enabled()) return;
1171 latlngs = latlngs || this.getLatLngs();
1172 if (isFlat(latlngs)) this.addVertexMarkers(latlngs);
1173 else for (var i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]);
1176 getLatLngs: function () {
1177 return this.feature.getLatLngs();
1181 // Rebuild edit elements (Vertex, MiddleMarker, etc.).
1182 reset: function () {
1183 this.editLayer.clearLayers();
1184 this.initVertexMarkers();
1187 addVertexMarker: function (latlng, latlngs) {
1188 return new this.tools.options.vertexMarkerClass(latlng, latlngs, this);
1191 onNewVertex: function (vertex) {
1192 // 🍂namespace Editable
1193 // 🍂section Vertex events
1194 // 🍂event editable:vertex:new: VertexEvent
1195 // Fired when a new vertex is created.
1196 this.fireAndForward('editable:vertex:new', {latlng: vertex.latlng, vertex: vertex});
1199 addVertexMarkers: function (latlngs) {
1200 for (var i = 0; i < latlngs.length; i++) {
1201 this.addVertexMarker(latlngs[i], latlngs);
1205 refreshVertexMarkers: function (latlngs) {
1206 latlngs = latlngs || this.getDefaultLatLngs();
1207 for (var i = 0; i < latlngs.length; i++) {
1208 latlngs[i].__vertex.update();
1212 addMiddleMarker: function (left, right, latlngs) {
1213 return new this.tools.options.middleMarkerClass(left, right, latlngs, this);
1216 onVertexMarkerClick: function (e) {
1217 L$$1.Editable.makeCancellable(e);
1218 // 🍂namespace Editable
1219 // 🍂section Vertex events
1220 // 🍂event editable:vertex:click: CancelableVertexEvent
1221 // Fired when a `click` is issued on a vertex, before any internal action is being processed.
1222 this.fireAndForward('editable:vertex:click', e);
1223 if (e._cancelled) return;
1224 if (this.tools.drawing() && this.tools._drawingEditor !== this) return;
1225 var index = e.vertex.getIndex(), commit;
1226 if (e.originalEvent.ctrlKey) {
1227 this.onVertexMarkerCtrlClick(e);
1228 } else if (e.originalEvent.altKey) {
1229 this.onVertexMarkerAltClick(e);
1230 } else if (e.originalEvent.shiftKey) {
1231 this.onVertexMarkerShiftClick(e);
1232 } else if (e.originalEvent.metaKey) {
1233 this.onVertexMarkerMetaKeyClick(e);
1234 } else if (index === e.vertex.getLastIndex() && this._drawing === L$$1.Editable.FORWARD) {
1235 if (index >= this.MIN_VERTEX - 1) commit = true;
1236 } else if (index === 0 && this._drawing === L$$1.Editable.BACKWARD && this._drawnLatLngs.length >= this.MIN_VERTEX) {
1238 } else if (index === 0 && this._drawing === L$$1.Editable.FORWARD && this._drawnLatLngs.length >= this.MIN_VERTEX && this.CLOSED) {
1239 commit = true; // Allow to close on first point also for polygons
1241 this.onVertexRawMarkerClick(e);
1243 // 🍂namespace Editable
1244 // 🍂section Vertex events
1245 // 🍂event editable:vertex:clicked: VertexEvent
1246 // Fired when a `click` is issued on a vertex, after all internal actions.
1247 this.fireAndForward('editable:vertex:clicked', e);
1248 if (commit) this.commitDrawing(e);
1251 onVertexRawMarkerClick: function (e) {
1252 // 🍂namespace Editable
1253 // 🍂section Vertex events
1254 // 🍂event editable:vertex:rawclick: CancelableVertexEvent
1255 // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode.
1256 this.fireAndForward('editable:vertex:rawclick', e);
1257 if (e._cancelled) return;
1258 if (!this.vertexCanBeDeleted(e.vertex)) return;
1262 vertexCanBeDeleted: function (vertex) {
1263 return vertex.latlngs.length > this.MIN_VERTEX;
1266 onVertexDeleted: function (e) {
1267 // 🍂namespace Editable
1268 // 🍂section Vertex events
1269 // 🍂event editable:vertex:deleted: VertexEvent
1270 // Fired after a vertex has been deleted by user.
1271 this.fireAndForward('editable:vertex:deleted', e);
1274 onVertexMarkerCtrlClick: function (e) {
1275 // 🍂namespace Editable
1276 // 🍂section Vertex events
1277 // 🍂event editable:vertex:ctrlclick: VertexEvent
1278 // Fired when a `click` with `ctrlKey` is issued on a vertex.
1279 this.fireAndForward('editable:vertex:ctrlclick', e);
1282 onVertexMarkerShiftClick: function (e) {
1283 // 🍂namespace Editable
1284 // 🍂section Vertex events
1285 // 🍂event editable:vertex:shiftclick: VertexEvent
1286 // Fired when a `click` with `shiftKey` is issued on a vertex.
1287 this.fireAndForward('editable:vertex:shiftclick', e);
1290 onVertexMarkerMetaKeyClick: function (e) {
1291 // 🍂namespace Editable
1292 // 🍂section Vertex events
1293 // 🍂event editable:vertex:metakeyclick: VertexEvent
1294 // Fired when a `click` with `metaKey` is issued on a vertex.
1295 this.fireAndForward('editable:vertex:metakeyclick', e);
1298 onVertexMarkerAltClick: function (e) {
1299 // 🍂namespace Editable
1300 // 🍂section Vertex events
1301 // 🍂event editable:vertex:altclick: VertexEvent
1302 // Fired when a `click` with `altKey` is issued on a vertex.
1303 this.fireAndForward('editable:vertex:altclick', e);
1306 onVertexMarkerContextMenu: function (e) {
1307 // 🍂namespace Editable
1308 // 🍂section Vertex events
1309 // 🍂event editable:vertex:contextmenu: VertexEvent
1310 // Fired when a `contextmenu` is issued on a vertex.
1311 this.fireAndForward('editable:vertex:contextmenu', e);
1314 onVertexMarkerMouseDown: function (e) {
1315 // 🍂namespace Editable
1316 // 🍂section Vertex events
1317 // 🍂event editable:vertex:mousedown: VertexEvent
1318 // Fired when user `mousedown` a vertex.
1319 this.fireAndForward('editable:vertex:mousedown', e);
1322 onVertexMarkerMouseOver: function (e) {
1323 // 🍂namespace Editable
1324 // 🍂section Vertex events
1325 // 🍂event editable:vertex:mouseover: VertexEvent
1326 // Fired when a user's mouse enters the vertex
1327 this.fireAndForward('editable:vertex:mouseover', e);
1330 onVertexMarkerMouseOut: function (e) {
1331 // 🍂namespace Editable
1332 // 🍂section Vertex events
1333 // 🍂event editable:vertex:mouseout: VertexEvent
1334 // Fired when a user's mouse leaves the vertex
1335 this.fireAndForward('editable:vertex:mouseout', e);
1338 onMiddleMarkerMouseDown: function (e) {
1339 // 🍂namespace Editable
1340 // 🍂section MiddleMarker events
1341 // 🍂event editable:middlemarker:mousedown: VertexEvent
1342 // Fired when user `mousedown` a middle marker.
1343 this.fireAndForward('editable:middlemarker:mousedown', e);
1346 onVertexMarkerDrag: function (e) {
1348 if (this.feature._bounds) this.extendBounds(e);
1349 // 🍂namespace Editable
1350 // 🍂section Vertex events
1351 // 🍂event editable:vertex:drag: VertexEvent
1352 // Fired when a vertex is dragged by user.
1353 this.fireAndForward('editable:vertex:drag', e);
1356 onVertexMarkerDragStart: function (e) {
1357 // 🍂namespace Editable
1358 // 🍂section Vertex events
1359 // 🍂event editable:vertex:dragstart: VertexEvent
1360 // Fired before a vertex is dragged by user.
1361 this.fireAndForward('editable:vertex:dragstart', e);
1364 onVertexMarkerDragEnd: function (e) {
1365 // 🍂namespace Editable
1366 // 🍂section Vertex events
1367 // 🍂event editable:vertex:dragend: VertexEvent
1368 // Fired after a vertex is dragged by user.
1369 this.fireAndForward('editable:vertex:dragend', e);
1372 setDrawnLatLngs: function (latlngs) {
1373 this._drawnLatLngs = latlngs || this.getDefaultLatLngs();
1376 startDrawing: function () {
1377 if (!this._drawnLatLngs) this.setDrawnLatLngs();
1378 L$$1.Editable.BaseEditor.prototype.startDrawing.call(this);
1381 startDrawingForward: function () {
1382 this.startDrawing();
1385 endDrawing: function () {
1386 this.tools.detachForwardLineGuide();
1387 this.tools.detachBackwardLineGuide();
1388 if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) this.deleteShape(this._drawnLatLngs);
1389 L$$1.Editable.BaseEditor.prototype.endDrawing.call(this);
1390 delete this._drawnLatLngs;
1393 addLatLng: function (latlng) {
1394 if (this._drawing === L$$1.Editable.FORWARD) this._drawnLatLngs.push(latlng);
1395 else this._drawnLatLngs.unshift(latlng);
1396 this.feature._bounds.extend(latlng);
1397 var vertex = this.addVertexMarker(latlng, this._drawnLatLngs);
1398 this.onNewVertex(vertex);
1402 newPointForward: function (latlng) {
1403 this.addLatLng(latlng);
1404 this.tools.attachForwardLineGuide();
1405 this.tools.anchorForwardLineGuide(latlng);
1408 newPointBackward: function (latlng) {
1409 this.addLatLng(latlng);
1410 this.tools.anchorBackwardLineGuide(latlng);
1413 // 🍂namespace PathEditor
1415 // Programmatically add a point while drawing.
1416 push: function (latlng) {
1417 if (!latlng) return console.error('L.Editable.PathEditor.push expect a valid latlng as parameter');
1418 if (this._drawing === L$$1.Editable.FORWARD) this.newPointForward(latlng);
1419 else this.newPointBackward(latlng);
1422 removeLatLng: function (latlng) {
1423 latlng.__vertex.delete();
1427 // 🍂method pop(): L.LatLng or null
1428 // Programmatically remove last point (if any) while drawing.
1430 if (this._drawnLatLngs.length <= 1) return;
1432 if (this._drawing === L$$1.Editable.FORWARD) latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1];
1433 else latlng = this._drawnLatLngs[0];
1434 this.removeLatLng(latlng);
1435 if (this._drawing === L$$1.Editable.FORWARD) this.tools.anchorForwardLineGuide(this._drawnLatLngs[this._drawnLatLngs.length - 1]);
1436 else this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]);
1440 processDrawingClick: function (e) {
1441 if (e.vertex && e.vertex.editor === this) return;
1442 if (this._drawing === L$$1.Editable.FORWARD) this.newPointForward(e.latlng);
1443 else this.newPointBackward(e.latlng);
1444 this.fireAndForward('editable:drawing:clicked', e);
1447 onDrawingMouseMove: function (e) {
1448 L$$1.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
1449 if (this._drawing) {
1450 this.tools.moveForwardLineGuide(e.latlng);
1451 this.tools.moveBackwardLineGuide(e.latlng);
1455 refresh: function () {
1456 this.feature.redraw();
1460 // 🍂namespace PathEditor
1461 // 🍂method newShape(latlng?: L.LatLng)
1462 // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it;
1463 // if optional `latlng` is given, start a path at this point.
1464 newShape: function (latlng) {
1465 var shape = this.addNewEmptyShape();
1467 this.setDrawnLatLngs(shape[0] || shape); // Polygon or polyline
1468 this.startDrawingForward();
1469 // 🍂namespace Editable
1470 // 🍂section Shape events
1471 // 🍂event editable:shape:new: ShapeEvent
1472 // Fired when a new shape is created in a multi (Polygon or Polyline).
1473 this.fireAndForward('editable:shape:new', {shape: shape});
1474 if (latlng) this.newPointForward(latlng);
1477 deleteShape: function (shape, latlngs) {
1478 var e = {shape: shape};
1479 L$$1.Editable.makeCancellable(e);
1480 // 🍂namespace Editable
1481 // 🍂section Shape events
1482 // 🍂event editable:shape:delete: CancelableShapeEvent
1483 // Fired before a new shape is deleted in a multi (Polygon or Polyline).
1484 this.fireAndForward('editable:shape:delete', e);
1485 if (e._cancelled) return;
1486 shape = this._deleteShape(shape, latlngs);
1487 if (this.ensureNotFlat) this.ensureNotFlat(); // Polygon.
1488 this.feature.setLatLngs(this.getLatLngs()); // Force bounds reset.
1491 // 🍂namespace Editable
1492 // 🍂section Shape events
1493 // 🍂event editable:shape:deleted: ShapeEvent
1494 // Fired after a new shape is deleted in a multi (Polygon or Polyline).
1495 this.fireAndForward('editable:shape:deleted', {shape: shape});
1499 _deleteShape: function (shape, latlngs) {
1500 latlngs = latlngs || this.getLatLngs();
1501 if (!latlngs.length) return;
1503 inplaceDelete = function (latlngs, shape) {
1504 // Called when deleting a flat latlngs
1505 shape = latlngs.splice(0, Number.MAX_VALUE);
1508 spliceDelete = function (latlngs, shape) {
1509 // Called when removing a latlngs inside an array
1510 latlngs.splice(latlngs.indexOf(shape), 1);
1511 if (!latlngs.length) self._deleteShape(latlngs);
1514 if (latlngs === shape) return inplaceDelete(latlngs, shape);
1515 for (var i = 0; i < latlngs.length; i++) {
1516 if (latlngs[i] === shape) return spliceDelete(latlngs, shape);
1517 else if (latlngs[i].indexOf(shape) !== -1) return spliceDelete(latlngs[i], shape);
1521 // 🍂namespace PathEditor
1522 // 🍂method deleteShapeAt(latlng: L.LatLng): Array
1523 // Remove a path shape at the given `latlng`.
1524 deleteShapeAt: function (latlng) {
1525 var shape = this.feature.shapeAt(latlng);
1526 if (shape) return this.deleteShape(shape);
1529 // 🍂method appendShape(shape: Array)
1530 // Append a new shape to the Polygon or Polyline.
1531 appendShape: function (shape) {
1532 this.insertShape(shape);
1535 // 🍂method prependShape(shape: Array)
1536 // Prepend a new shape to the Polygon or Polyline.
1537 prependShape: function (shape) {
1538 this.insertShape(shape, 0);
1541 // 🍂method insertShape(shape: Array, index: int)
1542 // Insert a new shape to the Polygon or Polyline at given index (default is to append).
1543 insertShape: function (shape, index) {
1545 shape = this.formatShape(shape);
1546 if (typeof index === 'undefined') index = this.feature._latlngs.length;
1547 this.feature._latlngs.splice(index, 0, shape);
1548 this.feature.redraw();
1549 if (this._enabled) this.reset();
1552 extendBounds: function (e) {
1553 this.feature._bounds.extend(e.vertex.latlng);
1556 onDragStart: function (e) {
1557 this.editLayer.clearLayers();
1558 L$$1.Editable.BaseEditor.prototype.onDragStart.call(this, e);
1561 onDragEnd: function (e) {
1562 this.initVertexMarkers();
1563 L$$1.Editable.BaseEditor.prototype.onDragEnd.call(this, e);
1568 // 🍂namespace Editable; 🍂class PolylineEditor; 🍂aka L.Editable.PolylineEditor
1569 // 🍂inherits PathEditor
1570 L$$1.Editable.PolylineEditor = L$$1.Editable.PathEditor.extend({
1572 startDrawingBackward: function () {
1573 this._drawing = L$$1.Editable.BACKWARD;
1574 this.startDrawing();
1577 // 🍂method continueBackward(latlngs?: Array)
1578 // Set up drawing tools to continue the line backward.
1579 continueBackward: function (latlngs) {
1580 if (this.drawing()) return;
1581 latlngs = latlngs || this.getDefaultLatLngs();
1582 this.setDrawnLatLngs(latlngs);
1583 if (latlngs.length > 0) {
1584 this.tools.attachBackwardLineGuide();
1585 this.tools.anchorBackwardLineGuide(latlngs[0]);
1587 this.startDrawingBackward();
1590 // 🍂method continueForward(latlngs?: Array)
1591 // Set up drawing tools to continue the line forward.
1592 continueForward: function (latlngs) {
1593 if (this.drawing()) return;
1594 latlngs = latlngs || this.getDefaultLatLngs();
1595 this.setDrawnLatLngs(latlngs);
1596 if (latlngs.length > 0) {
1597 this.tools.attachForwardLineGuide();
1598 this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]);
1600 this.startDrawingForward();
1603 getDefaultLatLngs: function (latlngs) {
1604 latlngs = latlngs || this.feature._latlngs;
1605 if (!latlngs.length || latlngs[0] instanceof L$$1.LatLng) return latlngs;
1606 else return this.getDefaultLatLngs(latlngs[0]);
1609 ensureMulti: function () {
1610 if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) {
1611 this.feature._latlngs = [this.feature._latlngs];
1615 addNewEmptyShape: function () {
1616 if (this.feature._latlngs.length) {
1618 this.appendShape(shape);
1621 return this.feature._latlngs;
1625 formatShape: function (shape) {
1626 if (isFlat(shape)) return shape;
1627 else if (shape[0]) return this.formatShape(shape[0]);
1630 // 🍂method splitShape(latlngs?: Array, index: int)
1631 // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`.
1632 splitShape: function (shape, index) {
1633 if (!index || index >= shape.length - 1) return;
1635 var shapeIndex = this.feature._latlngs.indexOf(shape);
1636 if (shapeIndex === -1) return;
1637 var first = shape.slice(0, index + 1),
1638 second = shape.slice(index);
1639 // We deal with reference, we don't want twice the same latlng around.
1640 second[0] = L$$1.latLng(second[0].lat, second[0].lng, second[0].alt);
1641 this.feature._latlngs.splice(shapeIndex, 1, first, second);
1648 // 🍂namespace Editable; 🍂class PolygonEditor; 🍂aka L.Editable.PolygonEditor
1649 // 🍂inherits PathEditor
1650 L$$1.Editable.PolygonEditor = L$$1.Editable.PathEditor.extend({
1655 newPointForward: function (latlng) {
1656 L$$1.Editable.PathEditor.prototype.newPointForward.call(this, latlng);
1657 if (!this.tools.backwardLineGuide._latlngs.length) this.tools.anchorBackwardLineGuide(latlng);
1658 if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide();
1661 addNewEmptyHole: function (latlng) {
1662 this.ensureNotFlat();
1663 var latlngs = this.feature.shapeAt(latlng);
1664 if (!latlngs) return;
1666 latlngs.push(holes);
1670 // 🍂method newHole(latlng?: L.LatLng, index: int)
1671 // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created.
1672 newHole: function (latlng) {
1673 var holes = this.addNewEmptyHole(latlng);
1675 this.setDrawnLatLngs(holes);
1676 this.startDrawingForward();
1677 if (latlng) this.newPointForward(latlng);
1680 addNewEmptyShape: function () {
1681 if (this.feature._latlngs.length && this.feature._latlngs[0].length) {
1683 this.appendShape(shape);
1686 return this.feature._latlngs;
1690 ensureMulti: function () {
1691 if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) {
1692 this.feature._latlngs = [this.feature._latlngs];
1696 ensureNotFlat: function () {
1697 if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs];
1700 vertexCanBeDeleted: function (vertex) {
1701 var parent = this.feature.parentShape(vertex.latlngs),
1702 idx = L$$1.Util.indexOf(parent, vertex.latlngs);
1703 if (idx > 0) return true; // Holes can be totally deleted without removing the layer itself.
1704 return L$$1.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex);
1707 getDefaultLatLngs: function () {
1708 if (!this.feature._latlngs.length) this.feature._latlngs.push([]);
1709 return this.feature._latlngs[0];
1712 formatShape: function (shape) {
1713 // [[1, 2], [3, 4]] => must be nested
1714 // [] => must be nested
1715 // [[]] => is already nested
1716 if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape];
1722 // 🍂namespace Editable; 🍂class RectangleEditor; 🍂aka L.Editable.RectangleEditor
1723 // 🍂inherits PathEditor
1724 L$$1.Editable.RectangleEditor = L$$1.Editable.PathEditor.extend({
1730 skipMiddleMarkers: true
1733 extendBounds: function (e) {
1734 var index = e.vertex.getIndex(),
1735 next = e.vertex.getNext(),
1736 previous = e.vertex.getPrevious(),
1737 oppositeIndex = (index + 2) % 4,
1738 opposite = e.vertex.latlngs[oppositeIndex],
1739 bounds = new L$$1.LatLngBounds(e.latlng, opposite);
1740 // Update latlngs by hand to preserve order.
1741 previous.latlng.update([e.latlng.lat, opposite.lng]);
1742 next.latlng.update([opposite.lat, e.latlng.lng]);
1743 this.updateBounds(bounds);
1744 this.refreshVertexMarkers();
1747 onDrawingMouseDown: function (e) {
1748 L$$1.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
1750 var latlngs = this.getDefaultLatLngs();
1751 // L.Polygon._convertLatLngs removes last latlng if it equals first point,
1752 // which is the case here as all latlngs are [0, 0]
1753 if (latlngs.length === 3) latlngs.push(e.latlng);
1754 var bounds = new L$$1.LatLngBounds(e.latlng, e.latlng);
1755 this.updateBounds(bounds);
1756 this.updateLatLngs(bounds);
1759 // Stop dragging map.
1760 // L.Draggable has two workflows:
1761 // - mousedown => mousemove => mouseup
1762 // - touchstart => touchmove => touchend
1763 // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only
1764 // can deal with mousedown, but then when in a touch device, we are dealing with
1765 // simulated events (actually simulated by L.Map.Tap), which are no more taken
1766 // into account by L.Draggable.
1767 // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103
1768 e.originalEvent._simulated = false;
1769 this.map.dragging._draggable._onUp(e.originalEvent);
1770 // Now transfer ongoing drag action to the bottom right corner.
1771 // Should we refine which corner will handle the drag according to
1773 latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent);
1776 onDrawingMouseUp: function (e) {
1777 this.commitDrawing(e);
1778 e.originalEvent._simulated = false;
1779 L$$1.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
1782 onDrawingMouseMove: function (e) {
1783 e.originalEvent._simulated = false;
1784 L$$1.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
1788 getDefaultLatLngs: function (latlngs) {
1789 return latlngs || this.feature._latlngs[0];
1792 updateBounds: function (bounds) {
1793 this.feature._bounds = bounds;
1796 updateLatLngs: function (bounds) {
1797 var latlngs = this.getDefaultLatLngs(),
1798 newLatlngs = this.feature._boundsToLatLngs(bounds);
1800 for (var i = 0; i < latlngs.length; i++) {
1801 latlngs[i].update(newLatlngs[i]);
1807 // 🍂namespace Editable; 🍂class CircleEditor; 🍂aka L.Editable.CircleEditor
1808 // 🍂inherits PathEditor
1809 L$$1.Editable.CircleEditor = L$$1.Editable.PathEditor.extend({
1814 skipMiddleMarkers: true
1817 initialize: function (map, feature, options) {
1818 L$$1.Editable.PathEditor.prototype.initialize.call(this, map, feature, options);
1819 this._resizeLatLng = this.computeResizeLatLng();
1822 computeResizeLatLng: function () {
1823 // While circle is not added to the map, _radius is not set.
1824 var delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4),
1825 point = this.map.project(this.feature._latlng);
1826 return this.map.unproject([point.x + delta, point.y - delta]);
1829 updateResizeLatLng: function () {
1830 this._resizeLatLng.update(this.computeResizeLatLng());
1831 this._resizeLatLng.__vertex.update();
1834 getLatLngs: function () {
1835 return [this.feature._latlng, this._resizeLatLng];
1838 getDefaultLatLngs: function () {
1839 return this.getLatLngs();
1842 onVertexMarkerDrag: function (e) {
1843 if (e.vertex.getIndex() === 1) this.resize(e);
1844 else this.updateResizeLatLng(e);
1845 L$$1.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e);
1848 resize: function (e) {
1849 var radius = this.feature._latlng.distanceTo(e.latlng);
1850 this.feature.setRadius(radius);
1853 onDrawingMouseDown: function (e) {
1854 L$$1.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
1855 this._resizeLatLng.update(e.latlng);
1856 this.feature._latlng.update(e.latlng);
1858 // Stop dragging map.
1859 e.originalEvent._simulated = false;
1860 this.map.dragging._draggable._onUp(e.originalEvent);
1861 // Now transfer ongoing drag action to the radius handler.
1862 this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent);
1865 onDrawingMouseUp: function (e) {
1866 this.commitDrawing(e);
1867 e.originalEvent._simulated = false;
1868 L$$1.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
1871 onDrawingMouseMove: function (e) {
1872 e.originalEvent._simulated = false;
1873 L$$1.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
1876 onDrag: function (e) {
1877 L$$1.Editable.PathEditor.prototype.onDrag.call(this, e);
1878 this.feature.dragging.updateLatLng(this._resizeLatLng);
1883 // 🍂namespace Editable; 🍂class EditableMixin
1884 // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle`
1885 // and `L.Marker`. It adds some methods to them.
1886 // *When editing is enabled, the editor is accessible on the instance with the
1887 // `editor` property.*
1888 var EditableMixin = {
1890 createEditor: function (map) {
1891 map = map || this._map;
1892 var tools = (this.options.editOptions || {}).editTools || map.editTools;
1893 if (!tools) throw Error('Unable to detect Editable instance.');
1894 var Klass = this.options.editorClass || this.getEditorClass(tools);
1895 return new Klass(map, this, this.options.editOptions);
1898 // 🍂method enableEdit(map?: L.Map): this.editor
1899 // Enable editing, by creating an editor if not existing, and then calling `enable` on it.
1900 enableEdit: function (map) {
1901 if (!this.editor) this.createEditor(map);
1902 this.editor.enable();
1906 // 🍂method editEnabled(): boolean
1907 // Return true if current instance has an editor attached, and this editor is enabled.
1908 editEnabled: function () {
1909 return this.editor && this.editor.enabled();
1912 // 🍂method disableEdit()
1913 // Disable editing, also remove the editor property reference.
1914 disableEdit: function () {
1916 this.editor.disable();
1921 // 🍂method toggleEdit()
1922 // Enable or disable editing, according to current status.
1923 toggleEdit: function () {
1924 if (this.editEnabled()) this.disableEdit();
1925 else this.enableEdit();
1928 _onEditableAdd: function () {
1929 if (this.editor) this.enableEdit();
1934 var PolylineMixin = {
1936 getEditorClass: function (tools) {
1937 return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L$$1.Editable.PolylineEditor;
1940 shapeAt: function (latlng, latlngs) {
1941 // We can have those cases:
1942 // - latlngs are just a flat array of latlngs, use this
1943 // - latlngs is an array of arrays of latlngs, loop over
1945 latlngs = latlngs || this._latlngs;
1946 if (!latlngs.length) return shape;
1947 else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
1948 else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i];
1952 isInLatLngs: function (l, latlngs) {
1953 if (!latlngs) return false;
1954 var i, k, len, part = [], p,
1955 w = this._clickTolerance();
1956 this._projectLatlngs(latlngs, part, this._pxBounds);
1958 p = this._map.latLngToLayerPoint(l);
1960 if (!this._pxBounds.contains(p)) { return false; }
1961 for (i = 1, len = part.length, k = 0; i < len; k = i++) {
1963 if (L$$1.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) {
1972 var PolygonMixin = {
1974 getEditorClass: function (tools) {
1975 return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L$$1.Editable.PolygonEditor;
1978 shapeAt: function (latlng, latlngs) {
1979 // We can have those cases:
1980 // - latlngs are just a flat array of latlngs, use this
1981 // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first
1982 // - latlngs is an array of arrays of arrays, this is a multi, loop over
1984 latlngs = latlngs || this._latlngs;
1985 if (!latlngs.length) return shape;
1986 else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
1987 else if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs;
1988 else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i];
1992 isInLatLngs: function (l, latlngs) {
1993 var inside = false, l1, l2, j, k, len2;
1995 for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) {
1999 if (((l1.lat > l.lat) !== (l2.lat > l.lat)) &&
2000 (l.lng < (l2.lng - l1.lng) * (l.lat - l1.lat) / (l2.lat - l1.lat) + l1.lng)) {
2008 parentShape: function (shape, latlngs) {
2009 latlngs = latlngs || this._latlngs;
2010 if (!latlngs) return;
2011 var idx = L$$1.Util.indexOf(latlngs, shape);
2012 if (idx !== -1) return latlngs;
2013 for (var i = 0; i < latlngs.length; i++) {
2014 idx = L$$1.Util.indexOf(latlngs[i], shape);
2015 if (idx !== -1) return latlngs[i];
2024 getEditorClass: function (tools) {
2025 return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L$$1.Editable.MarkerEditor;
2030 var RectangleMixin = {
2032 getEditorClass: function (tools) {
2033 return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L$$1.Editable.RectangleEditor;
2040 getEditorClass: function (tools) {
2041 return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L$$1.Editable.CircleEditor;
2046 var keepEditable = function () {
2047 // Make sure you can remove/readd an editable layer.
2048 this.on('add', this._onEditableAdd);
2051 var isFlat = L$$1.LineUtil.isFlat || L$$1.LineUtil._flat || L$$1.Polyline._flat; // <=> 1.1 compat.
2054 if (L$$1.Polyline) {
2055 L$$1.Polyline.include(EditableMixin);
2056 L$$1.Polyline.include(PolylineMixin);
2057 L$$1.Polyline.addInitHook(keepEditable);
2060 L$$1.Polygon.include(EditableMixin);
2061 L$$1.Polygon.include(PolygonMixin);
2064 L$$1.Marker.include(EditableMixin);
2065 L$$1.Marker.include(MarkerMixin);
2066 L$$1.Marker.addInitHook(keepEditable);
2068 if (L$$1.Rectangle) {
2069 L$$1.Rectangle.include(EditableMixin);
2070 L$$1.Rectangle.include(RectangleMixin);
2073 L$$1.Circle.include(EditableMixin);
2074 L$$1.Circle.include(CircleMixin);
2077 L$$1.LatLng.prototype.update = function (latlng) {
2078 latlng = L$$1.latLng(latlng);
2079 this.lat = latlng.lat;
2080 this.lng = latlng.lng;
2085 function applyDefaultPlot (FieldMap) {
2086 FieldMap.prototype.defaultPlot = function (row, col, plotWidth, plotLength) {
2087 plotWidth = plotWidth || this.opts.defaultPlotWidth;
2088 plotLength = plotLength || this.opts.defaultPlotWidth;
2089 var o = turf.point(this.opts.defaultPos);
2090 var tl = turf.destination(
2095 {'units': 'kilometers'}
2099 {'units': 'kilometers'}
2101 var br = turf.destination(
2106 {'units': 'kilometers'}
2110 {'units': 'kilometers'}
2112 var tr = turf.point([tl.geometry.coordinates[0], br.geometry.coordinates[1]]);
2113 var bl = turf.point([br.geometry.coordinates[0], tl.geometry.coordinates[1]]);
2114 return turf.polygon([
2115 [tl, tr, br, bl, tl].map(turf.getCoord)
2120 const NO_POLYGON_ERROR = "Please select the area that contain the plots";
2122 const DEFAULT_OPTS = {
2124 brapi_pageSize: 1000,
2125 brapi_levelName: 'plot',
2137 url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}?blankTile=false',
2139 attribution: '© <a href="http://www.esri.com/">Esri</a>, DigitalGlobe, GeoEye, i-cubed, USDA FSA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community',
2147 constructor(map_container, brapi_endpoint, opts = {}) {
2148 this.map_container = d3.select(map_container).style("background-color", "#888");
2149 this.brapi_endpoint = brapi_endpoint;
2152 this.opts = Object.assign(Object.create(DEFAULT_OPTS), opts);
2153 this.map = L.map(this.map_container.node(), {editable: true}).setView(this.opts.defaultPos, 2);
2154 this.map.on('preclick', ()=>{
2155 if (this.editablePolygon) this.finishTranslate();
2156 if (this.editablePlot) this.finishPlotEdition();
2159 this.tilelayer = L.tileLayer.fallback(this.opts.tileLayer.url, this.opts.tileLayer.options).addTo(this.map);
2161 L.EditControl = L.Control.extend({
2163 position: 'topleft',
2168 onAdd: function (map) {
2169 var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
2170 link = L.DomUtil.create('a', '', container);
2172 link.title = this.options.title;
2173 link.innerHTML = this.options.html;
2174 L.DomEvent.on(link, 'click', L.DomEvent.stop)
2175 .on(link, 'click', function () {
2176 window.LAYER = this.options.callback.call(map.editTools);
2183 L.NewPolygonControl = L.EditControl.extend({
2185 position: 'topleft',
2186 callback: function () {
2187 self.polygon = self.map.editTools.startPolygon();
2188 return self.polygon;
2190 title: 'Creates a new polygon',
2191 html: String.fromCodePoint(0x25B1)
2194 L.NewRectangleControl = L.EditControl.extend({
2196 position: 'topleft',
2197 callback: function () {
2198 self.polygon = self.map.editTools.startRectangle();
2199 return self.polygon;
2201 title: 'Creates a new rectangle',
2202 html: String.fromCodePoint(0x25AD)
2205 L.NewClearControl = L.EditControl.extend({
2207 position: 'topleft',
2208 callback: function () {
2209 self.map.editTools.featuresLayer.clearLayers();
2211 title: 'Clears all polygons',
2212 html: String.fromCodePoint(0x1F6AB)
2216 // Add additional map controls if NOT view only
2217 if ( !this.opts.viewOnly ) {
2218 this.map.addControl(new L.Control.Search({
2219 url: 'https://nominatim.openstreetmap.org/search?format=json&q={s}',
2220 jsonpParam: 'json_callback',
2221 propertyName: 'display_name',
2222 propertyLoc: ['lat', 'lon'],
2227 zoom: this.opts.normalZoom
2230 this.polygonControl = new L.NewPolygonControl();
2231 this.rectangleControl = new L.NewRectangleControl();
2232 this.clearPolygonsControl = new L.NewClearControl();
2234 this.map.addControl(this.polygonControl);
2235 this.map.addControl(this.rectangleControl);
2236 this.map.addControl(this.clearPolygonsControl);
2239 this.info = this.map_container.append("div")
2240 .style("bottom","5px")
2241 .style("left","5px")
2242 .style("position","absolute")
2243 .style("z-index",2000)
2244 .style("pointer-events","none")
2245 .style("background", "white")
2246 .style("border-radius", "5px");
2248 this.missing_plots = this.map_container.append("div")
2249 .style("bottom","5px")
2250 .style("right","5px")
2251 .style("position","absolute")
2252 .style("z-index", 2000)
2253 .style("background", "#DC3545")
2254 .style("color", "#fff")
2255 .style("border-radius", "5px")
2256 .style("padding", "10px")
2257 .style("font-weight", "bold")
2258 .style("display", "none");
2260 this.loading = this.map_container.append("div")
2263 .style("position","absolute")
2264 .style("z-index", 500)
2265 .style("width", "100%")
2266 .style("background", "#FFC107")
2267 .style("display", "none")
2268 .html("<p style='margin: 10px; text-align: center; font-weight: bold'>Loading Plots...</p>");
2270 this.onLoading = (loading) => {
2271 this.loading.style("display", loading ? 'block' : 'none');
2276 this.map.removeControl(this.polygonControl);
2277 this.map.removeControl(this.rectangleControl);
2278 this.map.removeControl(this.clearPolygonsControl);
2282 this.onLoading(true);
2283 this.generatePlots(studyDbId);
2284 return this.data.then(()=>{
2286 this.onLoading(false);
2290 this.onLoading(false);
2295 if (this.plotsLayer) this.plotsLayer.remove();
2296 this.plotsLayer = L.featureGroup(this.plots.features.map((plot)=>{
2297 return L.geoJSON(turf.transformScale(plot, this.opts.plotScaleFactor), this.opts.style);
2298 })).on('contextmenu', (e)=>{
2299 if (this.editablePlot) {
2300 this.finishPlotEdition();
2302 this.enableEdition(e.sourceTarget);
2303 }).on('click', (e)=>{
2304 if ( !this.opts.viewOnly ) this.enableTransform(e.target);
2305 }).on('mousemove', (e)=>{
2306 let sourceTarget = e.sourceTarget;
2307 let ou = this.plot_map[sourceTarget.feature.properties.observationUnitDbId];
2308 get_oup_rel(ou).forEach((levels)=>{
2309 if(levels.levelName == 'replicate'){ ou.replicate = levels.levelCode;}
2310 else if(levels.levelName == 'block'){ ou.blockNumber = levels.levelCode;}
2311 else if(levels.levelName == 'plot'){ ou.plotNumber = levels.levelCode;}});
2312 this.info.html(`<div style="padding: 5px"><div>Germplasm: ${ou.germplasmName}</div>
2313 <div>Replicate: ${ou.replicate}</div>
2314 <div> Block: ${ou.blockNumber}</div>
2315 <div> Row,Col: ${ou._row},${ou._col}</div>
2316 <div> Plot #: ${ou.plotNumber}</div></div>`);
2317 }).on('mouseout', ()=>{
2322 enableEdition(plot) {
2323 this.editablePlot = plot;
2327 finishPlotEdition() {
2328 this.editablePlot.disableEdit();
2329 this.plots = turf.featureCollection(this.plots.features.map((plot)=>{
2330 if (plot.properties.observationUnitDbId == this.editablePlot.feature.properties.observationUnitDbId) {
2331 let geojson = this.editablePlot.toGeoJSON();
2332 plot = turf.convex(geojson);
2333 plot.properties = geojson.properties;
2337 this.editablePlot = null;
2340 enableTransform(plotGroup) {
2341 this.plotsLayer.remove();
2342 this.editablePolygon = L.polygon(Fieldmap.featureToL(turf.convex(plotGroup.toGeoJSON())),
2343 Object.assign({transform:true,draggable:true}, this.opts.style))
2344 .on('dragend', (e)=>{
2345 let target = e.target;
2346 let startPos = turf.center(this.plots);
2347 let endPos = turf.center(target.toGeoJSON());
2348 this.plots = turf.transformTranslate(this.plots,
2349 turf.distance(startPos, endPos),
2350 turf.bearing(startPos, endPos));
2351 this.finishTranslate();
2353 .on('scaleend', (e)=>{
2354 let target = e.target;
2355 let startPos = turf.center(this.plots);
2356 let endPos = turf.center(target.toGeoJSON());
2357 let startArea = turf.area(this.plots);
2358 let endArea = turf.area(target.toGeoJSON());
2359 let factor = Math.sqrt(endArea/startArea);
2360 this.plots = turf.featureCollection(this.plots.features.map((plot)=>{
2361 let startCoord = turf.getCoords(startPos);
2362 let plotCoord = turf.getCoords(turf.center(plot));
2363 let bearing = turf.bearing(startCoord,plotCoord);
2364 let distance = turf.distance(startCoord,plotCoord);
2365 // after resize, bearing to centroid of all plots is the same, but scaled by the resize factor
2366 let plotEndCoord = turf.getCoords(turf.destination(turf.getCoords(endPos),distance*factor,bearing));
2367 plot = turf.transformTranslate(plot,
2368 turf.distance(plotCoord, plotEndCoord),
2369 turf.bearing(plotCoord, plotEndCoord));
2370 plot = turf.transformScale(plot, factor);
2373 this.finishTranslate();
2375 .on('rotateend', (e)=>{
2376 this.plots = turf.transformRotate(this.plots, turf.radiansToDegrees(e.rotation));
2377 this.finishTranslate();
2380 this.editablePolygon.transform.enable();
2381 this.editablePolygon.dragging.enable();
2385 let polygon = this.editablePolygon;
2386 polygon.transform.disable();
2387 polygon.dragging.disable();
2390 this.editablePolygon.remove();
2391 this.editablePolygon = null;
2396 * Try to make the polygon vertical before calculating row and col,
2397 * by rotating it in an angle formed between the center and one of
2401 let cellSide = Math.sqrt(turf.area(this.geoJson))/1000/10/2;
2402 let grid = turf.pointGrid(turf.bbox(this.geoJson), cellSide, {mask: this.geoJson});
2403 let center = turf.getCoord(turf.centerOfMass(this.geoJson));
2404 let distances = grid.features.map(f=>turf.distance(center, turf.getCoord(f))).sort();
2405 let q3 = d3.quantile(distances, 0.75);
2406 let clusters = turf.clustersKmeans(turf.featureCollection(grid.features.filter((f)=>{
2407 return turf.distance(center,turf.getCoord(f)) > q3;
2408 })), {numberOfClusters: 2});
2409 let clusterCenters = [];
2410 turf.clusterEach(clusters, 'cluster', (cluster)=>{
2411 clusterCenters.push(turf.getCoord(turf.center(cluster)));
2413 let bearing = turf.bearing(center, this.northernmost(clusterCenters[0], clusterCenters[1]));
2414 this.rotation = 90-( (Math.round(Math.abs(bearing)) == 0 || Math.round(Math.abs(bearing)) == 90) ? 90:bearing);
2415 this.geoJson = turf.transformRotate(this.geoJson, this.rotation);
2419 return [].slice.call(arguments).sort((a,b)=>b[1] - a[1])[0];
2422 generatePlots(studyDbId) {
2423 return this.load_ObsUnits(studyDbId)
2425 this.plots = turf.featureCollection(data.plots.map(p=>{
2426 return Object.assign(p._geoJSON, {properties: {observationUnitDbId: p.observationUnitDbId}});
2428 if (!data.plots_shaped) {
2429 // rotate to original position
2430 this.plots = turf.transformRotate(this.plots, -this.rotation);
2432 this.fitBounds(this.plots);
2436 load_ObsUnits(studyDbId){
2437 this.new_data = true;
2438 this.data_parsed = 0;
2439 this.data_total = 0;
2440 if(this.data && this.data_parsed!=this.data_total){
2441 this.data.reject("New Load Started");
2444 var rawdata = new Promise((resolve,reject)=>{
2446 const brapi = BrAPI(this.brapi_endpoint, "2.0", this.opts.brapi_auth);
2447 var results = {'plots':[]};
2448 brapi.search_observationunits({
2449 "studyDbIds":[studyDbId],
2450 'pageSize':this.opts.brapi_pageSize,
2451 'observationLevels' : [{ "levelName" : this.opts.brapi_levelName }]
2454 ou.X = parseFloat(ou.X);
2455 ou.Y = parseFloat(ou.Y);
2456 if(ou.observationUnitPosition.observationLevel.levelName.toUpperCase() === "PLOT") results.plots.push(ou);
2457 this.data_parsed+=1;
2458 this.data_total = ou.__response.metadata.pagination.totalCount;
2463 results.plots = results.plots.reduce((acc,plot)=>{
2464 if(!this.plot_map[plot.observationUnitDbId]){
2465 this.plot_map[plot.observationUnitDbId] = plot;
2472 results.plots.sort(function(a,b){
2473 if(a.plotNumber!=b.plotNumber){
2474 return parseFloat(a.plotNumber)>parseFloat(b.plotNumber)?1:-1
2479 if(results.plots.length>0){
2480 results.blocks = d3.nest().key(plot=>get_oup(plot).blockNumber).entries(results.plots);
2481 results.reps = d3.nest().key(plot=>get_oup(plot).replicate).entries(results.plots);
2484 clearInterval(this.while_downloading);
2488 this.data = rawdata.then((d)=>this.shape(d));
2489 this.data.reject = rej;
2490 this.while_downloading = setInterval(()=>{
2491 var status = this.data_parsed+"/"+this.data_total;
2492 console.log(status);
2495 clearInterval(this.while_downloading);
2504 // Determine what information is available for each obsUnit
2505 data.plots.forEach((ou)=>{
2506 const oup = get_oup(ou);
2507 ou._X = ou.X || oup.positionCoordinateX;
2508 ou._Y = ou.Y || oup.positionCoordinateY;
2510 ou._geoJSON = (this.opts.useGeoJson && oup.geoCoordinates)
2514 if (!isNaN(ou._X) && !isNaN(ou._Y)){
2515 if(oup.positionCoordinateXType
2516 && oup.positionCoordinateYType){
2517 if(oup.positionCoordinateXType=="GRID_ROW" && oup.positionCoordinateYType=="GRID_COL"
2518 || oup.positionCoordinateXType=="GRID_COL" && oup.positionCoordinateYType=="GRID_ROW"){
2519 ou._row = oup.positionCoordinateYType=="GRID_ROW" ? parseInt(ou._Y) : parseInt(ou._X);
2520 ou._col = oup.positionCoordinateXType=="GRID_COL" ? parseInt(ou._X) : parseInt(ou._Y);
2522 if(oup.positionCoordinateXType=="LONGITUDE" && oup.positionCoordinateYType=="LATITUDE"){
2523 if(!ou._geoJSON) ou._geoJSON = turf.point([ou._X,ou._Y]);
2527 if(ou._X==Math.floor(ou._X) && ou._Y==Math.floor(ou._Y)){
2528 ou._row = parseInt(ou._Y);
2529 ou._col = parseInt(ou._X);
2533 if(!ou._geoJSON) ou._geoJSON = turf.point([ou._X,ou._Y]);
2540 ou._type = turf.getType(ou._geoJSON);
2543 ou._type = "invalid";
2547 ou._type = "missing";
2551 // Separate out plots with invalid / missing geojson
2552 if ( this.opts.viewOnly ) {
2553 const plots_invalid = data.plots.filter((e) => e._type === 'invalid' || e._type === 'missing');
2554 const plots_valid = data.plots.filter((e) => e._type !== 'invalid' && e._type !== 'missing');
2555 if ( plots_valid.length === 0 ) {
2556 let html = "This trial does not have any plots with geo coordinates assigned."
2557 this.missing_plots.style("display", "block");
2558 this.missing_plots.html(html);
2559 throw NO_POLYGON_ERROR;
2561 else if ( plots_invalid.length > 0 ) {
2562 let html = "Plots with no geo coordinates:";
2563 html += "<ul style='padding-left: 25px; margin-bottom: 0'>";
2564 plots_invalid.forEach((p) => html += `<li>${p.observationUnitName}</li>`);
2566 this.missing_plots.style("display", "block");
2567 this.missing_plots.html(html);
2569 data.plots = plots_valid;
2572 // Generate a reasonable plot layout if there is missing row/col data
2573 if( data.plots.some(plot=>isNaN(plot._row)||isNaN(plot._col)) ){
2574 var lyt_width = this.layout_width(
2575 Math.round(d3.median(data.blocks,block=>block.values.length)),
2578 data.plots.forEach((plot,pos)=>{
2579 let row = Math.floor(pos/lyt_width);
2580 let col = (pos%lyt_width);
2581 if (row%2==1) col = (lyt_width-1)-col;
2588 data.plots_shaped = false;
2589 if(data.plots.every(plot=>(plot._type=="Polygon"))){
2590 // Plot shapes already exist!
2591 data.plots_shaped = this.opts.useGeoJson;
2593 else if(data.plots.every(plot=>(plot._type=="Point"||plot._type=="Polygon"))){
2594 // Create plot shapes using centroid Voronoi
2595 var centroids = turf.featureCollection(data.plots.map((plot,pos)=>{
2596 return turf.centroid(plot._geoJSON)
2598 var scale_factor = 50; //prevents rounding errors
2599 var scale_origin = turf.centroid(centroids);
2600 centroids = turf.transformScale(centroids,scale_factor,{origin:scale_origin});
2601 var bbox = turf.envelope(centroids);
2602 var area = turf.area(bbox);
2603 var offset = -Math.sqrt(area/data.plots.length)/1000/2;
2604 var hull = turf.polygonToLine(turf.convex(centroids, {units: 'kilometers'}));
2605 var crop = turf.lineToPolygon(turf.lineOffset(hull, offset, {units: 'kilometers'}));
2606 var voronoiBox = turf.lineToPolygon(turf.polygonToLine(turf.envelope(crop)));
2607 var cells = turf.voronoi(centroids,{bbox:turf.bbox(voronoiBox)});
2608 var cells_cropped = turf.featureCollection(cells.features.map(cell=>turf.intersect(cell,crop)));
2609 cells_cropped = turf.transformScale(cells_cropped,1/scale_factor,{origin:scale_origin});
2610 data.plots.forEach((plot,i)=>{
2611 plot._geoJSON = cells_cropped.features[i];
2613 data.plots_shaped = this.opts.useGeoJson;
2616 let plot_XY_groups = {};
2617 // group by plots with the same X/Y
2618 data.plots.forEach(plot=>{
2619 plot_XY_groups[plot._col] = plot_XY_groups[plot._col] || {};
2620 plot_XY_groups[plot._col][plot._row] = plot_XY_groups[plot._col][plot._row] || {};
2621 plot_XY_groups[plot._col][plot._row]=[plot];
2624 if(!data.plots_shaped){
2625 if (!this.polygon || !turf.area(this.polygon.toGeoJSON())) {
2626 throw NO_POLYGON_ERROR;
2628 this.geoJson = this.polygon.toGeoJSON();
2629 this.polygon.remove();
2631 const bbox = turf.bbox(this.geoJson);
2632 this.opts.defaultPos = [bbox[0], bbox[3]];
2633 let plotLength = this.opts.plotLength/1000,
2634 plotWidth = this.opts.plotWidth/1000;
2635 const cols = Object.keys(plot_XY_groups).length,
2636 rows = Object.values(plot_XY_groups).reduce((acc, col)=>{
2637 Object.keys(col).forEach((row, i)=>{
2639 acc[i] = acc[i]+1 || 1;
2642 }, []).filter(x=>x).length;
2643 plotLength = plotLength || turf.length(turf.lineString([[bbox[0], bbox[1]], [bbox[0], bbox[3]]]))/rows;
2644 plotWidth = plotWidth || turf.length(turf.lineString([[bbox[0], bbox[1]], [bbox[2], bbox[1]]]))/cols;
2645 // Use default plot shapes/positions based on X/Y positions
2646 for (let X in plot_XY_groups) {
2647 if (plot_XY_groups.hasOwnProperty(X)) {
2648 for (let Y in plot_XY_groups[X]) {
2649 if (plot_XY_groups[X].hasOwnProperty(Y)) {
2652 let polygon = this.defaultPlot(Y-1, X-1, plotWidth, plotLength);
2653 // if for some reason plots have the same x/y, split that x/y region
2654 plot_XY_groups[X][Y].forEach((plot, i)=>{
2655 plot._geoJSON = this.splitPlot(polygon, plot_XY_groups[X][Y].length, i);
2666 fitBounds(feature) {
2667 let bbox = turf.bbox(feature);
2668 this.map.fitBounds([[bbox[1], bbox[0]], [bbox[3], bbox[2]]]);
2671 layout_width(median_block_length,number_of_plots){
2672 let bllen = median_block_length;
2673 let squarelen = Math.round(Math.sqrt(number_of_plots));
2675 if(squarelen==bllen){
2676 lyt_width = squarelen;
2678 else if (squarelen>bllen) {
2679 lyt_width = Math.round(squarelen/bllen)*bllen;
2682 let closest_up = (bllen%squarelen)/Math.floor(bllen/squarelen);
2683 let closest_down = (squarelen-bllen%squarelen)/Math.ceil(bllen/squarelen);
2684 lyt_width = Math.round(
2685 closest_up<=closest_down?
2686 squarelen+closest_up:
2687 squarelen-closest_down
2693 splitPlot(polygon,partitions,index){
2694 this.splitPlot_memo = this.splitPlot_memo || {};
2695 let memo_key = `(${partitions})${polygon.geometry.coordinates.join(",")}`;
2696 if(this.splitPlot_memo[memo_key]) return this.splitPlot_memo[memo_key][index];
2697 if(!partitions||partitions<2) return (this.splitPlot_memo[memo_key] = [polygon])[index];
2699 let scale_factor = 50; //prevents rounding errors
2700 let scale_origin = turf.getCoord(turf.centroid(polygon));
2701 polygon = turf.transformScale(polygon, scale_factor, {'origin':scale_origin});
2703 let row_width = Math.ceil(Math.sqrt(partitions));
2704 let row_counts = [];
2705 for (var i = 0; i < Math.floor(partitions/row_width); i++) {
2706 row_counts[i] = row_width;
2708 if(partitions%row_width) row_counts[row_counts.length] = partitions%row_width;
2710 let polygonbbox = turf.bbox(polygon);
2711 polygonbbox[0]-=0.00001; polygonbbox[1]-=0.00001; polygonbbox[2]+=0.00001; polygonbbox[3]+=0.00001;
2712 let w = Math.sqrt(turf.area(polygon))/1000;
2713 let area = 50+100*partitions;
2714 let grid_dist = w/Math.sqrt(area);
2715 let grid = turf.pointGrid(polygonbbox,grid_dist,{'mask':polygon});
2716 let points = grid.features;
2718 let points_per_part = Math.floor(points.length/partitions);
2720 let row_point_counts = row_counts.map(rc=>rc*points_per_part);
2722 points = points.sort((b,a)=>d3.ascending(turf.getCoord(a)[1],turf.getCoord(b)[1]));
2726 row_point_counts.forEach((rpc,i)=>{
2728 while (rows[i].length<rpc && t<points.length){
2729 rows[i].push(points[t++]);
2734 rows.forEach((row,ri)=>{
2735 row = row.sort((a,b)=>d3.ascending(turf.getCoord(a)[0],turf.getCoord(b)[0]));
2737 let c0 = collecs.length;
2738 for (var ci = c0; ci < c0+row_counts[ri]; ci++) {
2740 while (collecs[ci].length<points_per_part && p<row.length){
2741 collecs[ci].push(row[p++]);
2745 let centroids = turf.featureCollection(collecs.map(c=>turf.centroid(turf.featureCollection(c))));
2746 var voronoi = turf.voronoi(
2748 {'bbox':polygonbbox}
2750 this.splitPlot_memo[memo_key] = voronoi.features.map(vc=>{
2751 var mask = turf.mask(vc,turf.bboxPolygon(polygonbbox));
2752 var c = turf.difference(polygon,mask);
2753 return turf.transformScale(c, 1/scale_factor, {'origin':scale_origin})
2755 return this.splitPlot_memo[memo_key][index];
2758 static featureToL(feature) {
2759 return turf.getCoords(turf.flip(feature));
2762 setLocation(studyDbId) {
2763 return new Promise((resolve, reject) => {
2764 this.brapi = BrAPI(this.brapi_endpoint, "2.0", this.opts.brapi_auth);
2765 this.brapi.studies_detail({studyDbId: studyDbId}).map((study) => {
2770 if (study.location && study.location.latitude && study.location.longitude) {
2771 // XXX some clients use the brapi v1 format
2773 study.location.latitude,
2774 study.location.longitude
2775 ], this.opts.normalZoom);
2777 } else if (study.locationDbId) {
2778 this.brapi.locations_detail({locationDbId: study.locationDbId}).map((location) => {
2779 if (!location || !location.coordinates) {
2783 this.map.setView(Fieldmap.featureToL(location.coordinates), this.opts.normalZoom);
2794 L.geoJSON(feature, {color: 'red'}).addTo(this.map);
2800 return Promise.reject('There are no plots loaded');
2802 let brapi = BrAPI(this.brapi_endpoint, "2.0", this.opts.brapi_auth);
2805 this.plots.features.forEach((plot)=>{
2806 params[plot.properties.observationUnitDbId] = {
2807 observationUnitPosition: {geoCoordinates: plot, observationLevel:{levelName: this.opts.brapi_levelName }}
2811 return new Promise((resolve, reject)=> {
2812 if ( Object.keys(params).length > 0 ) {
2813 brapi.simple_brapi_call({
2814 'defaultMethod': 'put',
2815 'urlTemplate': '/observationunits?pageSize={pageSize}',
2816 'params': { pageSize: Object.keys(params).length+1, ...params },
2819 return resolve("Plots updated!");
2822 return reject('There are no plots loaded');
2828 function get_oup(ou) {
2829 return ou.observationUnitPosition || {};
2832 function get_oup_rel(ou) {
2833 return (ou.observationUnitPosition || {}).observationLevelRelationships || {};
2836 applyDefaultPlot(Fieldmap);