Merge pull request #5205 from solgenomics/topic/generic_trial_upload
[sgn.git] / js / source / legacy / brapi / BrAPIFieldmap.js
blobe9a475609cd27f7654d7fd21d154f2134d058e27
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;
10         /*
11          Modified to explicitly use window.L
12         */
13         /**
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
17          * License Apache-2.0
18          */
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"));
24             } else {
25                 factory(window.L);
26             }
27         }(undefined, function (L$$1) {
29         L$$1.TileLayer.Fallback = L$$1.TileLayer.extend({
31                 options: {
32                         minNativeZoom: 0
33                 },
35                 initialize: function (urlTemplate, options) {
36                         L$$1.TileLayer.prototype.initialize.call(this, urlTemplate, options);
37                 },
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;
44                         return tile;
45                 },
47                 _createCurrentCoords: function (originalCoords) {
48                         var currentCoords = this._wrapCoords(originalCoords);
50                         currentCoords.fallback = true;
52                         return currentCoords;
53                 },
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(),
64                             style = tile.style,
65                             newUrl, top, left;
67                         // If no lower zoom tiles are available, fallback to errorTile.
68                         if (fallbackZoom < layer.options.minNativeZoom) {
69                                 return this._originalTileOnError(done, tile, e);
70                         }
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';
90                         // Crop (clip) image.
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', {
96                                 tile: tile,
97                                 url: tile._originalSrc,
98                                 urlMissing: tile.src,
99                                 urlFallback: newUrl
100                         });
102                         tile.src = newUrl;
103                 },
105                 getTileUrl: function (coords) {
106                         var z = coords.z = coords.fallback ? coords.z : this._getZoomForUrl();
108                         var data = {
109                                 r: L$$1.Browser.retina ? '@2x' : '',
110                                 s: this._getSubdomain(coords),
111                                 x: coords.x,
112                                 y: coords.y,
113                                 z: z
114                         };
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;
119                                 }
120                                 data['-y'] = invertedY;
121                         }
123                         return L$$1.Util.template(this._url, L$$1.extend(data, this.options));
124                 }
126         });
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);
133         };
137         }));
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'));
150             }
152             // attach your plugin to the global 'L' variable
153             if(typeof window !== 'undefined' && window.L){
154                 factory(window.L);
155             }
157         }(function (L$$1) {
158             // 🍂miniclass CancelableEvent (Event objects)
159             // 🍂method cancel()
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({
189                 statics: {
190                     FORWARD: 1,
191                     BACKWARD: -1
192                 },
194                 options: {
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.
199                     zIndex: 1000,
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
265                 },
267                 initialize: function (map, options) {
268                     L$$1.setOptions(this, options);
269                     this._lastZIndex = this.options.zIndex;
270                     this.map = map;
271                     this.editLayer = this.createEditLayer();
272                     this.featuresLayer = this.createFeaturesLayer();
273                     this.forwardLineGuide = this.createLineGuide();
274                     this.backwardLineGuide = this.createLineGuide();
275                 },
277                 fireAndForward: function (type, e) {
278                     e = e || {};
279                     e.editTools = this;
280                     this.fire(type, e);
281                     this.map.fire(type, e);
282                 },
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);
287                 },
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);
291                 },
293                 createEditLayer: function () {
294                     return this.options.editLayer || new L$$1.LayerGroup().addTo(this.map);
295                 },
297                 createFeaturesLayer: function () {
298                     return this.options.featuresLayer || new L$$1.LayerGroup().addTo(this.map);
299                 },
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();
306                     }
307                 },
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();
314                     }
315                 },
317                 anchorForwardLineGuide: function (latlng) {
318                     this.forwardLineGuide._latlngs[0] = latlng;
319                     this.forwardLineGuide._bounds.extend(latlng);
320                     this.forwardLineGuide.redraw();
321                 },
323                 anchorBackwardLineGuide: function (latlng) {
324                     this.backwardLineGuide._latlngs[0] = latlng;
325                     this.backwardLineGuide._bounds.extend(latlng);
326                     this.backwardLineGuide.redraw();
327                 },
329                 attachForwardLineGuide: function () {
330                     this.editLayer.addLayer(this.forwardLineGuide);
331                 },
333                 attachBackwardLineGuide: function () {
334                     this.editLayer.addLayer(this.backwardLineGuide);
335                 },
337                 detachForwardLineGuide: function () {
338                     this.forwardLineGuide.setLatLngs([]);
339                     this.editLayer.removeLayer(this.forwardLineGuide);
340                 },
342                 detachBackwardLineGuide: function () {
343                     this.backwardLineGuide.setLatLngs([]);
344                     this.editLayer.removeLayer(this.backwardLineGuide);
345                 },
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 = {};
352                     }
353                 },
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;
360                     }
361                 },
363                 registerForDrawing: function (editor) {
364                     if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor);
365                     this.blockEvents();
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;
374                 },
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;
381                     if (!editor) return;
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();
388                 },
390                 onMousedown: function (e) {
391                     if (e.originalEvent.which != 1) return;
392                     this._mouseDown = e;
393                     this._drawingEditor.onDrawingMouseDown(e);
394                 },
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);
406                     }
407                 },
409                 // 🍂section Public methods
410                 // You will generally access them by the `map.editTools`
411                 // instance:
412                 //
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();
419                 },
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();
425                 },
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);
432                 },
434                 connectCreatedToMap: function (layer) {
435                     return this.featuresLayer.addLayer(layer);
436                 },
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);
444                     return line;
445                 },
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);
453                     return polygon;
454                 },
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();
464                     return marker;
465                 },
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();
475                     return rectangle;
476                 },
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();
485                     return circle;
486                 },
488                 startHole: function (editor, latlng) {
489                     editor.newHole(latlng);
490                 },
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});
499                     return layer;
500                 },
502                 createPolyline: function (latlngs, options) {
503                     return this.createLayer(options && options.polylineClass || this.options.polylineClass, latlngs, options);
504                 },
506                 createPolygon: function (latlngs, options) {
507                     return this.createLayer(options && options.polygonClass || this.options.polygonClass, latlngs, options);
508                 },
510                 createMarker: function (latlng, options) {
511                     return this.createLayer(options && options.markerClass || this.options.markerClass, latlng, options);
512                 },
514                 createRectangle: function (bounds, options) {
515                     return this.createLayer(options && options.rectangleClass || this.options.rectangleClass, bounds, options);
516                 },
518                 createCircle: function (latlng, options) {
519                     return this.createLayer(options && options.circleClass || this.options.circleClass, latlng, options);
520                 }
522             });
524             L$$1.extend(L$$1.Editable, {
526                 makeCancellable: function (e) {
527                     e.cancel = function () {
528                         e._cancelled = true;
529                     };
530                 }
532             });
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.
537             // 🍂example
538             //
539             // ```js
540             // var map = L.map('map', {
541             //  editable: true,
542             //  editOptions: {
543             //    …
544             // }
545             // });
546             // ```
547             // 🍂section Editable Map Options
548             L$$1.Map.mergeOptions({
550                 // 🍂namespace Map
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.
558                 editable: false,
560                 // 🍂option editOptions: hash = {}
561                 // Options to pass to L.Editable when instantiating.
562                 editOptions: {}
564             });
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);
571                     }
572                 });
574             });
576             L$$1.Editable.VertexIcon = L$$1.DivIcon.extend({
578                 options: {
579                     iconSize: new L$$1.Point(8, 8)
580                 }
582             });
584             L$$1.Editable.TouchVertexIcon = L$$1.Editable.VertexIcon.extend({
586                 options: {
587                     iconSize: new L$$1.Point(20, 20)
588                 }
590             });
593             // 🍂namespace Editable; 🍂class VertexMarker; Handler for dragging path vertices.
594             L$$1.Editable.VertexMarker = L$$1.Marker.extend({
596                 options: {
597                     draggable: true,
598                     className: 'leaflet-div-icon leaflet-vertex-icon'
599                 },
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);
617                 },
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();
631                 },
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);
646                 },
648                 onDrag: function (e) {
649                     e.vertex = this;
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();
659                 },
661                 onDragStart: function (e) {
662                     e.vertex = this;
663                     this.editor.onVertexMarkerDragStart(e);
664                 },
666                 onDragEnd: function (e) {
667                     e.vertex = this;
668                     this.editor.onVertexMarkerDragEnd(e);
669                 },
671                 onClick: function (e) {
672                     e.vertex = this;
673                     this.editor.onVertexMarkerClick(e);
674                 },
676                 onMouseup: function (e) {
677                     L$$1.DomEvent.stop(e);
678                     e.vertex = this;
679                     this.editor.map.fire('mouseup', e);
680                 },
682                 onContextMenu: function (e) {
683                     e.vertex = this;
684                     this.editor.onVertexMarkerContextMenu(e);
685                 },
687                 onMouseDown: function (e) {
688                     e.vertex = this;
689                     this.editor.onVertexMarkerMouseDown(e);
690                 },
692                 onMouseOver: function (e) {
693                     e.vertex = this;
694                     this.editor.onVertexMarkerMouseOver(e);
695                 },
697                 onMouseOut: function (e) {
698                     e.vertex = this;
699                     this.editor.onVertexMarkerMouseOut(e);
700                 },
702                 // 🍂method delete()
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();
712                 },
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);
718                 },
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;
724                 },
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;
735                 },
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;
746                 },
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);
752                 },
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();
760                 },
762                 resetMiddleMarker: function () {
763                     if (this.middleMarker) this.middleMarker.delete();
764                     this.addMiddleMarker();
765                 },
767                 // 🍂method split()
768                 // Split the vertex LatLngs group at its index, if possible.
769                 split: function () {
770                     if (!this.editor.splitShape) return;  // Only for PolylineEditor
771                     this.editor.splitShape(this.latlngs, this.getIndex());
772                 },
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);
781                 }
783             });
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
792             });
794             L$$1.Editable.MiddleMarker = L$$1.Marker.extend({
796                 options: {
797                     opacity: 0.5,
798                     className: 'leaflet-div-icon leaflet-middle-icon',
799                     draggable: true
800                 },
802                 initialize: function (left, right, latlngs, editor, options) {
803                     this.left = left;
804                     this.right = right;
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();
812                 },
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();
819                     else this.show();
820                 },
822                 show: function () {
823                     this.setOpacity(this._opacity);
824                 },
826                 hide: function () {
827                     this.setOpacity(0);
828                 },
830                 updateLatLng: function () {
831                     this.setLatLng(this.computeLatLng());
832                     this.setVisibility();
833                 },
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]);
841                 },
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);
847                 },
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);
854                 },
856                 onMouseDown: function (e) {
857                     var iconPos = L$$1.DomUtil.getPosition(this._icon),
858                         latlng = this.editor.map.layerPointToLatLng(iconPos);
859                     e = {
860                         originalEvent: e,
861                         latlng: latlng
862                     };
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);
875                     marker._icon = icon;
876                     parent.appendChild(marker._icon);
877                     marker._initIcon();
878                     marker._initInteraction();
879                     marker.setOpacity(1);
880                     /* End hack */
881                     // Transfer ongoing dragging to real marker
882                     L$$1.Draggable._dragging = false;
883                     marker.dragging._draggable._onDown(e.originalEvent);
884                     this.delete();
885                 },
887                 delete: function () {
888                     this.editor.editLayer.removeLayer(this);
889                 },
891                 index: function () {
892                     return this.latlngs.indexOf(this.right.latlng);
893                 }
895             });
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
904             });
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);
913                     this.map = map;
914                     this.feature = feature;
915                     this.feature.editor = this;
916                     this.editLayer = new L$$1.LayerGroup();
917                     this.tools = this.options.editTools || map.editTools;
918                 },
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);
925                     this.onEnable();
926                     this.feature.on(this._getEvents(), this);
927                 },
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);
936                     this.onDisable();
937                     if (this._drawing) this.cancelDrawing();
938                 },
940                 // 🍂method drawing(): boolean
941                 // Return true if any drawing action is ongoing with this editor.
942                 drawing: function () {
943                     return !!this._drawing;
944                 },
946                 reset: function () {},
948                 onFeatureAdd: function () {
949                     this.tools.editLayer.addLayer(this.editLayer);
950                     if (this.feature.dragging) this.feature.dragging.enable();
951                 },
953                 hasMiddleMarkers: function () {
954                     return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers;
955                 },
957                 fireAndForward: function (type, e) {
958                     e = e || {};
959                     e.layer = this.feature;
960                     this.feature.fire(type, e);
961                     this.tools.fireAndForward(type, e);
962                 },
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');
969                 },
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');
976                 },
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');
983                 },
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');
991                 },
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');
999                 },
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');
1007                 },
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);
1015                 },
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);
1023                 },
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);
1031                 },
1033                 startDrawing: function () {
1034                     if (!this._drawing) this._drawing = L$$1.Editable.FORWARD;
1035                     this.tools.registerForDrawing(this);
1036                     this.onStartDrawing();
1037                 },
1039                 commitDrawing: function (e) {
1040                     this.onCommitDrawing(e);
1041                     this.endDrawing();
1042                 },
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();
1050                     this.endDrawing();
1051                 },
1053                 endDrawing: function () {
1054                     this._drawing = false;
1055                     this.tools.unregisterForDrawing(this);
1056                     this.onEndDrawing();
1057                 },
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);
1070                 },
1072                 isConnected: function () {
1073                     return this.map.hasLayer(this.feature);
1074                 },
1076                 connect: function () {
1077                     this.tools.connectCreatedToMap(this.feature);
1078                     this.tools.editLayer.addLayer(this.editLayer);
1079                 },
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);
1087                 },
1089                 onDrawingMouseMove: function (e) {
1090                     this.onMove(e);
1091                 },
1093                 _getEvents: function () {
1094                     return {
1095                         dragstart: this.onDragStart,
1096                         drag: this.onDrag,
1097                         dragend: this.onDragEnd,
1098                         remove: this.disable
1099                     };
1100                 },
1102                 onDragStart: function (e) {
1103                     this.onEditing();
1104                     // 🍂namespace Editable
1105                     // 🍂event editable:dragstart: Event
1106                     // Fired before a path feature is dragged.
1107                     this.fireAndForward('editable:dragstart', e);
1108                 },
1110                 onDrag: function (e) {
1111                     this.onMove(e);
1112                     // 🍂namespace Editable
1113                     // 🍂event editable:drag: Event
1114                     // Fired when a path feature is being dragged.
1115                     this.fireAndForward('editable:drag', e);
1116                 },
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);
1123                 }
1125             });
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);
1135                 },
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);
1144                 },
1146                 connect: function (e) {
1147                     // On touch, the latlng has not been updated because there is
1148                     // no mousemove.
1149                     if (e) this.feature._latlng = e.latlng;
1150                     L$$1.Editable.BaseEditor.prototype.connect.call(this, e);
1151                 }
1153             });
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({
1160                 CLOSED: false,
1161                 MIN_VERTEX: 2,
1163                 addHooks: function () {
1164                     L$$1.Editable.BaseEditor.prototype.addHooks.call(this);
1165                     if (this.feature) this.initVertexMarkers();
1166                     return this;
1167                 },
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]);
1174                 },
1176                 getLatLngs: function () {
1177                     return this.feature.getLatLngs();
1178                 },
1180                 // 🍂method reset()
1181                 // Rebuild edit elements (Vertex, MiddleMarker, etc.).
1182                 reset: function () {
1183                     this.editLayer.clearLayers();
1184                     this.initVertexMarkers();
1185                 },
1187                 addVertexMarker: function (latlng, latlngs) {
1188                     return new this.tools.options.vertexMarkerClass(latlng, latlngs, this);
1189                 },
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});
1197                 },
1199                 addVertexMarkers: function (latlngs) {
1200                     for (var i = 0; i < latlngs.length; i++) {
1201                         this.addVertexMarker(latlngs[i], latlngs);
1202                     }
1203                 },
1205                 refreshVertexMarkers: function (latlngs) {
1206                     latlngs = latlngs || this.getDefaultLatLngs();
1207                     for (var i = 0; i < latlngs.length; i++) {
1208                         latlngs[i].__vertex.update();
1209                     }
1210                 },
1212                 addMiddleMarker: function (left, right, latlngs) {
1213                     return new this.tools.options.middleMarkerClass(left, right, latlngs, this);
1214                 },
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) {
1237                         commit = true;
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
1240                     } else {
1241                         this.onVertexRawMarkerClick(e);
1242                     }
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);
1249                 },
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;
1259                     e.vertex.delete();
1260                 },
1262                 vertexCanBeDeleted: function (vertex) {
1263                     return vertex.latlngs.length > this.MIN_VERTEX;
1264                 },
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);
1272                 },
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);
1280                 },
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);
1288                 },
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);
1296                 },
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);
1304                 },
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);
1312                 },
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);
1320                 },
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);
1328                 },
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);
1336                 },
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);
1344                 },
1346                 onVertexMarkerDrag: function (e) {
1347                     this.onMove(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);
1354                 },
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);
1362                 },
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);
1370                 },
1372                 setDrawnLatLngs: function (latlngs) {
1373                     this._drawnLatLngs = latlngs || this.getDefaultLatLngs();
1374                 },
1376                 startDrawing: function () {
1377                     if (!this._drawnLatLngs) this.setDrawnLatLngs();
1378                     L$$1.Editable.BaseEditor.prototype.startDrawing.call(this);
1379                 },
1381                 startDrawingForward: function () {
1382                     this.startDrawing();
1383                 },
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;
1391                 },
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);
1399                     this.refresh();
1400                 },
1402                 newPointForward: function (latlng) {
1403                     this.addLatLng(latlng);
1404                     this.tools.attachForwardLineGuide();
1405                     this.tools.anchorForwardLineGuide(latlng);
1406                 },
1408                 newPointBackward: function (latlng) {
1409                     this.addLatLng(latlng);
1410                     this.tools.anchorBackwardLineGuide(latlng);
1411                 },
1413                 // 🍂namespace PathEditor
1414                 // 🍂method push()
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);
1420                 },
1422                 removeLatLng: function (latlng) {
1423                     latlng.__vertex.delete();
1424                     this.refresh();
1425                 },
1427                 // 🍂method pop(): L.LatLng or null
1428                 // Programmatically remove last point (if any) while drawing.
1429                 pop: function () {
1430                     if (this._drawnLatLngs.length <= 1) return;
1431                     var latlng;
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]);
1437                     return latlng;
1438                 },
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);
1445                 },
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);
1452                     }
1453                 },
1455                 refresh: function () {
1456                     this.feature.redraw();
1457                     this.onEditing();
1458                 },
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();
1466                     if (!shape) return;
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);
1475                 },
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.
1489                     this.refresh();
1490                     this.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});
1496                     return shape;
1497                 },
1499                 _deleteShape: function (shape, latlngs) {
1500                     latlngs = latlngs || this.getLatLngs();
1501                     if (!latlngs.length) return;
1502                     var self = this,
1503                         inplaceDelete = function (latlngs, shape) {
1504                             // Called when deleting a flat latlngs
1505                             shape = latlngs.splice(0, Number.MAX_VALUE);
1506                             return shape;
1507                         },
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);
1512                             return shape;
1513                         };
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);
1518                     }
1519                 },
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);
1527                 },
1529                 // 🍂method appendShape(shape: Array)
1530                 // Append a new shape to the Polygon or Polyline.
1531                 appendShape: function (shape) {
1532                     this.insertShape(shape);
1533                 },
1535                 // 🍂method prependShape(shape: Array)
1536                 // Prepend a new shape to the Polygon or Polyline.
1537                 prependShape: function (shape) {
1538                     this.insertShape(shape, 0);
1539                 },
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) {
1544                     this.ensureMulti();
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();
1550                 },
1552                 extendBounds: function (e) {
1553                     this.feature._bounds.extend(e.vertex.latlng);
1554                 },
1556                 onDragStart: function (e) {
1557                     this.editLayer.clearLayers();
1558                     L$$1.Editable.BaseEditor.prototype.onDragStart.call(this, e);
1559                 },
1561                 onDragEnd: function (e) {
1562                     this.initVertexMarkers();
1563                     L$$1.Editable.BaseEditor.prototype.onDragEnd.call(this, e);
1564                 }
1566             });
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();
1575                 },
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]);
1586                     }
1587                     this.startDrawingBackward();
1588                 },
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]);
1599                     }
1600                     this.startDrawingForward();
1601                 },
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]);
1607                 },
1609                 ensureMulti: function () {
1610                     if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) {
1611                         this.feature._latlngs = [this.feature._latlngs];
1612                     }
1613                 },
1615                 addNewEmptyShape: function () {
1616                     if (this.feature._latlngs.length) {
1617                         var shape = [];
1618                         this.appendShape(shape);
1619                         return shape;
1620                     } else {
1621                         return this.feature._latlngs;
1622                     }
1623                 },
1625                 formatShape: function (shape) {
1626                     if (isFlat(shape)) return shape;
1627                     else if (shape[0]) return this.formatShape(shape[0]);
1628                 },
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;
1634                     this.ensureMulti();
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);
1642                     this.refresh();
1643                     this.reset();
1644                 }
1646             });
1648             // 🍂namespace Editable; 🍂class PolygonEditor; 🍂aka L.Editable.PolygonEditor
1649             // 🍂inherits PathEditor
1650             L$$1.Editable.PolygonEditor = L$$1.Editable.PathEditor.extend({
1652                 CLOSED: true,
1653                 MIN_VERTEX: 3,
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();
1659                 },
1661                 addNewEmptyHole: function (latlng) {
1662                     this.ensureNotFlat();
1663                     var latlngs = this.feature.shapeAt(latlng);
1664                     if (!latlngs) return;
1665                     var holes = [];
1666                     latlngs.push(holes);
1667                     return holes;
1668                 },
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);
1674                     if (!holes) return;
1675                     this.setDrawnLatLngs(holes);
1676                     this.startDrawingForward();
1677                     if (latlng) this.newPointForward(latlng);
1678                 },
1680                 addNewEmptyShape: function () {
1681                     if (this.feature._latlngs.length && this.feature._latlngs[0].length) {
1682                         var shape = [];
1683                         this.appendShape(shape);
1684                         return shape;
1685                     } else {
1686                         return this.feature._latlngs;
1687                     }
1688                 },
1690                 ensureMulti: function () {
1691                     if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) {
1692                         this.feature._latlngs = [this.feature._latlngs];
1693                     }
1694                 },
1696                 ensureNotFlat: function () {
1697                     if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs];
1698                 },
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);
1705                 },
1707                 getDefaultLatLngs: function () {
1708                     if (!this.feature._latlngs.length) this.feature._latlngs.push([]);
1709                     return this.feature._latlngs[0];
1710                 },
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];
1717                     else return shape;
1718                 }
1720             });
1722             // 🍂namespace Editable; 🍂class RectangleEditor; 🍂aka L.Editable.RectangleEditor
1723             // 🍂inherits PathEditor
1724             L$$1.Editable.RectangleEditor = L$$1.Editable.PathEditor.extend({
1726                 CLOSED: true,
1727                 MIN_VERTEX: 4,
1729                 options: {
1730                     skipMiddleMarkers: true
1731                 },
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();
1745                 },
1747                 onDrawingMouseDown: function (e) {
1748                     L$$1.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
1749                     this.connect();
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);
1757                     this.refresh();
1758                     this.reset();
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
1772                     // drag direction?
1773                     latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent);
1774                 },
1776                 onDrawingMouseUp: function (e) {
1777                     this.commitDrawing(e);
1778                     e.originalEvent._simulated = false;
1779                     L$$1.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
1780                 },
1782                 onDrawingMouseMove: function (e) {
1783                     e.originalEvent._simulated = false;
1784                     L$$1.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
1785                 },
1788                 getDefaultLatLngs: function (latlngs) {
1789                     return latlngs || this.feature._latlngs[0];
1790                 },
1792                 updateBounds: function (bounds) {
1793                     this.feature._bounds = bounds;
1794                 },
1796                 updateLatLngs: function (bounds) {
1797                     var latlngs = this.getDefaultLatLngs(),
1798                         newLatlngs = this.feature._boundsToLatLngs(bounds);
1799                     // Keep references.
1800                     for (var i = 0; i < latlngs.length; i++) {
1801                         latlngs[i].update(newLatlngs[i]);
1802                     }
1803                 }
1805             });
1807             // 🍂namespace Editable; 🍂class CircleEditor; 🍂aka L.Editable.CircleEditor
1808             // 🍂inherits PathEditor
1809             L$$1.Editable.CircleEditor = L$$1.Editable.PathEditor.extend({
1811                 MIN_VERTEX: 2,
1813                 options: {
1814                     skipMiddleMarkers: true
1815                 },
1817                 initialize: function (map, feature, options) {
1818                     L$$1.Editable.PathEditor.prototype.initialize.call(this, map, feature, options);
1819                     this._resizeLatLng = this.computeResizeLatLng();
1820                 },
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]);
1827                 },
1829                 updateResizeLatLng: function () {
1830                     this._resizeLatLng.update(this.computeResizeLatLng());
1831                     this._resizeLatLng.__vertex.update();
1832                 },
1834                 getLatLngs: function () {
1835                     return [this.feature._latlng, this._resizeLatLng];
1836                 },
1838                 getDefaultLatLngs: function () {
1839                     return this.getLatLngs();
1840                 },
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);
1846                 },
1848                 resize: function (e) {
1849                     var radius = this.feature._latlng.distanceTo(e.latlng);
1850                     this.feature.setRadius(radius);
1851                 },
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);
1857                     this.connect();
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);
1863                 },
1865                 onDrawingMouseUp: function (e) {
1866                     this.commitDrawing(e);
1867                     e.originalEvent._simulated = false;
1868                     L$$1.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
1869                 },
1871                 onDrawingMouseMove: function (e) {
1872                     e.originalEvent._simulated = false;
1873                     L$$1.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
1874                 },
1876                 onDrag: function (e) {
1877                     L$$1.Editable.PathEditor.prototype.onDrag.call(this, e);
1878                     this.feature.dragging.updateLatLng(this._resizeLatLng);
1879                 }
1881             });
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);
1896                 },
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();
1903                     return this.editor;
1904                 },
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();
1910                 },
1912                 // 🍂method disableEdit()
1913                 // Disable editing, also remove the editor property reference.
1914                 disableEdit: function () {
1915                     if (this.editor) {
1916                         this.editor.disable();
1917                         delete this.editor;
1918                     }
1919                 },
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();
1926                 },
1928                 _onEditableAdd: function () {
1929                     if (this.editor) this.enableEdit();
1930                 }
1932             };
1934             var PolylineMixin = {
1936                 getEditorClass: function (tools) {
1937                     return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L$$1.Editable.PolylineEditor;
1938                 },
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
1944                     var shape = null;
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];
1949                     return shape;
1950                 },
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);
1957                     part = part[0];
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) {
1964                             return true;
1965                         }
1966                     }
1967                     return false;
1968                 }
1970             };
1972             var PolygonMixin = {
1974                 getEditorClass: function (tools) {
1975                     return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L$$1.Editable.PolygonEditor;
1976                 },
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
1983                     var shape = null;
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];
1989                     return shape;
1990                 },
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++) {
1996                         l1 = latlngs[j];
1997                         l2 = latlngs[k];
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)) {
2001                             inside = !inside;
2002                         }
2003                     }
2005                     return inside;
2006                 },
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];
2016                     }
2017                 }
2019             };
2022             var MarkerMixin = {
2024                 getEditorClass: function (tools) {
2025                     return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L$$1.Editable.MarkerEditor;
2026                 }
2028             };
2030             var RectangleMixin = {
2032                 getEditorClass: function (tools) {
2033                     return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L$$1.Editable.RectangleEditor;
2034                 }
2036             };
2038             var CircleMixin = {
2040                 getEditorClass: function (tools) {
2041                     return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L$$1.Editable.CircleEditor;
2042                 }
2044             };
2046             var keepEditable = function () {
2047                 // Make sure you can remove/readd an editable layer.
2048                 this.on('add', this._onEditableAdd);
2049             };
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);
2058             }
2059             if (L$$1.Polygon) {
2060                 L$$1.Polygon.include(EditableMixin);
2061                 L$$1.Polygon.include(PolygonMixin);
2062             }
2063             if (L$$1.Marker) {
2064                 L$$1.Marker.include(EditableMixin);
2065                 L$$1.Marker.include(MarkerMixin);
2066                 L$$1.Marker.addInitHook(keepEditable);
2067             }
2068             if (L$$1.Rectangle) {
2069                 L$$1.Rectangle.include(EditableMixin);
2070                 L$$1.Rectangle.include(RectangleMixin);
2071             }
2072             if (L$$1.Circle) {
2073                 L$$1.Circle.include(EditableMixin);
2074                 L$$1.Circle.include(CircleMixin);
2075             }
2077             L$$1.LatLng.prototype.update = function (latlng) {
2078                 latlng = L$$1.latLng(latlng);
2079                 this.lat = latlng.lat;
2080                 this.lng = latlng.lng;
2081             };
2083         }, window));
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(
2091               turf.destination(
2092                 o,
2093                 plotWidth*col,
2094                 90,
2095                 {'units': 'kilometers'}
2096               ),
2097               plotLength*row,
2098               180,
2099               {'units': 'kilometers'}
2100             );
2101             var br = turf.destination(
2102               turf.destination(
2103                 tl,
2104                 plotWidth,
2105                 90,
2106                 {'units': 'kilometers'}
2107               ),
2108               plotLength,
2109               180,
2110               {'units': 'kilometers'}
2111             );
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)
2116             ], {});
2117           };
2118         }
2120         const NO_POLYGON_ERROR = "Please select the area that contain the plots";
2122         const DEFAULT_OPTS = {
2123           brapi_auth: null,
2124           brapi_pageSize: 1000,
2125           brapi_levelName: 'plot',
2126           defaultPos: [0, 0],
2127           defaultZoom: 2,
2128           normalZoom: 16,
2129           plotWidth: 0,
2130           plotLength: 0,
2131           plotScaleFactor: 1,
2132           style: {
2133             weight: 1
2134           },
2135           useGeoJson: true,
2136           tileLayer: {
2137             url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}?blankTile=false',
2138             options: {
2139               attribution: '&copy; <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',
2140               maxZoom: 28,
2141               maxNativeZoom: 19
2142             }
2143           }
2144         };
2146         class Fieldmap {
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;
2151             // Parse Options
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();
2157             });
2159             this.tilelayer = L.tileLayer.fallback(this.opts.tileLayer.url, this.opts.tileLayer.options).addTo(this.map);
2161             L.EditControl = L.Control.extend({
2162               options: {
2163                 position: 'topleft',
2164                 callback: null,
2165                 title: '',
2166                 html: ''
2167               },
2168               onAdd: function (map) {
2169                 var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
2170                   link = L.DomUtil.create('a', '', container);
2171                 link.href = '#';
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);
2177                   }, this);
2178                 return container;
2179               }
2180             });
2182             let self = this;
2183               L.NewPolygonControl = L.EditControl.extend({
2184                 options: {
2185                   position: 'topleft',
2186                   callback: function () {
2187                     self.polygon = self.map.editTools.startPolygon();
2188                     return self.polygon;
2189                   },
2190                   title: 'Creates a new polygon',
2191                   html: String.fromCodePoint(0x25B1)
2192                 }
2193               });
2194               L.NewRectangleControl = L.EditControl.extend({
2195                 options: {
2196                   position: 'topleft',
2197                   callback: function () {
2198                     self.polygon = self.map.editTools.startRectangle();
2199                     return self.polygon;
2200                   },
2201                   title: 'Creates a new rectangle',
2202                   html: String.fromCodePoint(0x25AD)
2203                 }
2204               });
2205               L.NewClearControl = L.EditControl.extend({
2206                 options: {
2207                   position: 'topleft',
2208                   callback: function () {
2209                     self.map.editTools.featuresLayer.clearLayers();
2210                   },
2211                   title: 'Clears all polygons',
2212                   html: String.fromCodePoint(0x1F6AB)
2213                 }
2214               });
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'],
2223                 autoCollapse: true,
2224                 autoType: false,
2225                 minLength: 2,
2226                 marker: false,
2227                 zoom: this.opts.normalZoom
2228               }));
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);
2237                         }
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")
2261               .style("top", 0)
2262               .style("left", 0)
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');
2272             }
2273           }
2275           removeControls() {
2276             this.map.removeControl(this.polygonControl);
2277             this.map.removeControl(this.rectangleControl);
2278             this.map.removeControl(this.clearPolygonsControl);
2279           }
2281           load(studyDbId) {
2282             this.onLoading(true);
2283             this.generatePlots(studyDbId);
2284             return this.data.then(()=>{
2285               this.drawPlots();
2286               this.onLoading(false);
2287               return true;
2288             }).catch(resp=>{
2289               console.log(resp);
2290               this.onLoading(false);
2291             });
2292           }
2294           drawPlots() {
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();
2301               }
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', ()=>{
2318               this.info.html("");
2319             }).addTo(this.map);
2320           }
2322           enableEdition(plot) {
2323             this.editablePlot = plot;
2324             plot.enableEdit();
2325           }
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;
2334               }
2335               return plot;
2336             }));
2337             this.editablePlot = null;
2338           }
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();
2352               })
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);
2371                   return plot
2372                 }));
2373                 this.finishTranslate();
2374               })
2375               .on('rotateend', (e)=>{
2376                 this.plots = turf.transformRotate(this.plots, turf.radiansToDegrees(e.rotation));
2377                 this.finishTranslate();
2378               })
2379               .addTo(this.map);
2380               this.editablePolygon.transform.enable();
2381               this.editablePolygon.dragging.enable();
2382           }
2384           finishTranslate() {
2385             let polygon = this.editablePolygon;
2386             polygon.transform.disable();
2387             polygon.dragging.disable();
2388             this.drawPlots();
2389             setTimeout(()=>{
2390               this.editablePolygon.remove();
2391               this.editablePolygon = null;
2392             });
2393           }
2395           /**
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
2398            * the far ends.
2399            */
2400           level() {
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)));
2412             });
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);
2416           }
2418           northernmost() {
2419             return [].slice.call(arguments).sort((a,b)=>b[1] - a[1])[0];
2420           }
2422           generatePlots(studyDbId) {
2423             return this.load_ObsUnits(studyDbId)
2424               .then((data)=>{
2425                 this.plots = turf.featureCollection(data.plots.map(p=>{
2426                   return Object.assign(p._geoJSON, {properties: {observationUnitDbId: p.observationUnitDbId}});
2427                 }));
2428                 if (!data.plots_shaped) {
2429                   // rotate to original position
2430                   this.plots = turf.transformRotate(this.plots, -this.rotation);
2431                 }
2432                 this.fitBounds(this.plots);
2433               });
2434           }
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");
2442             }
2443             var rej;
2444             var rawdata = new Promise((resolve,reject)=>{
2445               rej = 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 }]
2452               })
2453                 .each(ou=>{
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;
2459                 })
2460                 .all(()=>{
2461                   // ensure unique
2462                   this.plot_map = {};
2463                   results.plots = results.plots.reduce((acc,plot)=>{
2464                     if(!this.plot_map[plot.observationUnitDbId]){
2465                       this.plot_map[plot.observationUnitDbId] = plot;
2466                       acc.push(plot);
2467                     }
2468                     return acc;
2469                   },[]);
2471                   // sort
2472                   results.plots.sort(function(a,b){
2473                     if(a.plotNumber!=b.plotNumber){
2474                       return parseFloat(a.plotNumber)>parseFloat(b.plotNumber)?1:-1
2475                     }
2476                     return 1;
2477                   });
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);
2482                   }
2484                   clearInterval(this.while_downloading);
2485                   resolve(results);
2486                 });
2487             });
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);
2493             },500);
2494             rawdata.catch(e=>{
2495               clearInterval(this.while_downloading);
2496               console.log(e);
2497             });
2498             return this.data;
2499           }
2501           shape(data){
2502             data.shape = {};
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;
2509               try {
2510                 ou._geoJSON = (this.opts.useGeoJson && oup.geoCoordinates)
2511                               || null;
2512               } catch (e) {}
2513               ou._type = "";
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);
2521                   }
2522                   if(oup.positionCoordinateXType=="LONGITUDE" && oup.positionCoordinateYType=="LATITUDE"){
2523                     if(!ou._geoJSON) ou._geoJSON = turf.point([ou._X,ou._Y]);
2524                   }
2525                 }
2526                 else {
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);
2530                   }
2531                   else {
2532                     try {
2533                       if(!ou._geoJSON) ou._geoJSON = turf.point([ou._X,ou._Y]);
2534                     } catch (e) {}
2535                   }
2536                 }
2537               }
2538               if(ou._geoJSON){
2539                 try {
2540                   ou._type = turf.getType(ou._geoJSON);
2541                 }
2542                 catch (err) {
2543                   ou._type = "invalid";
2544                 }
2545               }
2546               else {
2547                 ou._type = "missing";
2548               }
2549             });
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;
2560               }
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>`);
2565                 html += "</ul>";
2566                 this.missing_plots.style("display", "block");
2567                 this.missing_plots.html(html);
2568               }
2569               data.plots = plots_valid;
2570             }
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)),
2576                 data.plots.length
2577               );
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;
2582                 plot._col = col;
2583                 plot._row = row;
2584               });
2585             }
2587             // Shape Plots
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;
2592             }
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)
2597               }));
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];
2612               });
2613               data.plots_shaped = this.opts.useGeoJson;
2614             }
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];
2622             });
2624             if(!data.plots_shaped){
2625               if (!this.polygon || !turf.area(this.polygon.toGeoJSON())) {
2626                 throw NO_POLYGON_ERROR;
2627               }
2628               this.geoJson = this.polygon.toGeoJSON();
2629               this.polygon.remove();
2630               this.level();
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)=>{
2638                     if (!row) return;
2639                     acc[i] = acc[i]+1 || 1;
2640                   });
2641                   return acc;
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)) {
2650                       X = parseInt(X);
2651                       Y = parseInt(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);
2656                       });
2657                     }
2658                   }
2659                 }
2660               }
2661             }
2663             return data;
2664           }
2666           fitBounds(feature) {
2667             let bbox = turf.bbox(feature);
2668             this.map.fitBounds([[bbox[1], bbox[0]], [bbox[3], bbox[2]]]);
2669           }
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));
2674             let lyt_width;
2675             if(squarelen==bllen){
2676               lyt_width = squarelen;
2677             }
2678             else if (squarelen>bllen) {
2679               lyt_width = Math.round(squarelen/bllen)*bllen;
2680             }
2681             else {
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
2688               );
2689             }
2690             return lyt_width;
2691           }
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;
2707             }
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]));
2724             let t = 0;
2725             let rows = [];
2726             row_point_counts.forEach((rpc,i)=>{
2727               rows[i] = [];
2728               while (rows[i].length<rpc && t<points.length){
2729                 rows[i].push(points[t++]);
2730               }
2731             });
2733             let collecs = [];
2734             rows.forEach((row,ri)=>{
2735               row = row.sort((a,b)=>d3.ascending(turf.getCoord(a)[0],turf.getCoord(b)[0]));
2736               let p = 0;
2737               let c0 = collecs.length;
2738               for (var ci = c0; ci < c0+row_counts[ri]; ci++) {
2739                 collecs[ci] = [];
2740                 while (collecs[ci].length<points_per_part && p<row.length){
2741                   collecs[ci].push(row[p++]);
2742                 }
2743               }
2744             });
2745             let centroids = turf.featureCollection(collecs.map(c=>turf.centroid(turf.featureCollection(c))));
2746             var voronoi = turf.voronoi(
2747               centroids,
2748               {'bbox':polygonbbox}
2749             );
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})
2754             });
2755             return this.splitPlot_memo[memo_key][index];
2756           }
2758           static featureToL(feature) {
2759             return turf.getCoords(turf.flip(feature));
2760           }
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) => {
2766                 if (!study) {
2767                   reject();
2768                   return;
2769                 }
2770                 if (study.location && study.location.latitude && study.location.longitude) {
2771                   // XXX some clients use the brapi v1 format
2772                   this.map.setView([
2773                     study.location.latitude,
2774                     study.location.longitude
2775                   ], this.opts.normalZoom);
2776                   resolve();
2777                 } else if (study.locationDbId) {
2778                   this.brapi.locations_detail({locationDbId: study.locationDbId}).map((location) => {
2779                     if (!location || !location.coordinates) {
2780                       reject();
2781                       return;
2782                     }
2783                     this.map.setView(Fieldmap.featureToL(location.coordinates), this.opts.normalZoom);
2784                     resolve();
2785                   });
2786                 } else {
2787                   reject();
2788                 }
2789               });
2790             });
2791           }
2793           debug(feature) {
2794             L.geoJSON(feature, {color: 'red'}).addTo(this.map);
2795             return feature;
2796           }
2798           update() {
2799             if (!this.plots) {
2800               return Promise.reject('There are no plots loaded');
2801             }
2802             let brapi = BrAPI(this.brapi_endpoint, "2.0", this.opts.brapi_auth);
2804                         let params = {};
2805                         this.plots.features.forEach((plot)=>{
2806                                 params[plot.properties.observationUnitDbId] = {
2807                                         observationUnitPosition: {geoCoordinates: plot, observationLevel:{levelName: this.opts.brapi_levelName }}
2808                                 };
2809                         });
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 },
2817                                                 'behavior': 'map'
2818                                         }).all(() => {
2819                                                 return resolve("Plots updated!");
2820                                         });
2821                                 } else {
2822                                         return reject('There are no plots loaded');
2823                                 }
2824                         });
2825           }
2826         }
2828         function get_oup(ou) {
2829           return ou.observationUnitPosition || {};
2830         }
2832         function get_oup_rel(ou) {
2833           return (ou.observationUnitPosition || {}).observationLevelRelationships || {};
2834         }
2836         applyDefaultPlot(Fieldmap);
2838         return Fieldmap;
2840 })));