PluginProbe
WP Directory Kit / 1.3.2
WP Directory Kit v1.3.2
1.5.6 1.5.5 1.5.4 1.5.3 trunk 1.1.0 1.1.6 1.1.8 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.8 1.4.0 1.4.1 All 35 releases
wpdirectorykit / public / js / leaflet-draw / leaflet.draw-src.js

leaflet.draw-src.js in WP Directory Kit 1.3.2, at public/js/leaflet-draw/leaflet.draw-src.js

4,791 lines 126.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 Leaflet.draw 0.4.14, a plugin that adds drawing and editing tools to Leaflet powered maps.
3 (c) 2012-2017, Jacob Toye, Jon West, Smartrak, Leaflet
4
5 https://github.com/Leaflet/Leaflet.draw
6 http://leafletjs.com
7 */
8 (function (window, document, undefined) {/**
9 * Leaflet.draw assumes that you have already included the Leaflet library.
10 */
11 L.drawVersion = "0.4.14";
12 /**
13 * @class L.Draw
14 * @aka Draw
15 *
16 *
17 * To add the draw toolbar set the option drawControl: true in the map options.
18 *
19 * @example
20 * ```js
21 * var map = L.map('map', {drawControl: true}).setView([51.505, -0.09], 13);
22 *
23 * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
24 * attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
25 * }).addTo(map);
26 * ```
27 *
28 * ### Adding the edit toolbar
29 * To use the edit toolbar you must initialise the Leaflet.draw control and manually add it to the map.
30 *
31 * ```js
32 * var map = L.map('map').setView([51.505, -0.09], 13);
33 *
34 * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
35 * attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
36 * }).addTo(map);
37 *
38 * // FeatureGroup is to store editable layers
39 * var drawnItems = new L.FeatureGroup();
40 * map.addLayer(drawnItems);
41 *
42 * var drawControl = new L.Control.Draw({
43 * edit: {
44 * featureGroup: drawnItems
45 * }
46 * });
47 * map.addControl(drawControl);
48 * ```
49 *
50 * The key here is the featureGroup option. This tells the plugin which FeatureGroup contains the layers that
51 * should be editable. The featureGroup can contain 0 or more features with geometry types Point, LineString, and Polygon.
52 * Leaflet.draw does not work with multigeometry features such as MultiPoint, MultiLineString, MultiPolygon,
53 * or GeometryCollection. If you need to add multigeometry features to the draw plugin, convert them to a
54 * FeatureCollection of non-multigeometries (Points, LineStrings, or Polygons).
55 */
56 L.Draw = {};
57
58 /**
59 * @class L.drawLocal
60 * @aka L.drawLocal
61 *
62 * The core toolbar class of the API — it is used to create the toolbar ui
63 *
64 * @example
65 * ```js
66 * var modifiedDraw = L.drawLocal.extend({
67 * draw: {
68 * toolbar: {
69 * buttons: {
70 * polygon: 'Draw an awesome polygon'
71 * }
72 * }
73 * }
74 * });
75 * ```
76 *
77 * The default state for the control is the draw toolbar just below the zoom control.
78 * This will allow map users to draw vectors and markers.
79 * **Please note the edit toolbar is not enabled by default.**
80 */
81 L.drawLocal = {
82 // format: {
83 // numeric: {
84 // delimiters: {
85 // thousands: ',',
86 // decimal: '.'
87 // }
88 // }
89 // },
90 draw: {
91 toolbar: {
92 // #TODO: this should be reorganized where actions are nested in actions
93 // ex: actions.undo or actions.cancel
94 actions: {
95 title: 'Cancel drawing',
96 text: 'Cancel'
97 },
98 finish: {
99 title: 'Finish drawing',
100 text: 'Finish'
101 },
102 undo: {
103 title: 'Delete last point drawn',
104 text: 'Delete last point'
105 },
106 buttons: {
107 polyline: 'Draw a polyline',
108 polygon: 'Draw a polygon',
109 rectangle: 'Draw a rectangle',
110 circle: 'Draw a circle',
111 marker: 'Draw a marker',
112 circlemarker: 'Draw a circlemarker'
113 }
114 },
115 handlers: {
116 circle: {
117 tooltip: {
118 start: 'Click and drag to draw circle.'
119 },
120 radius: 'Radius'
121 },
122 circlemarker: {
123 tooltip: {
124 start: 'Click map to place circle marker.'
125 }
126 },
127 marker: {
128 tooltip: {
129 start: 'Click map to place marker.'
130 }
131 },
132 polygon: {
133 tooltip: {
134 start: 'Click to start drawing shape.',
135 cont: 'Click to continue drawing shape.',
136 end: 'Click first point to close this shape.'
137 }
138 },
139 polyline: {
140 error: '<strong>Error:</strong> shape edges cannot cross!',
141 tooltip: {
142 start: 'Click to start drawing line.',
143 cont: 'Click to continue drawing line.',
144 end: 'Click last point to finish line.'
145 }
146 },
147 rectangle: {
148 tooltip: {
149 start: 'Click and drag to draw rectangle.'
150 }
151 },
152 simpleshape: {
153 tooltip: {
154 end: 'Release mouse to finish drawing.'
155 }
156 }
157 }
158 },
159 edit: {
160 toolbar: {
161 actions: {
162 save: {
163 title: 'Save changes',
164 text: 'Save'
165 },
166 cancel: {
167 title: 'Cancel editing, discards all changes',
168 text: 'Cancel'
169 },
170 clearAll: {
171 title: 'Clear all layers',
172 text: 'Clear All'
173 }
174 },
175 buttons: {
176 edit: 'Edit layers',
177 editDisabled: 'No layers to edit',
178 remove: 'Delete layers',
179 removeDisabled: 'No layers to delete'
180 }
181 },
182 handlers: {
183 edit: {
184 tooltip: {
185 text: 'Drag handles or markers to edit features.',
186 subtext: 'Click cancel to undo changes.'
187 }
188 },
189 remove: {
190 tooltip: {
191 text: 'Click on a feature to remove.'
192 }
193 }
194 }
195 }
196 };
197
198
199
200 /**
201 * ### Events
202 * Once you have successfully added the Leaflet.draw plugin to your map you will want to respond to the different
203 * actions users can initiate. The following events will be triggered on the map:
204 *
205 * @class L.Draw.Event
206 * @aka Draw.Event
207 *
208 * Use `L.Draw.Event.EVENTNAME` constants to ensure events are correct.
209 *
210 * @example
211 * ```js
212 * map.on(L.Draw.Event.CREATED; function (e) {
213 * var type = e.layerType,
214 * layer = e.layer;
215 *
216 * if (type === 'marker') {
217 * // Do marker specific actions
218 * }
219 *
220 * // Do whatever else you need to. (save to db; add to map etc)
221 * map.addLayer(layer);
222 *});
223 * ```
224 */
225 L.Draw.Event = {};
226 /**
227 * @event draw:created: PolyLine; Polygon; Rectangle; Circle; Marker | String
228 *
229 * Layer that was just created.
230 * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`
231 * Triggered when a new vector or marker has been created.
232 *
233 */
234 L.Draw.Event.CREATED = 'draw:created';
235
236 /**
237 * @event draw:edited: LayerGroup
238 *
239 * List of all layers just edited on the map.
240 *
241 *
242 * Triggered when layers in the FeatureGroup; initialised with the plugin; have been edited and saved.
243 *
244 * @example
245 * ```js
246 * map.on('draw:edited', function (e) {
247 * var layers = e.layers;
248 * layers.eachLayer(function (layer) {
249 * //do whatever you want; most likely save back to db
250 * });
251 * });
252 * ```
253 */
254 L.Draw.Event.EDITED = 'draw:edited';
255
256 /**
257 * @event draw:deleted: LayerGroup
258 *
259 * List of all layers just removed from the map.
260 *
261 * Triggered when layers have been removed (and saved) from the FeatureGroup.
262 */
263 L.Draw.Event.DELETED = 'draw:deleted';
264
265 /**
266 * @event draw:drawstart: String
267 *
268 * The type of layer this is. One of:`polyline`; `polygon`; `rectangle`; `circle`; `marker`
269 *
270 * Triggered when the user has chosen to draw a particular vector or marker.
271 */
272 L.Draw.Event.DRAWSTART = 'draw:drawstart';
273
274 /**
275 * @event draw:drawstop: String
276 *
277 * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`
278 *
279 * Triggered when the user has finished a particular vector or marker.
280 */
281
282 L.Draw.Event.DRAWSTOP = 'draw:drawstop';
283
284 /**
285 * @event draw:drawvertex: LayerGroup
286 *
287 * List of all layers just being added from the map.
288 *
289 * Triggered when a vertex is created on a polyline or polygon.
290 */
291 L.Draw.Event.DRAWVERTEX = 'draw:drawvertex';
292
293 /**
294 * @event draw:editstart: String
295 *
296 * The type of edit this is. One of: `edit`
297 *
298 * Triggered when the user starts edit mode by clicking the edit tool button.
299 */
300
301 L.Draw.Event.EDITSTART = 'draw:editstart';
302
303 /**
304 * @event draw:editmove: ILayer
305 *
306 * Layer that was just moved.
307 *
308 * Triggered as the user moves a rectangle; circle or marker.
309 */
310 L.Draw.Event.EDITMOVE = 'draw:editmove';
311
312 /**
313 * @event draw:editresize: ILayer
314 *
315 * Layer that was just moved.
316 *
317 * Triggered as the user resizes a rectangle or circle.
318 */
319 L.Draw.Event.EDITRESIZE = 'draw:editresize';
320
321 /**
322 * @event draw:editvertex: LayerGroup
323 *
324 * List of all layers just being edited from the map.
325 *
326 * Triggered when a vertex is edited on a polyline or polygon.
327 */
328 L.Draw.Event.EDITVERTEX = 'draw:editvertex';
329
330 /**
331 * @event draw:editstop: String
332 *
333 * The type of edit this is. One of: `edit`
334 *
335 * Triggered when the user has finshed editing (edit mode) and saves edits.
336 */
337 L.Draw.Event.EDITSTOP = 'draw:editstop';
338
339 /**
340 * @event draw:deletestart: String
341 *
342 * The type of edit this is. One of: `remove`
343 *
344 * Triggered when the user starts remove mode by clicking the remove tool button.
345 */
346 L.Draw.Event.DELETESTART = 'draw:deletestart';
347
348 /**
349 * @event draw:deletestop: String
350 *
351 * The type of edit this is. One of: `remove`
352 *
353 * Triggered when the user has finished removing shapes (remove mode) and saves.
354 */
355 L.Draw.Event.DELETESTOP = 'draw:deletestop';
356
357 /**
358 * @event draw:toolbaropened: String
359 *
360 * Triggered when a toolbar is opened.
361 */
362 L.Draw.Event.TOOLBAROPENED = 'draw:toolbaropened';
363
364 /**
365 * @event draw:toolbarclosed: String
366 *
367 * Triggered when a toolbar is closed.
368 */
369 L.Draw.Event.TOOLBARCLOSED = 'draw:toolbarclosed';
370
371 /**
372 * @event draw:markercontext: String
373 *
374 * Triggered when a marker is right clicked.
375 */
376 L.Draw.Event.MARKERCONTEXT = 'draw:markercontext';
377
378
379 L.Draw = L.Draw || {};
380
381 /**
382 * @class L.Draw.Feature
383 * @aka Draw.Feature
384 */
385 L.Draw.Feature = L.Handler.extend({
386
387 // @method initialize(): void
388 initialize: function (map, options) {
389 this._map = map;
390 this._container = map._container;
391 this._overlayPane = map._panes.overlayPane;
392 this._popupPane = map._panes.popupPane;
393
394 // Merge default shapeOptions options with custom shapeOptions
395 if (options && options.shapeOptions) {
396 options.shapeOptions = L.Util.extend({}, this.options.shapeOptions, options.shapeOptions);
397 }
398 L.setOptions(this, options);
399
400 var version = L.version.split('.');
401 //If Version is >= 1.2.0
402 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
403 L.Draw.Feature.include(L.Evented.prototype);
404 } else {
405 L.Draw.Feature.include(L.Mixin.Events);
406 }
407 },
408
409 // @method enable(): void
410 // Enables this handler
411 enable: function () {
412 if (this._enabled) {
413 return;
414 }
415
416 L.Handler.prototype.enable.call(this);
417
418 this.fire('enabled', {handler: this.type});
419
420 this._map.fire(L.Draw.Event.DRAWSTART, {layerType: this.type});
421 },
422
423 // @method disable(): void
424 disable: function () {
425 if (!this._enabled) {
426 return;
427 }
428
429 L.Handler.prototype.disable.call(this);
430
431 this._map.fire(L.Draw.Event.DRAWSTOP, {layerType: this.type});
432
433 this.fire('disabled', {handler: this.type});
434 },
435
436 // @method addHooks(): void
437 // Add's event listeners to this handler
438 addHooks: function () {
439 var map = this._map;
440
441 if (map) {
442 L.DomUtil.disableTextSelection();
443
444 map.getContainer().focus();
445
446 this._tooltip = new L.Draw.Tooltip(this._map);
447
448 L.DomEvent.on(this._container, 'keyup', this._cancelDrawing, this);
449 }
450 },
451
452 // @method removeHooks(): void
453 // Removes event listeners from this handler
454 removeHooks: function () {
455 if (this._map) {
456 L.DomUtil.enableTextSelection();
457
458 this._tooltip.dispose();
459 this._tooltip = null;
460
461 L.DomEvent.off(this._container, 'keyup', this._cancelDrawing, this);
462 }
463 },
464
465 // @method setOptions(object): void
466 // Sets new options to this handler
467 setOptions: function (options) {
468 L.setOptions(this, options);
469 },
470
471 _fireCreatedEvent: function (layer) {
472 this._map.fire(L.Draw.Event.CREATED, {layer: layer, layerType: this.type});
473 },
474
475 // Cancel drawing when the escape key is pressed
476 _cancelDrawing: function (e) {
477 if (e.keyCode === 27) {
478 this._map.fire('draw:canceled', {layerType: this.type});
479 this.disable();
480 }
481 }
482 });
483
484
485
486 /**
487 * @class L.Draw.Polyline
488 * @aka Draw.Polyline
489 * @inherits L.Draw.Feature
490 */
491 L.Draw.Polyline = L.Draw.Feature.extend({
492 statics: {
493 TYPE: 'polyline'
494 },
495
496 Poly: L.Polyline,
497
498 options: {
499 allowIntersection: true,
500 repeatMode: false,
501 drawError: {
502 color: '#b00b00',
503 timeout: 2500
504 },
505 icon: new L.DivIcon({
506 iconSize: new L.Point(8, 8),
507 className: 'leaflet-div-icon leaflet-editing-icon'
508 }),
509 touchIcon: new L.DivIcon({
510 iconSize: new L.Point(20, 20),
511 className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'
512 }),
513 guidelineDistance: 20,
514 maxGuideLineLength: 4000,
515 shapeOptions: {
516 stroke: true,
517 color: '#3388ff',
518 weight: 4,
519 opacity: 0.5,
520 fill: false,
521 clickable: true
522 },
523 metric: true, // Whether to use the metric measurement system or imperial
524 feet: true, // When not metric, to use feet instead of yards for display.
525 nautic: false, // When not metric, not feet use nautic mile for display
526 showLength: true, // Whether to display distance in the tooltip
527 zIndexOffset: 2000, // This should be > than the highest z-index any map layers
528 factor: 1, // To change distance calculation
529 maxPoints: 0 // Once this number of points are placed, finish shape
530 },
531
532 // @method initialize(): void
533 initialize: function (map, options) {
534 // if touch, switch to touch icon
535 if (L.Browser.touch) {
536 this.options.icon = this.options.touchIcon;
537 }
538
539 // Need to set this here to ensure the correct message is used.
540 this.options.drawError.message = L.drawLocal.draw.handlers.polyline.error;
541
542 // Merge default drawError options with custom options
543 if (options && options.drawError) {
544 options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
545 }
546
547 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
548 this.type = L.Draw.Polyline.TYPE;
549
550 L.Draw.Feature.prototype.initialize.call(this, map, options);
551 },
552
553 // @method addHooks(): void
554 // Add listener hooks to this handler
555 addHooks: function () {
556 L.Draw.Feature.prototype.addHooks.call(this);
557 if (this._map) {
558 this._markers = [];
559
560 this._markerGroup = new L.LayerGroup();
561 this._map.addLayer(this._markerGroup);
562
563 this._poly = new L.Polyline([], this.options.shapeOptions);
564
565 this._tooltip.updateContent(this._getTooltipText());
566
567 // Make a transparent marker that will used to catch click events. These click
568 // events will create the vertices. We need to do this so we can ensure that
569 // we can create vertices over other map layers (markers, vector layers). We
570 // also do not want to trigger any click handlers of objects we are clicking on
571 // while drawing.
572 if (!this._mouseMarker) {
573 this._mouseMarker = L.marker(this._map.getCenter(), {
574 icon: L.divIcon({
575 className: 'leaflet-mouse-marker',
576 iconAnchor: [20, 20],
577 iconSize: [40, 40]
578 }),
579 opacity: 0,
580 zIndexOffset: this.options.zIndexOffset
581 });
582 }
583
584 this._mouseMarker
585 .on('mouseout', this._onMouseOut, this)
586 .on('mousemove', this._onMouseMove, this) // Necessary to prevent 0.8 stutter
587 .on('mousedown', this._onMouseDown, this)
588 .on('mouseup', this._onMouseUp, this) // Necessary for 0.8 compatibility
589 .addTo(this._map);
590
591 this._map
592 .on('mouseup', this._onMouseUp, this) // Necessary for 0.7 compatibility
593 .on('mousemove', this._onMouseMove, this)
594 .on('zoomlevelschange', this._onZoomEnd, this)
595 .on('touchstart', this._onTouch, this)
596 .on('zoomend', this._onZoomEnd, this);
597
598 }
599 },
600
601 // @method removeHooks(): void
602 // Remove listener hooks from this handler.
603 removeHooks: function () {
604 L.Draw.Feature.prototype.removeHooks.call(this);
605
606 this._clearHideErrorTimeout();
607
608 this._cleanUpShape();
609
610 // remove markers from map
611 this._map.removeLayer(this._markerGroup);
612 delete this._markerGroup;
613 delete this._markers;
614
615 this._map.removeLayer(this._poly);
616 delete this._poly;
617
618 this._mouseMarker
619 .off('mousedown', this._onMouseDown, this)
620 .off('mouseout', this._onMouseOut, this)
621 .off('mouseup', this._onMouseUp, this)
622 .off('mousemove', this._onMouseMove, this);
623 this._map.removeLayer(this._mouseMarker);
624 delete this._mouseMarker;
625
626 // clean up DOM
627 this._clearGuides();
628
629 this._map
630 .off('mouseup', this._onMouseUp, this)
631 .off('mousemove', this._onMouseMove, this)
632 .off('zoomlevelschange', this._onZoomEnd, this)
633 .off('zoomend', this._onZoomEnd, this)
634 .off('touchstart', this._onTouch, this)
635 .off('click', this._onTouch, this);
636 },
637
638 // @method deleteLastVertex(): void
639 // Remove the last vertex from the polyline, removes polyline from map if only one point exists.
640 deleteLastVertex: function () {
641 if (this._markers.length <= 1) {
642 return;
643 }
644
645 var lastMarker = this._markers.pop(),
646 poly = this._poly,
647 // Replaces .spliceLatLngs()
648 latlngs = poly.getLatLngs(),
649 latlng = latlngs.splice(-1, 1)[0];
650 this._poly.setLatLngs(latlngs);
651
652 this._markerGroup.removeLayer(lastMarker);
653
654 if (poly.getLatLngs().length < 2) {
655 this._map.removeLayer(poly);
656 }
657
658 this._vertexChanged(latlng, false);
659 },
660
661 // @method addVertex(): void
662 // Add a vertex to the end of the polyline
663 addVertex: function (latlng) {
664 var markersLength = this._markers.length;
665 // markersLength must be greater than or equal to 2 before intersections can occur
666 if (markersLength >= 2 && !this.options.allowIntersection && this._poly.newLatLngIntersects(latlng)) {
667 this._showErrorTooltip();
668 return;
669 }
670 else if (this._errorShown) {
671 this._hideErrorTooltip();
672 }
673
674 this._markers.push(this._createMarker(latlng));
675
676 this._poly.addLatLng(latlng);
677
678 if (this._poly.getLatLngs().length === 2) {
679 this._map.addLayer(this._poly);
680 }
681
682 this._vertexChanged(latlng, true);
683 },
684
685 // @method completeShape(): void
686 // Closes the polyline between the first and last points
687 completeShape: function () {
688 if (this._markers.length <= 1) {
689 return;
690 }
691
692 this._fireCreatedEvent();
693 this.disable();
694
695 if (this.options.repeatMode) {
696 this.enable();
697 }
698 },
699
700 _finishShape: function () {
701 var latlngs = this._poly._defaultShape ? this._poly._defaultShape() : this._poly.getLatLngs();
702 var intersects = this._poly.newLatLngIntersects(latlngs[latlngs.length - 1]);
703
704 if ((!this.options.allowIntersection && intersects) || !this._shapeIsValid()) {
705 this._showErrorTooltip();
706 return;
707 }
708
709 this._fireCreatedEvent();
710 this.disable();
711 if (this.options.repeatMode) {
712 this.enable();
713 }
714 },
715
716 // Called to verify the shape is valid when the user tries to finish it
717 // Return false if the shape is not valid
718 _shapeIsValid: function () {
719 return true;
720 },
721
722 _onZoomEnd: function () {
723 if (this._markers !== null) {
724 this._updateGuide();
725 }
726 },
727
728 _onMouseMove: function (e) {
729 var newPos = this._map.mouseEventToLayerPoint(e.originalEvent);
730 var latlng = this._map.layerPointToLatLng(newPos);
731
732 // Save latlng
733 // should this be moved to _updateGuide() ?
734 this._currentLatLng = latlng;
735
736 this._updateTooltip(latlng);
737
738 // Update the guide line
739 this._updateGuide(newPos);
740
741 // Update the mouse marker position
742 this._mouseMarker.setLatLng(latlng);
743
744 L.DomEvent.preventDefault(e.originalEvent);
745 },
746
747 _vertexChanged: function (latlng, added) {
748 this._map.fire(L.Draw.Event.DRAWVERTEX, {layers: this._markerGroup});
749 this._updateFinishHandler();
750
751 this._updateRunningMeasure(latlng, added);
752
753 this._clearGuides();
754
755 this._updateTooltip();
756 },
757
758 _onMouseDown: function (e) {
759 if (!this._clickHandled && !this._touchHandled && !this._disableMarkers) {
760 this._onMouseMove(e);
761 this._clickHandled = true;
762 this._disableNewMarkers();
763 var originalEvent = e.originalEvent;
764 var clientX = originalEvent.clientX;
765 var clientY = originalEvent.clientY;
766 this._startPoint.call(this, clientX, clientY);
767 }
768 },
769
770 _startPoint: function (clientX, clientY) {
771 this._mouseDownOrigin = L.point(clientX, clientY);
772 },
773
774 _onMouseUp: function (e) {
775 var originalEvent = e.originalEvent;
776 var clientX = originalEvent.clientX;
777 var clientY = originalEvent.clientY;
778 this._endPoint.call(this, clientX, clientY, e);
779 this._clickHandled = null;
780 },
781
782 _endPoint: function (clientX, clientY, e) {
783 if (this._mouseDownOrigin) {
784 var dragCheckDistance = L.point(clientX, clientY)
785 .distanceTo(this._mouseDownOrigin);
786 var lastPtDistance = this._calculateFinishDistance(e.latlng);
787 if (this.options.maxPoints > 1 && this.options.maxPoints == this._markers.length + 1) {
788 this.addVertex(e.latlng);
789 this._finishShape();
790 } else if (lastPtDistance < 10 && L.Browser.touch) {
791 this._finishShape();
792 } else if (Math.abs(dragCheckDistance) < 9 * (window.devicePixelRatio || 1)) {
793 this.addVertex(e.latlng);
794 }
795 this._enableNewMarkers(); // after a short pause, enable new markers
796 }
797 this._mouseDownOrigin = null;
798 },
799
800 // ontouch prevented by clickHandled flag because some browsers fire both click/touch events,
801 // causing unwanted behavior
802 _onTouch: function (e) {
803 var originalEvent = e.originalEvent;
804 var clientX;
805 var clientY;
806 if (originalEvent.touches && originalEvent.touches[0] && !this._clickHandled && !this._touchHandled && !this._disableMarkers) {
807 clientX = originalEvent.touches[0].clientX;
808 clientY = originalEvent.touches[0].clientY;
809 this._disableNewMarkers();
810 this._touchHandled = true;
811 this._startPoint.call(this, clientX, clientY);
812 this._endPoint.call(this, clientX, clientY, e);
813 this._touchHandled = null;
814 }
815 this._clickHandled = null;
816 },
817
818 _onMouseOut: function () {
819 if (this._tooltip) {
820 this._tooltip._onMouseOut.call(this._tooltip);
821 }
822 },
823
824 // calculate if we are currently within close enough distance
825 // of the closing point (first point for shapes, last point for lines)
826 // this is semi-ugly code but the only reliable way i found to get the job done
827 // note: calculating point.distanceTo between mouseDownOrigin and last marker did NOT work
828 _calculateFinishDistance: function (potentialLatLng) {
829 var lastPtDistance;
830 if (this._markers.length > 0) {
831 var finishMarker;
832 if (this.type === L.Draw.Polyline.TYPE) {
833 finishMarker = this._markers[this._markers.length - 1];
834 } else if (this.type === L.Draw.Polygon.TYPE) {
835 finishMarker = this._markers[0];
836 } else {
837 return Infinity;
838 }
839 var lastMarkerPoint = this._map.latLngToContainerPoint(finishMarker.getLatLng()),
840 potentialMarker = new L.Marker(potentialLatLng, {
841 icon: this.options.icon,
842 zIndexOffset: this.options.zIndexOffset * 2
843 });
844 var potentialMarkerPint = this._map.latLngToContainerPoint(potentialMarker.getLatLng());
845 lastPtDistance = lastMarkerPoint.distanceTo(potentialMarkerPint);
846 } else {
847 lastPtDistance = Infinity;
848 }
849 return lastPtDistance;
850 },
851
852 _updateFinishHandler: function () {
853 var markerCount = this._markers.length;
854 // The last marker should have a click handler to close the polyline
855 if (markerCount > 1) {
856 this._markers[markerCount - 1].on('click', this._finishShape, this);
857 }
858
859 // Remove the old marker click handler (as only the last point should close the polyline)
860 if (markerCount > 2) {
861 this._markers[markerCount - 2].off('click', this._finishShape, this);
862 }
863 },
864
865 _createMarker: function (latlng) {
866 var marker = new L.Marker(latlng, {
867 icon: this.options.icon,
868 zIndexOffset: this.options.zIndexOffset * 2
869 });
870
871 this._markerGroup.addLayer(marker);
872
873 return marker;
874 },
875
876 _updateGuide: function (newPos) {
877 var markerCount = this._markers ? this._markers.length : 0;
878
879 if (markerCount > 0) {
880 newPos = newPos || this._map.latLngToLayerPoint(this._currentLatLng);
881
882 // draw the guide line
883 this._clearGuides();
884 this._drawGuide(
885 this._map.latLngToLayerPoint(this._markers[markerCount - 1].getLatLng()),
886 newPos
887 );
888 }
889 },
890
891 _updateTooltip: function (latLng) {
892 var text = this._getTooltipText();
893
894 if (latLng) {
895 this._tooltip.updatePosition(latLng);
896 }
897
898 if (!this._errorShown) {
899 this._tooltip.updateContent(text);
900 }
901 },
902
903 _drawGuide: function (pointA, pointB) {
904 var length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) + Math.pow((pointB.y - pointA.y), 2))),
905 guidelineDistance = this.options.guidelineDistance,
906 maxGuideLineLength = this.options.maxGuideLineLength,
907 // Only draw a guideline with a max length
908 i = length > maxGuideLineLength ? length - maxGuideLineLength : guidelineDistance,
909 fraction,
910 dashPoint,
911 dash;
912
913 //create the guides container if we haven't yet
914 if (!this._guidesContainer) {
915 this._guidesContainer = L.DomUtil.create('div', 'leaflet-draw-guides', this._overlayPane);
916 }
917
918 //draw a dash every GuildeLineDistance
919 for (; i < length; i += this.options.guidelineDistance) {
920 //work out fraction along line we are
921 fraction = i / length;
922
923 //calculate new x,y point
924 dashPoint = {
925 x: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)),
926 y: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y))
927 };
928
929 //add guide dash to guide container
930 dash = L.DomUtil.create('div', 'leaflet-draw-guide-dash', this._guidesContainer);
931 dash.style.backgroundColor =
932 !this._errorShown ? this.options.shapeOptions.color : this.options.drawError.color;
933
934 L.DomUtil.setPosition(dash, dashPoint);
935 }
936 },
937
938 _updateGuideColor: function (color) {
939 if (this._guidesContainer) {
940 for (var i = 0, l = this._guidesContainer.childNodes.length; i < l; i++) {
941 this._guidesContainer.childNodes[i].style.backgroundColor = color;
942 }
943 }
944 },
945
946 // removes all child elements (guide dashes) from the guides container
947 _clearGuides: function () {
948 if (this._guidesContainer) {
949 while (this._guidesContainer.firstChild) {
950 this._guidesContainer.removeChild(this._guidesContainer.firstChild);
951 }
952 }
953 },
954
955 _getTooltipText: function () {
956 var showLength = this.options.showLength,
957 labelText, distanceStr;
958 if (this._markers.length === 0) {
959 labelText = {
960 text: L.drawLocal.draw.handlers.polyline.tooltip.start
961 };
962 } else {
963 distanceStr = showLength ? this._getMeasurementString() : '';
964
965 if (this._markers.length === 1) {
966 labelText = {
967 text: L.drawLocal.draw.handlers.polyline.tooltip.cont,
968 subtext: distanceStr
969 };
970 } else {
971 labelText = {
972 text: L.drawLocal.draw.handlers.polyline.tooltip.end,
973 subtext: distanceStr
974 };
975 }
976 }
977 return labelText;
978 },
979
980 _updateRunningMeasure: function (latlng, added) {
981 var markersLength = this._markers.length,
982 previousMarkerIndex, distance;
983
984 if (this._markers.length === 1) {
985 this._measurementRunningTotal = 0;
986 } else {
987 previousMarkerIndex = markersLength - (added ? 2 : 1);
988
989 // Calculate the distance based on the version
990 if (L.GeometryUtil.isVersion07x()) {
991 distance = latlng.distanceTo(this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1);
992 } else {
993 distance = this._map.distance(latlng, this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1);
994 }
995
996 this._measurementRunningTotal += distance * (added ? 1 : -1);
997 }
998 },
999
1000 _getMeasurementString: function () {
1001 var currentLatLng = this._currentLatLng,
1002 previousLatLng = this._markers[this._markers.length - 1].getLatLng(),
1003 distance;
1004
1005 // Calculate the distance from the last fixed point to the mouse position based on the version
1006 if (L.GeometryUtil.isVersion07x()) {
1007 distance = previousLatLng && currentLatLng && currentLatLng.distanceTo ? this._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0;
1008 } else {
1009 distance = previousLatLng && currentLatLng ? this._measurementRunningTotal + this._map.distance(currentLatLng, previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0;
1010 }
1011
1012 return L.GeometryUtil.readableDistance(distance, this.options.metric, this.options.feet, this.options.nautic, this.options.precision);
1013 },
1014
1015 _showErrorTooltip: function () {
1016 this._errorShown = true;
1017
1018 // Update tooltip
1019 this._tooltip
1020 .showAsError()
1021 .updateContent({text: this.options.drawError.message});
1022
1023 // Update shape
1024 this._updateGuideColor(this.options.drawError.color);
1025 this._poly.setStyle({color: this.options.drawError.color});
1026
1027 // Hide the error after 2 seconds
1028 this._clearHideErrorTimeout();
1029 this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout);
1030 },
1031
1032 _hideErrorTooltip: function () {
1033 this._errorShown = false;
1034
1035 this._clearHideErrorTimeout();
1036
1037 // Revert tooltip
1038 this._tooltip
1039 .removeError()
1040 .updateContent(this._getTooltipText());
1041
1042 // Revert shape
1043 this._updateGuideColor(this.options.shapeOptions.color);
1044 this._poly.setStyle({color: this.options.shapeOptions.color});
1045 },
1046
1047 _clearHideErrorTimeout: function () {
1048 if (this._hideErrorTimeout) {
1049 clearTimeout(this._hideErrorTimeout);
1050 this._hideErrorTimeout = null;
1051 }
1052 },
1053
1054 // disable new markers temporarily;
1055 // this is to prevent duplicated touch/click events in some browsers
1056 _disableNewMarkers: function () {
1057 this._disableMarkers = true;
1058 },
1059
1060 // see _disableNewMarkers
1061 _enableNewMarkers: function () {
1062 setTimeout(function () {
1063 this._disableMarkers = false;
1064 }.bind(this), 50);
1065 },
1066
1067 _cleanUpShape: function () {
1068 if (this._markers.length > 1) {
1069 this._markers[this._markers.length - 1].off('click', this._finishShape, this);
1070 }
1071 },
1072
1073 _fireCreatedEvent: function () {
1074 var poly = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions);
1075 L.Draw.Feature.prototype._fireCreatedEvent.call(this, poly);
1076 }
1077 });
1078
1079
1080
1081 /**
1082 * @class L.Draw.Polygon
1083 * @aka Draw.Polygon
1084 * @inherits L.Draw.Polyline
1085 */
1086 L.Draw.Polygon = L.Draw.Polyline.extend({
1087 statics: {
1088 TYPE: 'polygon'
1089 },
1090
1091 Poly: L.Polygon,
1092
1093 options: {
1094 showArea: false,
1095 showLength: false,
1096 shapeOptions: {
1097 stroke: true,
1098 color: '#3388ff',
1099 weight: 4,
1100 opacity: 0.5,
1101 fill: true,
1102 fillColor: null, //same as color by default
1103 fillOpacity: 0.2,
1104 clickable: true
1105 },
1106 // Whether to use the metric measurement system (truthy) or not (falsy).
1107 // Also defines the units to use for the metric system as an array of
1108 // strings (e.g. `['ha', 'm']`).
1109 metric: true,
1110 feet: true, // When not metric, to use feet instead of yards for display.
1111 nautic: false, // When not metric, not feet use nautic mile for display
1112 // Defines the precision for each type of unit (e.g. {km: 2, ft: 0}
1113 precision: {}
1114 },
1115
1116 // @method initialize(): void
1117 initialize: function (map, options) {
1118 L.Draw.Polyline.prototype.initialize.call(this, map, options);
1119
1120 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
1121 this.type = L.Draw.Polygon.TYPE;
1122 },
1123
1124 _updateFinishHandler: function () {
1125 var markerCount = this._markers.length;
1126
1127 // The first marker should have a click handler to close the polygon
1128 if (markerCount === 1) {
1129 this._markers[0].on('click', this._finishShape, this);
1130 }
1131
1132 // Add and update the double click handler
1133 if (markerCount > 2) {
1134 this._markers[markerCount - 1].on('dblclick', this._finishShape, this);
1135 // Only need to remove handler if has been added before
1136 if (markerCount > 3) {
1137 this._markers[markerCount - 2].off('dblclick', this._finishShape, this);
1138 }
1139 }
1140 },
1141
1142 _getTooltipText: function () {
1143 var text, subtext;
1144
1145 if (this._markers.length === 0) {
1146 text = L.drawLocal.draw.handlers.polygon.tooltip.start;
1147 } else if (this._markers.length < 3) {
1148 text = L.drawLocal.draw.handlers.polygon.tooltip.cont;
1149 subtext = this._getMeasurementString();
1150 } else {
1151 text = L.drawLocal.draw.handlers.polygon.tooltip.end;
1152 subtext = this._getMeasurementString();
1153 }
1154
1155 return {
1156 text: text,
1157 subtext: subtext
1158 };
1159 },
1160
1161 _getMeasurementString: function () {
1162 var area = this._area,
1163 measurementString = '';
1164
1165
1166 if (!area && !this.options.showLength) {
1167 return null;
1168 }
1169
1170 if (this.options.showLength) {
1171 measurementString = L.Draw.Polyline.prototype._getMeasurementString.call(this);
1172 }
1173
1174 if (area) {
1175 measurementString += '<br>' + L.GeometryUtil.readableArea(area, this.options.metric, this.options.precision);
1176 }
1177
1178 return measurementString;
1179 },
1180
1181 _shapeIsValid: function () {
1182 return this._markers.length >= 3;
1183 },
1184
1185 _vertexChanged: function (latlng, added) {
1186 var latLngs;
1187
1188 // Check to see if we should show the area
1189 if (!this.options.allowIntersection && this.options.showArea) {
1190 latLngs = this._poly.getLatLngs();
1191
1192 this._area = L.GeometryUtil.geodesicArea(latLngs);
1193 }
1194
1195 L.Draw.Polyline.prototype._vertexChanged.call(this, latlng, added);
1196 },
1197
1198 _cleanUpShape: function () {
1199 var markerCount = this._markers.length;
1200
1201 if (markerCount > 0) {
1202 this._markers[0].off('click', this._finishShape, this);
1203
1204 if (markerCount > 2) {
1205 this._markers[markerCount - 1].off('dblclick', this._finishShape, this);
1206 }
1207 }
1208 }
1209 });
1210
1211
1212
1213 L.SimpleShape = {};
1214 /**
1215 * @class L.Draw.SimpleShape
1216 * @aka Draw.SimpleShape
1217 * @inherits L.Draw.Feature
1218 */
1219 L.Draw.SimpleShape = L.Draw.Feature.extend({
1220 options: {
1221 repeatMode: false
1222 },
1223
1224 // @method initialize(): void
1225 initialize: function (map, options) {
1226 this._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end;
1227
1228 L.Draw.Feature.prototype.initialize.call(this, map, options);
1229 },
1230
1231 // @method addHooks(): void
1232 // Add listener hooks to this handler.
1233 addHooks: function () {
1234 L.Draw.Feature.prototype.addHooks.call(this);
1235 if (this._map) {
1236 this._mapDraggable = this._map.dragging.enabled();
1237
1238 if (this._mapDraggable) {
1239 this._map.dragging.disable();
1240 }
1241
1242 //TODO refactor: move cursor to styles
1243 this._container.style.cursor = 'crosshair';
1244
1245 this._tooltip.updateContent({text: this._initialLabelText});
1246
1247 this._map
1248 .on('mousedown', this._onMouseDown, this)
1249 .on('mousemove', this._onMouseMove, this)
1250 .on('touchstart', this._onMouseDown, this)
1251 .on('touchmove', this._onMouseMove, this);
1252
1253 // we should prevent default, otherwise default behavior (scrolling) will fire,
1254 // and that will cause document.touchend to fire and will stop the drawing
1255 // (circle, rectangle) in touch mode.
1256 // (update): we have to send passive now to prevent scroll, because by default it is {passive: true} now, which means,
1257 // handler can't event.preventDefault
1258 // check the news https://developers.google.com/web/updates/2016/06/passive-event-listeners
1259 document.addEventListener('touchstart', L.DomEvent.preventDefault, {passive: false});
1260 }
1261 },
1262
1263 // @method removeHooks(): void
1264 // Remove listener hooks from this handler.
1265 removeHooks: function () {
1266 L.Draw.Feature.prototype.removeHooks.call(this);
1267 if (this._map) {
1268 if (this._mapDraggable) {
1269 this._map.dragging.enable();
1270 }
1271
1272 //TODO refactor: move cursor to styles
1273 this._container.style.cursor = '';
1274
1275 this._map
1276 .off('mousedown', this._onMouseDown, this)
1277 .off('mousemove', this._onMouseMove, this)
1278 .off('touchstart', this._onMouseDown, this)
1279 .off('touchmove', this._onMouseMove, this);
1280
1281 L.DomEvent.off(document, 'mouseup', this._onMouseUp, this);
1282 L.DomEvent.off(document, 'touchend', this._onMouseUp, this);
1283
1284 document.removeEventListener('touchstart', L.DomEvent.preventDefault);
1285
1286 // If the box element doesn't exist they must not have moved the mouse, so don't need to destroy/return
1287 if (this._shape) {
1288 this._map.removeLayer(this._shape);
1289 delete this._shape;
1290 }
1291 }
1292 this._isDrawing = false;
1293 },
1294
1295 _getTooltipText: function () {
1296 return {
1297 text: this._endLabelText
1298 };
1299 },
1300
1301 _onMouseDown: function (e) {
1302 this._isDrawing = true;
1303 this._startLatLng = e.latlng;
1304
1305 L.DomEvent
1306 .on(document, 'mouseup', this._onMouseUp, this)
1307 .on(document, 'touchend', this._onMouseUp, this)
1308 .preventDefault(e.originalEvent);
1309 },
1310
1311 _onMouseMove: function (e) {
1312 var latlng = e.latlng;
1313
1314 this._tooltip.updatePosition(latlng);
1315 if (this._isDrawing) {
1316 this._tooltip.updateContent(this._getTooltipText());
1317 this._drawShape(latlng);
1318 }
1319 },
1320
1321 _onMouseUp: function () {
1322 if (this._shape) {
1323 this._fireCreatedEvent();
1324 }
1325
1326 this.disable();
1327 if (this.options.repeatMode) {
1328 this.enable();
1329 }
1330 }
1331 });
1332
1333
1334
1335 /**
1336 * @class L.Draw.Rectangle
1337 * @aka Draw.Rectangle
1338 * @inherits L.Draw.SimpleShape
1339 */
1340 L.Draw.Rectangle = L.Draw.SimpleShape.extend({
1341 statics: {
1342 TYPE: 'rectangle'
1343 },
1344
1345 options: {
1346 shapeOptions: {
1347 stroke: true,
1348 color: '#3388ff',
1349 weight: 4,
1350 opacity: 0.5,
1351 fill: true,
1352 fillColor: null, //same as color by default
1353 fillOpacity: 0.2,
1354 showArea: true,
1355 clickable: true
1356 },
1357 metric: true // Whether to use the metric measurement system or imperial
1358 },
1359
1360 // @method initialize(): void
1361 initialize: function (map, options) {
1362 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
1363 this.type = L.Draw.Rectangle.TYPE;
1364
1365 this._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start;
1366
1367 L.Draw.SimpleShape.prototype.initialize.call(this, map, options);
1368 },
1369
1370 // @method disable(): void
1371 disable: function () {
1372 if (!this._enabled) {
1373 return;
1374 }
1375
1376 this._isCurrentlyTwoClickDrawing = false;
1377 L.Draw.SimpleShape.prototype.disable.call(this);
1378 },
1379
1380 _onMouseUp: function (e) {
1381 if (!this._shape && !this._isCurrentlyTwoClickDrawing) {
1382 this._isCurrentlyTwoClickDrawing = true;
1383 return;
1384 }
1385
1386 // Make sure closing click is on map
1387 if (this._isCurrentlyTwoClickDrawing && !_hasAncestor(e.target, 'leaflet-pane')) {
1388 return;
1389 }
1390
1391 L.Draw.SimpleShape.prototype._onMouseUp.call(this);
1392 },
1393
1394 _drawShape: function (latlng) {
1395 if (!this._shape) {
1396 this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, latlng), this.options.shapeOptions);
1397 this._map.addLayer(this._shape);
1398 } else {
1399 this._shape.setBounds(new L.LatLngBounds(this._startLatLng, latlng));
1400 }
1401 },
1402
1403 _fireCreatedEvent: function () {
1404 var rectangle = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions);
1405 L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, rectangle);
1406 },
1407
1408 _getTooltipText: function () {
1409 var tooltipText = L.Draw.SimpleShape.prototype._getTooltipText.call(this),
1410 shape = this._shape,
1411 showArea = this.options.showArea,
1412 latLngs, area, subtext;
1413
1414 if (shape) {
1415 latLngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs();
1416 area = L.GeometryUtil.geodesicArea(latLngs);
1417 subtext = showArea ? L.GeometryUtil.readableArea(area, this.options.metric) : '';
1418 }
1419
1420 return {
1421 text: tooltipText.text,
1422 subtext: subtext
1423 };
1424 }
1425 });
1426
1427 function _hasAncestor(el, cls) {
1428 while ((el = el.parentElement) && !el.classList.contains(cls)) {
1429 ;
1430 }
1431 return el;
1432 }
1433
1434
1435
1436 /**
1437 * @class L.Draw.Marker
1438 * @aka Draw.Marker
1439 * @inherits L.Draw.Feature
1440 */
1441 L.Draw.Marker = L.Draw.Feature.extend({
1442 statics: {
1443 TYPE: 'marker'
1444 },
1445
1446 options: {
1447 icon: new L.Icon.Default(),
1448 repeatMode: false,
1449 zIndexOffset: 2000 // This should be > than the highest z-index any markers
1450 },
1451
1452 // @method initialize(): void
1453 initialize: function (map, options) {
1454 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
1455 this.type = L.Draw.Marker.TYPE;
1456
1457 this._initialLabelText = L.drawLocal.draw.handlers.marker.tooltip.start;
1458
1459 L.Draw.Feature.prototype.initialize.call(this, map, options);
1460 },
1461
1462 // @method addHooks(): void
1463 // Add listener hooks to this handler.
1464 addHooks: function () {
1465 L.Draw.Feature.prototype.addHooks.call(this);
1466
1467 if (this._map) {
1468 this._tooltip.updateContent({text: this._initialLabelText});
1469
1470 // Same mouseMarker as in Draw.Polyline
1471 if (!this._mouseMarker) {
1472 this._mouseMarker = L.marker(this._map.getCenter(), {
1473 icon: L.divIcon({
1474 className: 'leaflet-mouse-marker',
1475 iconAnchor: [20, 20],
1476 iconSize: [40, 40]
1477 }),
1478 opacity: 0,
1479 zIndexOffset: this.options.zIndexOffset
1480 });
1481 }
1482
1483 this._mouseMarker
1484 .on('click', this._onClick, this)
1485 .addTo(this._map);
1486
1487 this._map.on('mousemove', this._onMouseMove, this);
1488 this._map.on('click', this._onTouch, this);
1489 }
1490 },
1491
1492 // @method removeHooks(): void
1493 // Remove listener hooks from this handler.
1494 removeHooks: function () {
1495 L.Draw.Feature.prototype.removeHooks.call(this);
1496
1497 if (this._map) {
1498 this._map
1499 .off('click', this._onClick, this)
1500 .off('click', this._onTouch, this);
1501 if (this._marker) {
1502 this._marker.off('click', this._onClick, this);
1503 this._map
1504 .removeLayer(this._marker);
1505 delete this._marker;
1506 }
1507
1508 this._mouseMarker.off('click', this._onClick, this);
1509 this._map.removeLayer(this._mouseMarker);
1510 delete this._mouseMarker;
1511
1512 this._map.off('mousemove', this._onMouseMove, this);
1513 }
1514 },
1515
1516 _onMouseMove: function (e) {
1517 var latlng = e.latlng;
1518
1519 this._tooltip.updatePosition(latlng);
1520 this._mouseMarker.setLatLng(latlng);
1521
1522 if (!this._marker) {
1523 this._marker = this._createMarker(latlng);
1524 // Bind to both marker and map to make sure we get the click event.
1525 this._marker.on('click', this._onClick, this);
1526 this._map
1527 .on('click', this._onClick, this)
1528 .addLayer(this._marker);
1529 }
1530 else {
1531 latlng = this._mouseMarker.getLatLng();
1532 this._marker.setLatLng(latlng);
1533 }
1534 },
1535
1536 _createMarker: function (latlng) {
1537 return new L.Marker(latlng, {
1538 icon: this.options.icon,
1539 zIndexOffset: this.options.zIndexOffset
1540 });
1541 },
1542
1543 _onClick: function () {
1544 this._fireCreatedEvent();
1545
1546 this.disable();
1547 if (this.options.repeatMode) {
1548 this.enable();
1549 }
1550 },
1551
1552 _onTouch: function (e) {
1553 // called on click & tap, only really does any thing on tap
1554 this._onMouseMove(e); // creates & places marker
1555 this._onClick(); // permanently places marker & ends interaction
1556 },
1557
1558 _fireCreatedEvent: function () {
1559 var marker = new L.Marker.Touch(this._marker.getLatLng(), {icon: this.options.icon});
1560 L.Draw.Feature.prototype._fireCreatedEvent.call(this, marker);
1561 }
1562 });
1563
1564
1565
1566 /**
1567 * @class L.Draw.CircleMarker
1568 * @aka Draw.CircleMarker
1569 * @inherits L.Draw.Marker
1570 */
1571 L.Draw.CircleMarker = L.Draw.Marker.extend({
1572 statics: {
1573 TYPE: 'circlemarker'
1574 },
1575
1576 options: {
1577 stroke: true,
1578 color: '#3388ff',
1579 weight: 4,
1580 opacity: 0.5,
1581 fill: true,
1582 fillColor: null, //same as color by default
1583 fillOpacity: 0.2,
1584 clickable: true,
1585 zIndexOffset: 2000 // This should be > than the highest z-index any markers
1586 },
1587
1588 // @method initialize(): void
1589 initialize: function (map, options) {
1590 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
1591 this.type = L.Draw.CircleMarker.TYPE;
1592
1593 this._initialLabelText = L.drawLocal.draw.handlers.circlemarker.tooltip.start;
1594
1595 L.Draw.Feature.prototype.initialize.call(this, map, options);
1596 },
1597
1598
1599 _fireCreatedEvent: function () {
1600 var circleMarker = new L.CircleMarker(this._marker.getLatLng(), this.options);
1601 L.Draw.Feature.prototype._fireCreatedEvent.call(this, circleMarker);
1602 },
1603
1604 _createMarker: function (latlng) {
1605 return new L.CircleMarker(latlng, this.options);
1606 }
1607 });
1608
1609
1610
1611 /**
1612 * @class L.Draw.Circle
1613 * @aka Draw.Circle
1614 * @inherits L.Draw.SimpleShape
1615 */
1616 L.Draw.Circle = L.Draw.SimpleShape.extend({
1617 statics: {
1618 TYPE: 'circle'
1619 },
1620
1621 options: {
1622 shapeOptions: {
1623 stroke: true,
1624 color: '#3388ff',
1625 weight: 4,
1626 opacity: 0.5,
1627 fill: true,
1628 fillColor: null, //same as color by default
1629 fillOpacity: 0.2,
1630 clickable: true
1631 },
1632 showRadius: true,
1633 metric: true, // Whether to use the metric measurement system or imperial
1634 feet: true, // When not metric, use feet instead of yards for display
1635 nautic: false // When not metric, not feet use nautic mile for display
1636 },
1637
1638 // @method initialize(): void
1639 initialize: function (map, options) {
1640 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
1641 this.type = L.Draw.Circle.TYPE;
1642
1643 this._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start;
1644
1645 L.Draw.SimpleShape.prototype.initialize.call(this, map, options);
1646 },
1647
1648 _drawShape: function (latlng) {
1649 // Calculate the distance based on the version
1650 if (L.GeometryUtil.isVersion07x()) {
1651 var distance = this._startLatLng.distanceTo(latlng);
1652 } else {
1653 var distance = this._map.distance(this._startLatLng, latlng);
1654 }
1655
1656 if (!this._shape) {
1657 this._shape = new L.Circle(this._startLatLng, distance, this.options.shapeOptions);
1658 this._map.addLayer(this._shape);
1659 } else {
1660 this._shape.setRadius(distance);
1661 }
1662 },
1663
1664 _fireCreatedEvent: function () {
1665 var circle = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions);
1666 L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, circle);
1667 },
1668
1669 _onMouseMove: function (e) {
1670 var latlng = e.latlng,
1671 showRadius = this.options.showRadius,
1672 useMetric = this.options.metric,
1673 radius;
1674
1675 this._tooltip.updatePosition(latlng);
1676 if (this._isDrawing) {
1677 this._drawShape(latlng);
1678
1679 // Get the new radius (rounded to 1 dp)
1680 radius = this._shape.getRadius().toFixed(1);
1681
1682 var subtext = '';
1683 if (showRadius) {
1684 subtext = L.drawLocal.draw.handlers.circle.radius + ': ' +
1685 L.GeometryUtil.readableDistance(radius, useMetric, this.options.feet, this.options.nautic);
1686 }
1687 this._tooltip.updateContent({
1688 text: this._endLabelText,
1689 subtext: subtext
1690 });
1691 }
1692 }
1693 });
1694
1695
1696
1697 L.Edit = L.Edit || {};
1698
1699 /**
1700 * @class L.Edit.Marker
1701 * @aka Edit.Marker
1702 */
1703 L.Edit.Marker = L.Handler.extend({
1704 // @method initialize(): void
1705 initialize: function (marker, options) {
1706 this._marker = marker;
1707 L.setOptions(this, options);
1708 },
1709
1710 // @method addHooks(): void
1711 // Add listener hooks to this handler
1712 addHooks: function () {
1713 var marker = this._marker;
1714
1715 marker.dragging.enable();
1716 marker.on('dragend', this._onDragEnd, marker);
1717 this._toggleMarkerHighlight();
1718 },
1719
1720 // @method removeHooks(): void
1721 // Remove listener hooks from this handler
1722 removeHooks: function () {
1723 var marker = this._marker;
1724
1725 marker.dragging.disable();
1726 marker.off('dragend', this._onDragEnd, marker);
1727 this._toggleMarkerHighlight();
1728 },
1729
1730 _onDragEnd: function (e) {
1731 var layer = e.target;
1732 layer.edited = true;
1733 this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer});
1734 },
1735
1736 _toggleMarkerHighlight: function () {
1737 var icon = this._marker._icon;
1738
1739 // Don't do anything if this layer is a marker but doesn't have an icon. Markers
1740 // should usually have icons. If using Leaflet.draw with Leaflet.markercluster there
1741 // is a chance that a marker doesn't.
1742 if (!icon) {
1743 return;
1744 }
1745
1746 // This is quite naughty, but I don't see another way of doing it. (short of setting a new icon)
1747 icon.style.display = 'none';
1748
1749 if (L.DomUtil.hasClass(icon, 'leaflet-edit-marker-selected')) {
1750 L.DomUtil.removeClass(icon, 'leaflet-edit-marker-selected');
1751 // Offset as the border will make the icon move.
1752 this._offsetMarker(icon, -4);
1753
1754 } else {
1755 L.DomUtil.addClass(icon, 'leaflet-edit-marker-selected');
1756 // Offset as the border will make the icon move.
1757 this._offsetMarker(icon, 4);
1758 }
1759
1760 icon.style.display = '';
1761 },
1762
1763 _offsetMarker: function (icon, offset) {
1764 var iconMarginTop = parseInt(icon.style.marginTop, 10) - offset,
1765 iconMarginLeft = parseInt(icon.style.marginLeft, 10) - offset;
1766
1767 icon.style.marginTop = iconMarginTop + 'px';
1768 icon.style.marginLeft = iconMarginLeft + 'px';
1769 }
1770 });
1771
1772 L.Marker.addInitHook(function () {
1773 if (L.Edit.Marker) {
1774 this.editing = new L.Edit.Marker(this);
1775
1776 if (this.options.editable) {
1777 this.editing.enable();
1778 }
1779 }
1780 });
1781
1782
1783
1784 L.Edit = L.Edit || {};
1785
1786 /**
1787 * @class L.Edit.Polyline
1788 * @aka L.Edit.Poly
1789 * @aka Edit.Poly
1790 */
1791 L.Edit.Poly = L.Handler.extend({
1792 // @method initialize(): void
1793 initialize: function (poly) {
1794
1795 this.latlngs = [poly._latlngs];
1796 if (poly._holes) {
1797 this.latlngs = this.latlngs.concat(poly._holes);
1798 }
1799
1800 this._poly = poly;
1801
1802 this._poly.on('revert-edited', this._updateLatLngs, this);
1803 },
1804
1805 // Compatibility method to normalize Poly* objects
1806 // between 0.7.x and 1.0+
1807 _defaultShape: function () {
1808 if (!L.Polyline._flat) {
1809 return this._poly._latlngs;
1810 }
1811 return L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0];
1812 },
1813
1814 _eachVertexHandler: function (callback) {
1815 for (var i = 0; i < this._verticesHandlers.length; i++) {
1816 callback(this._verticesHandlers[i]);
1817 }
1818 },
1819
1820 // @method addHooks(): void
1821 // Add listener hooks to this handler
1822 addHooks: function () {
1823 this._initHandlers();
1824 this._eachVertexHandler(function (handler) {
1825 handler.addHooks();
1826 });
1827 },
1828
1829 // @method removeHooks(): void
1830 // Remove listener hooks from this handler
1831 removeHooks: function () {
1832 this._eachVertexHandler(function (handler) {
1833 handler.removeHooks();
1834 });
1835 },
1836
1837 // @method updateMarkers(): void
1838 // Fire an update for each vertex handler
1839 updateMarkers: function () {
1840 this._eachVertexHandler(function (handler) {
1841 handler.updateMarkers();
1842 });
1843 },
1844
1845 _initHandlers: function () {
1846 this._verticesHandlers = [];
1847 for (var i = 0; i < this.latlngs.length; i++) {
1848 this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this._poly.options.poly));
1849 }
1850 },
1851
1852 _updateLatLngs: function (e) {
1853 this.latlngs = [e.layer._latlngs];
1854 if (e.layer._holes) {
1855 this.latlngs = this.latlngs.concat(e.layer._holes);
1856 }
1857 }
1858
1859 });
1860
1861 /**
1862 * @class L.Edit.PolyVerticesEdit
1863 * @aka Edit.PolyVerticesEdit
1864 */
1865 L.Edit.PolyVerticesEdit = L.Handler.extend({
1866 options: {
1867 icon: new L.DivIcon({
1868 iconSize: new L.Point(8, 8),
1869 className: 'leaflet-div-icon leaflet-editing-icon'
1870 }),
1871 touchIcon: new L.DivIcon({
1872 iconSize: new L.Point(20, 20),
1873 className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'
1874 }),
1875 drawError: {
1876 color: '#b00b00',
1877 timeout: 1000
1878 }
1879
1880
1881 },
1882
1883 // @method intialize(): void
1884 initialize: function (poly, latlngs, options) {
1885 // if touch, switch to touch icon
1886 if (L.Browser.touch) {
1887 this.options.icon = this.options.touchIcon;
1888 }
1889 this._poly = poly;
1890
1891 if (options && options.drawError) {
1892 options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
1893 }
1894
1895 this._latlngs = latlngs;
1896
1897 L.setOptions(this, options);
1898 },
1899
1900 // Compatibility method to normalize Poly* objects
1901 // between 0.7.x and 1.0+
1902 _defaultShape: function () {
1903 if (!L.Polyline._flat) {
1904 return this._latlngs;
1905 }
1906 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
1907 },
1908
1909 // @method addHooks(): void
1910 // Add listener hooks to this handler.
1911 addHooks: function () {
1912 var poly = this._poly;
1913 var path = poly._path;
1914
1915 if (!(poly instanceof L.Polygon)) {
1916 poly.options.fill = false;
1917 if (poly.options.editing) {
1918 poly.options.editing.fill = false;
1919 }
1920 }
1921
1922 if (path) {
1923 if (poly.options.editing.className) {
1924 if (poly.options.original.className) {
1925 poly.options.original.className.split(' ').forEach(function (className) {
1926 L.DomUtil.removeClass(path, className);
1927 });
1928 }
1929 poly.options.editing.className.split(' ').forEach(function (className) {
1930 L.DomUtil.addClass(path, className);
1931 });
1932 }
1933 }
1934
1935 poly.setStyle(poly.options.editing);
1936
1937 if (this._poly._map) {
1938
1939 this._map = this._poly._map; // Set map
1940
1941 if (!this._markerGroup) {
1942 this._initMarkers();
1943 }
1944 this._poly._map.addLayer(this._markerGroup);
1945 }
1946 },
1947
1948 // @method removeHooks(): void
1949 // Remove listener hooks from this handler.
1950 removeHooks: function () {
1951 var poly = this._poly;
1952 var path = poly._path;
1953
1954 if (path) {
1955 if (poly.options.editing.className) {
1956 poly.options.editing.className.split(' ').forEach(function (className) {
1957 L.DomUtil.removeClass(path, className);
1958 });
1959 if (poly.options.original.className) {
1960 poly.options.original.className.split(' ').forEach(function (className) {
1961 L.DomUtil.addClass(path, className);
1962 });
1963 }
1964 }
1965 }
1966
1967 poly.setStyle(poly.options.original);
1968
1969 if (poly._map) {
1970 poly._map.removeLayer(this._markerGroup);
1971 delete this._markerGroup;
1972 delete this._markers;
1973 }
1974 },
1975
1976 // @method updateMarkers(): void
1977 // Clear markers and update their location
1978 updateMarkers: function () {
1979 this._markerGroup.clearLayers();
1980 this._initMarkers();
1981 },
1982
1983 _initMarkers: function () {
1984 if (!this._markerGroup) {
1985 this._markerGroup = new L.LayerGroup();
1986 }
1987 this._markers = [];
1988
1989 var latlngs = this._defaultShape(),
1990 i, j, len, marker;
1991
1992 for (i = 0, len = latlngs.length; i < len; i++) {
1993
1994 marker = this._createMarker(latlngs[i], i);
1995 marker.on('click', this._onMarkerClick, this);
1996 marker.on('contextmenu', this._onContextMenu, this);
1997 this._markers.push(marker);
1998 }
1999
2000 var markerLeft, markerRight;
2001
2002 for (i = 0, j = len - 1; i < len; j = i++) {
2003 if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
2004 continue;
2005 }
2006
2007 markerLeft = this._markers[j];
2008 markerRight = this._markers[i];
2009
2010 this._createMiddleMarker(markerLeft, markerRight);
2011 this._updatePrevNext(markerLeft, markerRight);
2012 }
2013 },
2014
2015 _createMarker: function (latlng, index) {
2016 // Extending L.Marker in TouchEvents.js to include touch.
2017 var marker = new L.Marker.Touch(latlng, {
2018 draggable: true,
2019 icon: this.options.icon,
2020 });
2021
2022 marker._origLatLng = latlng;
2023 marker._index = index;
2024
2025 marker
2026 .on('dragstart', this._onMarkerDragStart, this)
2027 .on('drag', this._onMarkerDrag, this)
2028 .on('dragend', this._fireEdit, this)
2029 .on('touchmove', this._onTouchMove, this)
2030 .on('touchend', this._fireEdit, this)
2031 .on('MSPointerMove', this._onTouchMove, this)
2032 .on('MSPointerUp', this._fireEdit, this);
2033
2034 this._markerGroup.addLayer(marker);
2035
2036 return marker;
2037 },
2038
2039 _onMarkerDragStart: function () {
2040 this._poly.fire('editstart');
2041 },
2042
2043 _spliceLatLngs: function () {
2044 var latlngs = this._defaultShape();
2045 var removed = [].splice.apply(latlngs, arguments);
2046 this._poly._convertLatLngs(latlngs, true);
2047 this._poly.redraw();
2048 return removed;
2049 },
2050
2051 _removeMarker: function (marker) {
2052 var i = marker._index;
2053
2054 this._markerGroup.removeLayer(marker);
2055 this._markers.splice(i, 1);
2056 this._spliceLatLngs(i, 1);
2057 this._updateIndexes(i, -1);
2058
2059 marker
2060 .off('dragstart', this._onMarkerDragStart, this)
2061 .off('drag', this._onMarkerDrag, this)
2062 .off('dragend', this._fireEdit, this)
2063 .off('touchmove', this._onMarkerDrag, this)
2064 .off('touchend', this._fireEdit, this)
2065 .off('click', this._onMarkerClick, this)
2066 .off('MSPointerMove', this._onTouchMove, this)
2067 .off('MSPointerUp', this._fireEdit, this);
2068 },
2069
2070 _fireEdit: function () {
2071 this._poly.edited = true;
2072 this._poly.fire('edit');
2073 this._poly._map.fire(L.Draw.Event.EDITVERTEX, {layers: this._markerGroup, poly: this._poly});
2074 },
2075
2076 _onMarkerDrag: function (e) {
2077 var marker = e.target;
2078 var poly = this._poly;
2079
2080 L.extend(marker._origLatLng, marker._latlng);
2081
2082 if (marker._middleLeft) {
2083 marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
2084 }
2085 if (marker._middleRight) {
2086 marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
2087 }
2088
2089 if (poly.options.poly) {
2090 var tooltip = poly._map._editTooltip; // Access the tooltip
2091
2092 // If we don't allow intersections and the polygon intersects
2093 if (!poly.options.poly.allowIntersection && poly.intersects()) {
2094
2095 var originalColor = poly.options.color;
2096 poly.setStyle({color: this.options.drawError.color});
2097
2098 // Manually trigger 'dragend' behavior on marker we are about to remove
2099 // WORKAROUND: introduced in 1.0.0-rc2, may be related to #4484
2100 if (L.version.indexOf('0.7') !== 0) {
2101 marker.dragging._draggable._onUp(e);
2102 }
2103 this._onMarkerClick(e); // Remove violating marker
2104 // FIXME: Reset the marker to it's original position (instead of remove)
2105
2106 if (tooltip) {
2107 tooltip.updateContent({
2108 text: L.drawLocal.draw.handlers.polyline.error
2109 });
2110 }
2111
2112 // Reset everything back to normal after a second
2113 setTimeout(function () {
2114 poly.setStyle({color: originalColor});
2115 if (tooltip) {
2116 tooltip.updateContent({
2117 text: L.drawLocal.edit.handlers.edit.tooltip.text,
2118 subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
2119 });
2120 }
2121 }, 1000);
2122 }
2123 }
2124 //refresh the bounds when draging
2125 this._poly._bounds._southWest = L.latLng(Infinity, Infinity);
2126 this._poly._bounds._northEast = L.latLng(-Infinity, -Infinity);
2127 var latlngs = this._poly.getLatLngs();
2128 this._poly._convertLatLngs(latlngs, true);
2129 this._poly.redraw();
2130 this._poly.fire('editdrag');
2131 },
2132
2133 _onMarkerClick: function (e) {
2134
2135 var minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3,
2136 marker = e.target;
2137
2138 // If removing this point would create an invalid polyline/polygon don't remove
2139 if (this._defaultShape().length < minPoints) {
2140 return;
2141 }
2142
2143 // remove the marker
2144 this._removeMarker(marker);
2145
2146 // update prev/next links of adjacent markers
2147 this._updatePrevNext(marker._prev, marker._next);
2148
2149 // remove ghost markers near the removed marker
2150 if (marker._middleLeft) {
2151 this._markerGroup.removeLayer(marker._middleLeft);
2152 }
2153 if (marker._middleRight) {
2154 this._markerGroup.removeLayer(marker._middleRight);
2155 }
2156
2157 // create a ghost marker in place of the removed one
2158 if (marker._prev && marker._next) {
2159 this._createMiddleMarker(marker._prev, marker._next);
2160
2161 } else if (!marker._prev) {
2162 marker._next._middleLeft = null;
2163
2164 } else if (!marker._next) {
2165 marker._prev._middleRight = null;
2166 }
2167
2168 this._fireEdit();
2169 },
2170
2171 _onContextMenu: function (e) {
2172 var marker = e.target;
2173 var poly = this._poly;
2174 this._poly._map.fire(L.Draw.Event.MARKERCONTEXT, {marker: marker, layers: this._markerGroup, poly: this._poly});
2175 L.DomEvent.stopPropagation;
2176 },
2177
2178 _onTouchMove: function (e) {
2179
2180 var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),
2181 latlng = this._map.layerPointToLatLng(layerPoint),
2182 marker = e.target;
2183
2184 L.extend(marker._origLatLng, latlng);
2185
2186 if (marker._middleLeft) {
2187 marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
2188 }
2189 if (marker._middleRight) {
2190 marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
2191 }
2192
2193 this._poly.redraw();
2194 this.updateMarkers();
2195 },
2196
2197 _updateIndexes: function (index, delta) {
2198 this._markerGroup.eachLayer(function (marker) {
2199 if (marker._index > index) {
2200 marker._index += delta;
2201 }
2202 });
2203 },
2204
2205 _createMiddleMarker: function (marker1, marker2) {
2206 var latlng = this._getMiddleLatLng(marker1, marker2),
2207 marker = this._createMarker(latlng),
2208 onClick,
2209 onDragStart,
2210 onDragEnd;
2211
2212 marker.setOpacity(0.6);
2213
2214 marker1._middleRight = marker2._middleLeft = marker;
2215
2216 onDragStart = function () {
2217 marker.off('touchmove', onDragStart, this);
2218 var i = marker2._index;
2219
2220 marker._index = i;
2221
2222 marker
2223 .off('click', onClick, this)
2224 .on('click', this._onMarkerClick, this);
2225
2226 latlng.lat = marker.getLatLng().lat;
2227 latlng.lng = marker.getLatLng().lng;
2228 this._spliceLatLngs(i, 0, latlng);
2229 this._markers.splice(i, 0, marker);
2230
2231 marker.setOpacity(1);
2232
2233 this._updateIndexes(i, 1);
2234 marker2._index++;
2235 this._updatePrevNext(marker1, marker);
2236 this._updatePrevNext(marker, marker2);
2237
2238 this._poly.fire('editstart');
2239 };
2240
2241 onDragEnd = function () {
2242 marker.off('dragstart', onDragStart, this);
2243 marker.off('dragend', onDragEnd, this);
2244 marker.off('touchmove', onDragStart, this);
2245
2246 this._createMiddleMarker(marker1, marker);
2247 this._createMiddleMarker(marker, marker2);
2248 };
2249
2250 onClick = function () {
2251 onDragStart.call(this);
2252 onDragEnd.call(this);
2253 this._fireEdit();
2254 };
2255
2256 marker
2257 .on('click', onClick, this)
2258 .on('dragstart', onDragStart, this)
2259 .on('dragend', onDragEnd, this)
2260 .on('touchmove', onDragStart, this);
2261
2262 this._markerGroup.addLayer(marker);
2263 },
2264
2265 _updatePrevNext: function (marker1, marker2) {
2266 if (marker1) {
2267 marker1._next = marker2;
2268 }
2269 if (marker2) {
2270 marker2._prev = marker1;
2271 }
2272 },
2273
2274 _getMiddleLatLng: function (marker1, marker2) {
2275 var map = this._poly._map,
2276 p1 = map.project(marker1.getLatLng()),
2277 p2 = map.project(marker2.getLatLng());
2278
2279 return map.unproject(p1._add(p2)._divideBy(2));
2280 }
2281 });
2282
2283 L.Polyline.addInitHook(function () {
2284
2285 // Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit
2286 if (this.editing) {
2287 return;
2288 }
2289
2290 if (L.Edit.Poly) {
2291
2292 this.editing = new L.Edit.Poly(this);
2293
2294 if (this.options.editable) {
2295 this.editing.enable();
2296 }
2297 }
2298
2299 this.on('add', function () {
2300 if (this.editing && this.editing.enabled()) {
2301 this.editing.addHooks();
2302 }
2303 });
2304
2305 this.on('remove', function () {
2306 if (this.editing && this.editing.enabled()) {
2307 this.editing.removeHooks();
2308 }
2309 });
2310 });
2311
2312
2313
2314 L.Edit = L.Edit || {};
2315 /**
2316 * @class L.Edit.SimpleShape
2317 * @aka Edit.SimpleShape
2318 */
2319 L.Edit.SimpleShape = L.Handler.extend({
2320 options: {
2321 moveIcon: new L.DivIcon({
2322 iconSize: new L.Point(8, 8),
2323 className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move'
2324 }),
2325 resizeIcon: new L.DivIcon({
2326 iconSize: new L.Point(8, 8),
2327 className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize'
2328 }),
2329 touchMoveIcon: new L.DivIcon({
2330 iconSize: new L.Point(20, 20),
2331 className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon'
2332 }),
2333 touchResizeIcon: new L.DivIcon({
2334 iconSize: new L.Point(20, 20),
2335 className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon'
2336 }),
2337 },
2338
2339 // @method intialize(): void
2340 initialize: function (shape, options) {
2341 // if touch, switch to touch icon
2342 if (L.Browser.touch) {
2343 this.options.moveIcon = this.options.touchMoveIcon;
2344 this.options.resizeIcon = this.options.touchResizeIcon;
2345 }
2346
2347 this._shape = shape;
2348 L.Util.setOptions(this, options);
2349 },
2350
2351 // @method addHooks(): void
2352 // Add listener hooks to this handler
2353 addHooks: function () {
2354 var shape = this._shape;
2355 if (this._shape._map) {
2356 this._map = this._shape._map;
2357 shape.setStyle(shape.options.editing);
2358
2359 if (shape._map) {
2360 this._map = shape._map;
2361 if (!this._markerGroup) {
2362 this._initMarkers();
2363 }
2364 this._map.addLayer(this._markerGroup);
2365 }
2366 }
2367 },
2368
2369 // @method removeHooks(): void
2370 // Remove listener hooks from this handler
2371 removeHooks: function () {
2372 var shape = this._shape;
2373
2374 shape.setStyle(shape.options.original);
2375
2376 if (shape._map) {
2377 this._unbindMarker(this._moveMarker);
2378
2379 for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
2380 this._unbindMarker(this._resizeMarkers[i]);
2381 }
2382 this._resizeMarkers = null;
2383
2384 this._map.removeLayer(this._markerGroup);
2385 delete this._markerGroup;
2386 }
2387
2388 this._map = null;
2389 },
2390
2391 // @method updateMarkers(): void
2392 // Remove the edit markers from this layer
2393 updateMarkers: function () {
2394 this._markerGroup.clearLayers();
2395 this._initMarkers();
2396 },
2397
2398 _initMarkers: function () {
2399 if (!this._markerGroup) {
2400 this._markerGroup = new L.LayerGroup();
2401 }
2402
2403 // Create center marker
2404 this._createMoveMarker();
2405
2406 // Create edge marker
2407 this._createResizeMarker();
2408 },
2409
2410 _createMoveMarker: function () {
2411 // Children override
2412 },
2413
2414 _createResizeMarker: function () {
2415 // Children override
2416 },
2417
2418 _createMarker: function (latlng, icon) {
2419 // Extending L.Marker in TouchEvents.js to include touch.
2420 var marker = new L.Marker.Touch(latlng, {
2421 draggable: true,
2422 icon: icon,
2423 zIndexOffset: 10
2424 });
2425
2426 this._bindMarker(marker);
2427
2428 this._markerGroup.addLayer(marker);
2429
2430 return marker;
2431 },
2432
2433 _bindMarker: function (marker) {
2434 marker
2435 .on('dragstart', this._onMarkerDragStart, this)
2436 .on('drag', this._onMarkerDrag, this)
2437 .on('dragend', this._onMarkerDragEnd, this)
2438 .on('touchstart', this._onTouchStart, this)
2439 .on('touchmove', this._onTouchMove, this)
2440 .on('MSPointerMove', this._onTouchMove, this)
2441 .on('touchend', this._onTouchEnd, this)
2442 .on('MSPointerUp', this._onTouchEnd, this);
2443 },
2444
2445 _unbindMarker: function (marker) {
2446 marker
2447 .off('dragstart', this._onMarkerDragStart, this)
2448 .off('drag', this._onMarkerDrag, this)
2449 .off('dragend', this._onMarkerDragEnd, this)
2450 .off('touchstart', this._onTouchStart, this)
2451 .off('touchmove', this._onTouchMove, this)
2452 .off('MSPointerMove', this._onTouchMove, this)
2453 .off('touchend', this._onTouchEnd, this)
2454 .off('MSPointerUp', this._onTouchEnd, this);
2455 },
2456
2457 _onMarkerDragStart: function (e) {
2458 var marker = e.target;
2459 marker.setOpacity(0);
2460
2461 this._shape.fire('editstart');
2462 },
2463
2464 _fireEdit: function () {
2465 this._shape.edited = true;
2466 this._shape.fire('edit');
2467 },
2468
2469 _onMarkerDrag: function (e) {
2470 var marker = e.target,
2471 latlng = marker.getLatLng();
2472
2473 if (marker === this._moveMarker) {
2474 this._move(latlng);
2475 } else {
2476 this._resize(latlng);
2477 }
2478
2479 this._shape.redraw();
2480 this._shape.fire('editdrag');
2481 },
2482
2483 _onMarkerDragEnd: function (e) {
2484 var marker = e.target;
2485 marker.setOpacity(1);
2486
2487 this._fireEdit();
2488 },
2489
2490 _onTouchStart: function (e) {
2491 L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);
2492
2493 if (typeof(this._getCorners) === 'function') {
2494 // Save a reference to the opposite point
2495 var corners = this._getCorners(),
2496 marker = e.target,
2497 currentCornerIndex = marker._cornerIndex;
2498
2499 marker.setOpacity(0);
2500
2501 // Copyed from Edit.Rectangle.js line 23 _onMarkerDragStart()
2502 // Latlng is null otherwise.
2503 this._oppositeCorner = corners[(currentCornerIndex + 2) % 4];
2504 this._toggleCornerMarkers(0, currentCornerIndex);
2505 }
2506
2507 this._shape.fire('editstart');
2508 },
2509
2510 _onTouchMove: function (e) {
2511 var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),
2512 latlng = this._map.layerPointToLatLng(layerPoint),
2513 marker = e.target;
2514
2515 if (marker === this._moveMarker) {
2516 this._move(latlng);
2517 } else {
2518 this._resize(latlng);
2519 }
2520
2521 this._shape.redraw();
2522
2523 // prevent touchcancel in IOS
2524 // e.preventDefault();
2525 return false;
2526 },
2527
2528 _onTouchEnd: function (e) {
2529 var marker = e.target;
2530 marker.setOpacity(1);
2531 this.updateMarkers();
2532 this._fireEdit();
2533 },
2534
2535 _move: function () {
2536 // Children override
2537 },
2538
2539 _resize: function () {
2540 // Children override
2541 }
2542 });
2543
2544
2545
2546 L.Edit = L.Edit || {};
2547 /**
2548 * @class L.Edit.Rectangle
2549 * @aka Edit.Rectangle
2550 * @inherits L.Edit.SimpleShape
2551 */
2552 L.Edit.Rectangle = L.Edit.SimpleShape.extend({
2553 _createMoveMarker: function () {
2554 var bounds = this._shape.getBounds(),
2555 center = bounds.getCenter();
2556
2557 this._moveMarker = this._createMarker(center, this.options.moveIcon);
2558 },
2559
2560 _createResizeMarker: function () {
2561 var corners = this._getCorners();
2562
2563 this._resizeMarkers = [];
2564
2565 for (var i = 0, l = corners.length; i < l; i++) {
2566 this._resizeMarkers.push(this._createMarker(corners[i], this.options.resizeIcon));
2567 // Monkey in the corner index as we will need to know this for dragging
2568 this._resizeMarkers[i]._cornerIndex = i;
2569 }
2570 },
2571
2572 _onMarkerDragStart: function (e) {
2573 L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);
2574
2575 // Save a reference to the opposite point
2576 var corners = this._getCorners(),
2577 marker = e.target,
2578 currentCornerIndex = marker._cornerIndex;
2579
2580 this._oppositeCorner = corners[(currentCornerIndex + 2) % 4];
2581
2582 this._toggleCornerMarkers(0, currentCornerIndex);
2583 },
2584
2585 _onMarkerDragEnd: function (e) {
2586 var marker = e.target,
2587 bounds, center;
2588
2589 // Reset move marker position to the center
2590 if (marker === this._moveMarker) {
2591 bounds = this._shape.getBounds();
2592 center = bounds.getCenter();
2593
2594 marker.setLatLng(center);
2595 }
2596
2597 this._toggleCornerMarkers(1);
2598
2599 this._repositionCornerMarkers();
2600
2601 L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, e);
2602 },
2603
2604 _move: function (newCenter) {
2605 var latlngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(),
2606 bounds = this._shape.getBounds(),
2607 center = bounds.getCenter(),
2608 offset, newLatLngs = [];
2609
2610 // Offset the latlngs to the new center
2611 for (var i = 0, l = latlngs.length; i < l; i++) {
2612 offset = [latlngs[i].lat - center.lat, latlngs[i].lng - center.lng];
2613 newLatLngs.push([newCenter.lat + offset[0], newCenter.lng + offset[1]]);
2614 }
2615
2616 this._shape.setLatLngs(newLatLngs);
2617
2618 // Reposition the resize markers
2619 this._repositionCornerMarkers();
2620
2621 this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape});
2622 },
2623
2624 _resize: function (latlng) {
2625 var bounds;
2626
2627 // Update the shape based on the current position of this corner and the opposite point
2628 this._shape.setBounds(L.latLngBounds(latlng, this._oppositeCorner));
2629
2630 // Reposition the move marker
2631 bounds = this._shape.getBounds();
2632 this._moveMarker.setLatLng(bounds.getCenter());
2633
2634 this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape});
2635 },
2636
2637 _getCorners: function () {
2638 var bounds = this._shape.getBounds(),
2639 nw = bounds.getNorthWest(),
2640 ne = bounds.getNorthEast(),
2641 se = bounds.getSouthEast(),
2642 sw = bounds.getSouthWest();
2643
2644 return [nw, ne, se, sw];
2645 },
2646
2647 _toggleCornerMarkers: function (opacity) {
2648 for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
2649 this._resizeMarkers[i].setOpacity(opacity);
2650 }
2651 },
2652
2653 _repositionCornerMarkers: function () {
2654 var corners = this._getCorners();
2655
2656 for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
2657 this._resizeMarkers[i].setLatLng(corners[i]);
2658 }
2659 }
2660 });
2661
2662 L.Rectangle.addInitHook(function () {
2663 if (L.Edit.Rectangle) {
2664 this.editing = new L.Edit.Rectangle(this);
2665
2666 if (this.options.editable) {
2667 this.editing.enable();
2668 }
2669 }
2670 });
2671
2672
2673
2674 L.Edit = L.Edit || {};
2675 /**
2676 * @class L.Edit.CircleMarker
2677 * @aka Edit.Circle
2678 * @inherits L.Edit.SimpleShape
2679 */
2680 L.Edit.CircleMarker = L.Edit.SimpleShape.extend({
2681 _createMoveMarker: function () {
2682 var center = this._shape.getLatLng();
2683
2684 this._moveMarker = this._createMarker(center, this.options.moveIcon);
2685 },
2686
2687 _createResizeMarker: function () {
2688 // To avoid an undefined check in L.Edit.SimpleShape.removeHooks
2689 this._resizeMarkers = [];
2690 },
2691
2692 _move: function (latlng) {
2693 if (this._resizeMarkers.length) {
2694 var resizemarkerPoint = this._getResizeMarkerPoint(latlng);
2695 // Move the resize marker
2696 this._resizeMarkers[0].setLatLng(resizemarkerPoint);
2697 }
2698
2699 // Move the circle
2700 this._shape.setLatLng(latlng);
2701
2702 this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape});
2703 },
2704 });
2705
2706 L.CircleMarker.addInitHook(function () {
2707 if (L.Edit.CircleMarker) {
2708 this.editing = new L.Edit.CircleMarker(this);
2709
2710 if (this.options.editable) {
2711 this.editing.enable();
2712 }
2713 }
2714
2715 this.on('add', function () {
2716 if (this.editing && this.editing.enabled()) {
2717 this.editing.addHooks();
2718 }
2719 });
2720
2721 this.on('remove', function () {
2722 if (this.editing && this.editing.enabled()) {
2723 this.editing.removeHooks();
2724 }
2725 });
2726 });
2727
2728
2729
2730 L.Edit = L.Edit || {};
2731 /**
2732 * @class L.Edit.Circle
2733 * @aka Edit.Circle
2734 * @inherits L.Edit.CircleMarker
2735 */
2736 L.Edit.Circle = L.Edit.CircleMarker.extend({
2737
2738 _createResizeMarker: function () {
2739 var center = this._shape.getLatLng(),
2740 resizemarkerPoint = this._getResizeMarkerPoint(center);
2741
2742 this._resizeMarkers = [];
2743 this._resizeMarkers.push(this._createMarker(resizemarkerPoint, this.options.resizeIcon));
2744 },
2745
2746 _getResizeMarkerPoint: function (latlng) {
2747 // From L.shape.getBounds()
2748 var delta = this._shape._radius * Math.cos(Math.PI / 4),
2749 point = this._map.project(latlng);
2750 return this._map.unproject([point.x + delta, point.y - delta]);
2751 },
2752
2753 _resize: function (latlng) {
2754 var moveLatLng = this._moveMarker.getLatLng();
2755
2756 // Calculate the radius based on the version
2757 if (L.GeometryUtil.isVersion07x()) {
2758 radius = moveLatLng.distanceTo(latlng);
2759 } else {
2760 radius = this._map.distance(moveLatLng, latlng);
2761 }
2762 this._shape.setRadius(radius);
2763
2764 this._map._editTooltip.updateContent({
2765 text: L.drawLocal.edit.handlers.edit.tooltip.subtext + '<br />' + L.drawLocal.edit.handlers.edit.tooltip.text,
2766 subtext: L.drawLocal.draw.handlers.circle.radius + ': ' +
2767 L.GeometryUtil.readableDistance(radius, true, this.options.feet, this.options.nautic)
2768 });
2769
2770 this._shape.setRadius(radius);
2771
2772 this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape});
2773 }
2774 });
2775
2776 L.Circle.addInitHook(function () {
2777 if (L.Edit.Circle) {
2778 this.editing = new L.Edit.Circle(this);
2779
2780 if (this.options.editable) {
2781 this.editing.enable();
2782 }
2783 }
2784
2785 this.on('add', function () {
2786 if (this.editing && this.editing.enabled()) {
2787 this.editing.addHooks();
2788 }
2789 });
2790
2791 this.on('remove', function () {
2792 if (this.editing && this.editing.enabled()) {
2793 this.editing.removeHooks();
2794 }
2795 });
2796 });
2797
2798
2799
2800 L.Map.mergeOptions({
2801 touchExtend: true
2802 });
2803
2804 /**
2805 * @class L.Map.TouchExtend
2806 * @aka TouchExtend
2807 */
2808 L.Map.TouchExtend = L.Handler.extend({
2809
2810 // @method initialize(): void
2811 // Sets TouchExtend private accessor variables
2812 initialize: function (map) {
2813 this._map = map;
2814 this._container = map._container;
2815 this._pane = map._panes.overlayPane;
2816 },
2817
2818 // @method addHooks(): void
2819 // Adds dom listener events to the map container
2820 addHooks: function () {
2821 L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
2822 L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
2823 L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this);
2824 if (this._detectIE()) {
2825 L.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this);
2826 L.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this);
2827 L.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this);
2828 L.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this);
2829
2830 } else {
2831 L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this);
2832 L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this);
2833 }
2834 },
2835
2836 // @method removeHooks(): void
2837 // Removes dom listener events from the map container
2838 removeHooks: function () {
2839 L.DomEvent.off(this._container, 'touchstart', this._onTouchStart);
2840 L.DomEvent.off(this._container, 'touchend', this._onTouchEnd);
2841 L.DomEvent.off(this._container, 'touchmove', this._onTouchMove);
2842 if (this._detectIE()) {
2843 L.DomEvent.off(this._container, 'MSPointerDowm', this._onTouchStart);
2844 L.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd);
2845 L.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove);
2846 L.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel);
2847 } else {
2848 L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel);
2849 L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave);
2850 }
2851 },
2852
2853 _touchEvent: function (e, type) {
2854 // #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events
2855 // _filterClick is what leaflet uses as a workaround.
2856 // This is a problem with more things than just android. Another problem is touchEnd has no touches in
2857 // its touch list.
2858 var touchEvent = {};
2859 if (typeof e.touches !== 'undefined') {
2860 if (!e.touches.length) {
2861 return;
2862 }
2863 touchEvent = e.touches[0];
2864 } else if (e.pointerType === 'touch') {
2865 touchEvent = e;
2866 if (!this._filterClick(e)) {
2867 return;
2868 }
2869 } else {
2870 return;
2871 }
2872
2873 var containerPoint = this._map.mouseEventToContainerPoint(touchEvent),
2874 layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
2875 latlng = this._map.layerPointToLatLng(layerPoint);
2876
2877 this._map.fire(type, {
2878 latlng: latlng,
2879 layerPoint: layerPoint,
2880 containerPoint: containerPoint,
2881 pageX: touchEvent.pageX,
2882 pageY: touchEvent.pageY,
2883 originalEvent: e
2884 });
2885 },
2886
2887 /** Borrowed from Leaflet and modified for bool ops **/
2888 _filterClick: function (e) {
2889 var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
2890 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
2891
2892 // are they closer together than 500ms yet more than 100ms?
2893 // Android typically triggers them ~300ms apart while multiple listeners
2894 // on the same event should be triggered far faster;
2895 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2896 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2897 L.DomEvent.stop(e);
2898 return false;
2899 }
2900 L.DomEvent._lastClick = timeStamp;
2901 return true;
2902 },
2903
2904 _onTouchStart: function (e) {
2905 if (!this._map._loaded) {
2906 return;
2907 }
2908
2909 var type = 'touchstart';
2910 this._touchEvent(e, type);
2911
2912 },
2913
2914 _onTouchEnd: function (e) {
2915 if (!this._map._loaded) {
2916 return;
2917 }
2918
2919 var type = 'touchend';
2920 this._touchEvent(e, type);
2921 },
2922
2923 _onTouchCancel: function (e) {
2924 if (!this._map._loaded) {
2925 return;
2926 }
2927
2928 var type = 'touchcancel';
2929 if (this._detectIE()) {
2930 type = 'pointercancel';
2931 }
2932 this._touchEvent(e, type);
2933 },
2934
2935 _onTouchLeave: function (e) {
2936 if (!this._map._loaded) {
2937 return;
2938 }
2939
2940 var type = 'touchleave';
2941 this._touchEvent(e, type);
2942 },
2943
2944 _onTouchMove: function (e) {
2945 if (!this._map._loaded) {
2946 return;
2947 }
2948
2949 var type = 'touchmove';
2950 this._touchEvent(e, type);
2951 },
2952
2953 _detectIE: function () {
2954 var ua = window.navigator.userAgent;
2955
2956 var msie = ua.indexOf('MSIE ');
2957 if (msie > 0) {
2958 // IE 10 or older => return version number
2959 return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
2960 }
2961
2962 var trident = ua.indexOf('Trident/');
2963 if (trident > 0) {
2964 // IE 11 => return version number
2965 var rv = ua.indexOf('rv:');
2966 return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
2967 }
2968
2969 var edge = ua.indexOf('Edge/');
2970 if (edge > 0) {
2971 // IE 12 => return version number
2972 return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
2973 }
2974
2975 // other browser
2976 return false;
2977 }
2978 });
2979
2980 L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);
2981
2982
2983 /**
2984 * @class L.Marker.Touch
2985 * @aka Marker.Touch
2986 *
2987 * This isn't full Touch support. This is just to get markers to also support dom touch events after creation
2988 * #TODO: find a better way of getting markers to support touch.
2989 */
2990 L.Marker.Touch = L.Marker.extend({
2991
2992 _initInteraction: function () {
2993 if (!this.addInteractiveTarget) {
2994 // 0.7.x support
2995 return this._initInteractionLegacy();
2996 }
2997 // TODO this may need be updated to re-add touch events for 1.0+
2998 return L.Marker.prototype._initInteraction.apply(this);
2999 },
3000
3001 // This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js
3002 // with the addition of the touch events
3003 _initInteractionLegacy: function () {
3004
3005 if (!this.options.clickable) {
3006 return;
3007 }
3008
3009 // TODO refactor into something shared with Map/Path/etc. to DRY it up
3010
3011 var icon = this._icon,
3012 events = ['dblclick',
3013 'mousedown',
3014 'mouseover',
3015 'mouseout',
3016 'contextmenu',
3017 'touchstart',
3018 'touchend',
3019 'touchmove'];
3020 if (this._detectIE) {
3021 events.concat(['MSPointerDown',
3022 'MSPointerUp',
3023 'MSPointerMove',
3024 'MSPointerCancel']);
3025 } else {
3026 events.concat(['touchcancel']);
3027 }
3028
3029 L.DomUtil.addClass(icon, 'leaflet-clickable');
3030 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
3031 L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
3032
3033 for (var i = 0; i < events.length; i++) {
3034 L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
3035 }
3036
3037 if (L.Handler.MarkerDrag) {
3038 this.dragging = new L.Handler.MarkerDrag(this);
3039
3040 if (this.options.draggable) {
3041 this.dragging.enable();
3042 }
3043 }
3044 },
3045
3046 _detectIE: function () {
3047 var ua = window.navigator.userAgent;
3048
3049 var msie = ua.indexOf('MSIE ');
3050 if (msie > 0) {
3051 // IE 10 or older => return version number
3052 return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
3053 }
3054
3055 var trident = ua.indexOf('Trident/');
3056 if (trident > 0) {
3057 // IE 11 => return version number
3058 var rv = ua.indexOf('rv:');
3059 return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
3060 }
3061
3062 var edge = ua.indexOf('Edge/');
3063 if (edge > 0) {
3064 // IE 12 => return version number
3065 return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
3066 }
3067
3068 // other browser
3069 return false;
3070 }
3071 });
3072
3073
3074
3075 /**
3076 * @class L.LatLngUtil
3077 * @aka LatLngUtil
3078 */
3079 L.LatLngUtil = {
3080 // Clones a LatLngs[], returns [][]
3081
3082 // @method cloneLatLngs(LatLngs[]): L.LatLngs[]
3083 // Clone the latLng point or points or nested points and return an array with those points
3084 cloneLatLngs: function (latlngs) {
3085 var clone = [];
3086 for (var i = 0, l = latlngs.length; i < l; i++) {
3087 // Check for nested array (Polyline/Polygon)
3088 if (Array.isArray(latlngs[i])) {
3089 clone.push(L.LatLngUtil.cloneLatLngs(latlngs[i]));
3090 } else {
3091 clone.push(this.cloneLatLng(latlngs[i]));
3092 }
3093 }
3094 return clone;
3095 },
3096
3097 // @method cloneLatLng(LatLng): L.LatLng
3098 // Clone the latLng and return a new LatLng object.
3099 cloneLatLng: function (latlng) {
3100 return L.latLng(latlng.lat, latlng.lng);
3101 }
3102 };
3103
3104
3105
3106 (function () {
3107
3108 var defaultPrecision = {
3109 km: 2,
3110 ha: 2,
3111 m: 0,
3112 mi: 2,
3113 ac: 2,
3114 yd: 0,
3115 ft: 0,
3116 nm: 2
3117 };
3118
3119
3120 /**
3121 * @class L.GeometryUtil
3122 * @aka GeometryUtil
3123 */
3124 L.GeometryUtil = L.extend(L.GeometryUtil || {}, {
3125 // Ported from the OpenLayers implementation. See https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Geometry/LinearRing.js#L270
3126
3127 // @method geodesicArea(): number
3128 geodesicArea: function (latLngs) {
3129 var pointsCount = latLngs.length,
3130 area = 0.0,
3131 d2r = Math.PI / 180,
3132 p1, p2;
3133
3134 if (pointsCount > 2) {
3135 for (var i = 0; i < pointsCount; i++) {
3136 p1 = latLngs[i];
3137 p2 = latLngs[(i + 1) % pointsCount];
3138 area += ((p2.lng - p1.lng) * d2r) *
3139 (2 + Math.sin(p1.lat * d2r) + Math.sin(p2.lat * d2r));
3140 }
3141 area = area * 6378137.0 * 6378137.0 / 2.0;
3142 }
3143
3144 return Math.abs(area);
3145 },
3146
3147 // @method formattedNumber(n, precision): string
3148 // Returns n in specified number format (if defined) and precision
3149 formattedNumber: function (n, precision) {
3150 var formatted = parseFloat(n).toFixed(precision),
3151 format = L.drawLocal.format && L.drawLocal.format.numeric,
3152 delimiters = format && format.delimiters,
3153 thousands = delimiters && delimiters.thousands,
3154 decimal = delimiters && delimiters.decimal;
3155
3156 if (thousands || decimal) {
3157 var splitValue = formatted.split('.');
3158 formatted = thousands ? splitValue[0].replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousands) : splitValue[0];
3159 decimal = decimal || '.';
3160 if (splitValue.length > 1) {
3161 formatted = formatted + decimal + splitValue[1];
3162 }
3163 }
3164
3165 return formatted;
3166 },
3167
3168 // @method readableArea(area, isMetric, precision): string
3169 // Returns a readable area string in yards or metric.
3170 // The value will be rounded as defined by the precision option object.
3171 readableArea: function (area, isMetric, precision) {
3172 var areaStr,
3173 units,
3174 precision = L.Util.extend({}, defaultPrecision, precision);
3175
3176 if (isMetric) {
3177 units = ['ha', 'm'];
3178 type = typeof isMetric;
3179 if (type === 'string') {
3180 units = [isMetric];
3181 } else if (type !== 'boolean') {
3182 units = isMetric;
3183 }
3184
3185 if (area >= 1000000 && units.indexOf('km') !== -1) {
3186 areaStr = L.GeometryUtil.formattedNumber(area * 0.000001, precision['km']) + ' km²';
3187 } else if (area >= 10000 && units.indexOf('ha') !== -1) {
3188 areaStr = L.GeometryUtil.formattedNumber(area * 0.0001, precision['ha']) + ' ha';
3189 } else {
3190 areaStr = L.GeometryUtil.formattedNumber(area, precision['m']) + ' m²';
3191 }
3192 } else {
3193 area /= 0.836127; // Square yards in 1 meter
3194
3195 if (area >= 3097600) { //3097600 square yards in 1 square mile
3196 areaStr = L.GeometryUtil.formattedNumber(area / 3097600, precision['mi']) + ' mi²';
3197 } else if (area >= 4840) { //4840 square yards in 1 acre
3198 areaStr = L.GeometryUtil.formattedNumber(area / 4840, precision['ac']) + ' acres';
3199 } else {
3200 areaStr = L.GeometryUtil.formattedNumber(area, precision['yd']) + ' yd²';
3201 }
3202 }
3203
3204 return areaStr;
3205 },
3206
3207 // @method readableDistance(distance, units): string
3208 // Converts a metric distance to one of [ feet, nauticalMile, metric or yards ] string
3209 //
3210 // @alternative
3211 // @method readableDistance(distance, isMetric, useFeet, isNauticalMile, precision): string
3212 // Converts metric distance to distance string.
3213 // The value will be rounded as defined by the precision option object.
3214 readableDistance: function (distance, isMetric, isFeet, isNauticalMile, precision) {
3215 var distanceStr,
3216 units,
3217 precision = L.Util.extend({}, defaultPrecision, precision);
3218
3219 if (isMetric) {
3220 units = typeof isMetric == 'string' ? isMetric : 'metric';
3221 } else if (isFeet) {
3222 units = 'feet';
3223 } else if (isNauticalMile) {
3224 units = 'nauticalMile';
3225 } else {
3226 units = 'yards';
3227 }
3228
3229 switch (units) {
3230 case 'metric':
3231 // show metres when distance is < 1km, then show km
3232 if (distance > 1000) {
3233 distanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['km']) + ' km';
3234 } else {
3235 distanceStr = L.GeometryUtil.formattedNumber(distance, precision['m']) + ' m';
3236 }
3237 break;
3238 case 'feet':
3239 distance *= 1.09361 * 3;
3240 distanceStr = L.GeometryUtil.formattedNumber(distance, precision['ft']) + ' ft';
3241
3242 break;
3243 case 'nauticalMile':
3244 distance *= 0.53996;
3245 distanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['nm']) + ' nm';
3246 break;
3247 case 'yards':
3248 default:
3249 distance *= 1.09361;
3250
3251 if (distance > 1760) {
3252 distanceStr = L.GeometryUtil.formattedNumber(distance / 1760, precision['mi']) + ' miles';
3253 } else {
3254 distanceStr = L.GeometryUtil.formattedNumber(distance, precision['yd']) + ' yd';
3255 }
3256 break;
3257 }
3258 return distanceStr;
3259 },
3260
3261 // @method isVersion07x(): boolean
3262 // Returns true if the Leaflet version is 0.7.x, false otherwise.
3263 isVersion07x: function () {
3264 var version = L.version.split('.');
3265 //If Version is == 0.7.*
3266 return parseInt(version[0], 10) === 0 && parseInt(version[1], 10) === 7;
3267 },
3268 });
3269
3270 })();
3271
3272
3273
3274 /**
3275 * @class L.LineUtil
3276 * @aka Util
3277 * @aka L.Utils
3278 */
3279 L.Util.extend(L.LineUtil, {
3280
3281 // @method segmentsIntersect(): boolean
3282 // Checks to see if two line segments intersect. Does not handle degenerate cases.
3283 // http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf
3284 segmentsIntersect: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2, /*Point*/ p3) {
3285 return this._checkCounterclockwise(p, p2, p3) !==
3286 this._checkCounterclockwise(p1, p2, p3) &&
3287 this._checkCounterclockwise(p, p1, p2) !==
3288 this._checkCounterclockwise(p, p1, p3);
3289 },
3290
3291 // check to see if points are in counterclockwise order
3292 _checkCounterclockwise: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
3293 return (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x);
3294 }
3295 });
3296
3297
3298
3299 /**
3300 * @class L.Polyline
3301 * @aka Polyline
3302 */
3303 L.Polyline.include({
3304
3305 // @method intersects(): boolean
3306 // Check to see if this polyline has any linesegments that intersect.
3307 // NOTE: does not support detecting intersection for degenerate cases.
3308 intersects: function () {
3309 var points = this._getProjectedPoints(),
3310 len = points ? points.length : 0,
3311 i, p, p1;
3312
3313 if (this._tooFewPointsForIntersection()) {
3314 return false;
3315 }
3316
3317 for (i = len - 1; i >= 3; i--) {
3318 p = points[i - 1];
3319 p1 = points[i];
3320
3321
3322 if (this._lineSegmentsIntersectsRange(p, p1, i - 2)) {
3323 return true;
3324 }
3325 }
3326
3327 return false;
3328 },
3329
3330 // @method newLatLngIntersects(): boolean
3331 // Check for intersection if new latlng was added to this polyline.
3332 // NOTE: does not support detecting intersection for degenerate cases.
3333 newLatLngIntersects: function (latlng, skipFirst) {
3334 // Cannot check a polyline for intersecting lats/lngs when not added to the map
3335 if (!this._map) {
3336 return false;
3337 }
3338
3339 return this.newPointIntersects(this._map.latLngToLayerPoint(latlng), skipFirst);
3340 },
3341
3342 // @method newPointIntersects(): boolean
3343 // Check for intersection if new point was added to this polyline.
3344 // newPoint must be a layer point.
3345 // NOTE: does not support detecting intersection for degenerate cases.
3346 newPointIntersects: function (newPoint, skipFirst) {
3347 var points = this._getProjectedPoints(),
3348 len = points ? points.length : 0,
3349 lastPoint = points ? points[len - 1] : null,
3350 // The previous previous line segment. Previous line segment doesn't need testing.
3351 maxIndex = len - 2;
3352
3353 if (this._tooFewPointsForIntersection(1)) {
3354 return false;
3355 }
3356
3357 return this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex, skipFirst ? 1 : 0);
3358 },
3359
3360 // Polylines with 2 sides can only intersect in cases where points are collinear (we don't support detecting these).
3361 // Cannot have intersection when < 3 line segments (< 4 points)
3362 _tooFewPointsForIntersection: function (extraPoints) {
3363 var points = this._getProjectedPoints(),
3364 len = points ? points.length : 0;
3365 // Increment length by extraPoints if present
3366 len += extraPoints || 0;
3367
3368 return !points || len <= 3;
3369 },
3370
3371 // Checks a line segment intersections with any line segments before its predecessor.
3372 // Don't need to check the predecessor as will never intersect.
3373 _lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) {
3374 var points = this._getProjectedPoints(),
3375 p2, p3;
3376
3377 minIndex = minIndex || 0;
3378
3379 // Check all previous line segments (beside the immediately previous) for intersections
3380 for (var j = maxIndex; j > minIndex; j--) {
3381 p2 = points[j - 1];
3382 p3 = points[j];
3383
3384 if (L.LineUtil.segmentsIntersect(p, p1, p2, p3)) {
3385 return true;
3386 }
3387 }
3388
3389 return false;
3390 },
3391
3392 _getProjectedPoints: function () {
3393 if (!this._defaultShape) {
3394 return this._originalPoints;
3395 }
3396 var points = [],
3397 _shape = this._defaultShape();
3398
3399 for (var i = 0; i < _shape.length; i++) {
3400 points.push(this._map.latLngToLayerPoint(_shape[i]));
3401 }
3402 return points;
3403 }
3404 });
3405
3406
3407
3408 /**
3409 * @class L.Polygon
3410 * @aka Polygon
3411 */
3412 L.Polygon.include({
3413
3414 // @method intersects(): boolean
3415 // Checks a polygon for any intersecting line segments. Ignores holes.
3416 intersects: function () {
3417 var polylineIntersects,
3418 points = this._getProjectedPoints(),
3419 len, firstPoint, lastPoint, maxIndex;
3420
3421 if (this._tooFewPointsForIntersection()) {
3422 return false;
3423 }
3424
3425 polylineIntersects = L.Polyline.prototype.intersects.call(this);
3426
3427 // If already found an intersection don't need to check for any more.
3428 if (polylineIntersects) {
3429 return true;
3430 }
3431
3432 len = points.length;
3433 firstPoint = points[0];
3434 lastPoint = points[len - 1];
3435 maxIndex = len - 2;
3436
3437 // Check the line segment between last and first point. Don't need to check the first line segment (minIndex = 1)
3438 return this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex, 1);
3439 }
3440 });
3441
3442
3443
3444 /**
3445 * @class L.Control.Draw
3446 * @aka L.Draw
3447 */
3448 L.Control.Draw = L.Control.extend({
3449
3450 // Options
3451 options: {
3452 position: 'topleft',
3453 draw: {},
3454 edit: false
3455 },
3456
3457 // @method initialize(): void
3458 // Initializes draw control, toolbars from the options
3459 initialize: function (options) {
3460 if (L.version < '0.7') {
3461 throw new Error('Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/');
3462 }
3463
3464 L.Control.prototype.initialize.call(this, options);
3465
3466 var toolbar;
3467
3468 this._toolbars = {};
3469
3470 // Initialize toolbars
3471 if (L.DrawToolbar && this.options.draw) {
3472 toolbar = new L.DrawToolbar(this.options.draw);
3473
3474 this._toolbars[L.DrawToolbar.TYPE] = toolbar;
3475
3476 // Listen for when toolbar is enabled
3477 this._toolbars[L.DrawToolbar.TYPE].on('enable', this._toolbarEnabled, this);
3478 }
3479
3480 if (L.EditToolbar && this.options.edit) {
3481 toolbar = new L.EditToolbar(this.options.edit);
3482
3483 this._toolbars[L.EditToolbar.TYPE] = toolbar;
3484
3485 // Listen for when toolbar is enabled
3486 this._toolbars[L.EditToolbar.TYPE].on('enable', this._toolbarEnabled, this);
3487 }
3488 L.toolbar = this; //set global var for editing the toolbar
3489 },
3490
3491 // @method onAdd(): container
3492 // Adds the toolbar container to the map
3493 onAdd: function (map) {
3494 var container = L.DomUtil.create('div', 'leaflet-draw'),
3495 addedTopClass = false,
3496 topClassName = 'leaflet-draw-toolbar-top',
3497 toolbarContainer;
3498
3499 for (var toolbarId in this._toolbars) {
3500 if (this._toolbars.hasOwnProperty(toolbarId)) {
3501 toolbarContainer = this._toolbars[toolbarId].addToolbar(map);
3502
3503 if (toolbarContainer) {
3504 // Add class to the first toolbar to remove the margin
3505 if (!addedTopClass) {
3506 if (!L.DomUtil.hasClass(toolbarContainer, topClassName)) {
3507 L.DomUtil.addClass(toolbarContainer.childNodes[0], topClassName);
3508 }
3509 addedTopClass = true;
3510 }
3511
3512 container.appendChild(toolbarContainer);
3513 }
3514 }
3515 }
3516
3517 return container;
3518 },
3519
3520 // @method onRemove(): void
3521 // Removes the toolbars from the map toolbar container
3522 onRemove: function () {
3523 for (var toolbarId in this._toolbars) {
3524 if (this._toolbars.hasOwnProperty(toolbarId)) {
3525 this._toolbars[toolbarId].removeToolbar();
3526 }
3527 }
3528 },
3529
3530 // @method setDrawingOptions(options): void
3531 // Sets options to all toolbar instances
3532 setDrawingOptions: function (options) {
3533 for (var toolbarId in this._toolbars) {
3534 if (this._toolbars[toolbarId] instanceof L.DrawToolbar) {
3535 this._toolbars[toolbarId].setOptions(options);
3536 }
3537 }
3538 },
3539
3540 _toolbarEnabled: function (e) {
3541 var enabledToolbar = e.target;
3542
3543 for (var toolbarId in this._toolbars) {
3544 if (this._toolbars[toolbarId] !== enabledToolbar) {
3545 this._toolbars[toolbarId].disable();
3546 }
3547 }
3548 }
3549 });
3550
3551 L.Map.mergeOptions({
3552 drawControlTooltips: true,
3553 drawControl: false
3554 });
3555
3556 L.Map.addInitHook(function () {
3557 if (this.options.drawControl) {
3558 this.drawControl = new L.Control.Draw();
3559 this.addControl(this.drawControl);
3560 }
3561 });
3562
3563
3564
3565 /**
3566 * @class L.Draw.Toolbar
3567 * @aka Toolbar
3568 *
3569 * The toolbar class of the API — it is used to create the ui
3570 * This will be depreciated
3571 *
3572 * @example
3573 *
3574 * ```js
3575 * var toolbar = L.Toolbar();
3576 * toolbar.addToolbar(map);
3577 * ```
3578 *
3579 * ### Disabling a toolbar
3580 *
3581 * If you do not want a particular toolbar in your app you can turn it off by setting the toolbar to false.
3582 *
3583 * ```js
3584 * var drawControl = new L.Control.Draw({
3585 * draw: false,
3586 * edit: {
3587 * featureGroup: editableLayers
3588 * }
3589 * });
3590 * ```
3591 *
3592 * ### Disabling a toolbar item
3593 *
3594 * If you want to turn off a particular toolbar item, set it to false. The following disables drawing polygons and
3595 * markers. It also turns off the ability to edit layers.
3596 *
3597 * ```js
3598 * var drawControl = new L.Control.Draw({
3599 * draw: {
3600 * polygon: false,
3601 * marker: false
3602 * },
3603 * edit: {
3604 * featureGroup: editableLayers,
3605 * edit: false
3606 * }
3607 * });
3608 * ```
3609 */
3610 L.Toolbar = L.Class.extend({
3611 // @section Methods for modifying the toolbar
3612
3613 // @method initialize(options): void
3614 // Toolbar constructor
3615 initialize: function (options) {
3616 L.setOptions(this, options);
3617
3618 this._modes = {};
3619 this._actionButtons = [];
3620 this._activeMode = null;
3621
3622 var version = L.version.split('.');
3623 //If Version is >= 1.2.0
3624 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
3625 L.Toolbar.include(L.Evented.prototype);
3626 } else {
3627 L.Toolbar.include(L.Mixin.Events);
3628 }
3629 },
3630
3631 // @method enabled(): boolean
3632 // Gets a true/false of whether the toolbar is enabled
3633 enabled: function () {
3634 return this._activeMode !== null;
3635 },
3636
3637 // @method disable(): void
3638 // Disables the toolbar
3639 disable: function () {
3640 if (!this.enabled()) {
3641 return;
3642 }
3643
3644 this._activeMode.handler.disable();
3645 },
3646
3647 // @method addToolbar(map): L.DomUtil
3648 // Adds the toolbar to the map and returns the toolbar dom element
3649 addToolbar: function (map) {
3650 var container = L.DomUtil.create('div', 'leaflet-draw-section'),
3651 buttonIndex = 0,
3652 buttonClassPrefix = this._toolbarClass || '',
3653 modeHandlers = this.getModeHandlers(map),
3654 i;
3655
3656 this._toolbarContainer = L.DomUtil.create('div', 'leaflet-draw-toolbar leaflet-bar');
3657 this._map = map;
3658
3659 for (i = 0; i < modeHandlers.length; i++) {
3660 if (modeHandlers[i].enabled) {
3661 this._initModeHandler(
3662 modeHandlers[i].handler,
3663 this._toolbarContainer,
3664 buttonIndex++,
3665 buttonClassPrefix,
3666 modeHandlers[i].title
3667 );
3668 }
3669 }
3670
3671 // if no buttons were added, do not add the toolbar
3672 if (!buttonIndex) {
3673 return;
3674 }
3675
3676 // Save button index of the last button, -1 as we would have ++ after the last button
3677 this._lastButtonIndex = --buttonIndex;
3678
3679 // Create empty actions part of the toolbar
3680 this._actionsContainer = L.DomUtil.create('ul', 'leaflet-draw-actions');
3681
3682 // Add draw and cancel containers to the control container
3683 container.appendChild(this._toolbarContainer);
3684 container.appendChild(this._actionsContainer);
3685
3686 return container;
3687 },
3688
3689 // @method removeToolbar(): void
3690 // Removes the toolbar and drops the handler event listeners
3691 removeToolbar: function () {
3692 // Dispose each handler
3693 for (var handlerId in this._modes) {
3694 if (this._modes.hasOwnProperty(handlerId)) {
3695 // Unbind handler button
3696 this._disposeButton(
3697 this._modes[handlerId].button,
3698 this._modes[handlerId].handler.enable,
3699 this._modes[handlerId].handler
3700 );
3701
3702 // Make sure is disabled
3703 this._modes[handlerId].handler.disable();
3704
3705 // Unbind handler
3706 this._modes[handlerId].handler
3707 .off('enabled', this._handlerActivated, this)
3708 .off('disabled', this._handlerDeactivated, this);
3709 }
3710 }
3711 this._modes = {};
3712
3713 // Dispose the actions toolbar
3714 for (var i = 0, l = this._actionButtons.length; i < l; i++) {
3715 this._disposeButton(
3716 this._actionButtons[i].button,
3717 this._actionButtons[i].callback,
3718 this
3719 );
3720 }
3721 this._actionButtons = [];
3722 this._actionsContainer = null;
3723 },
3724
3725 _initModeHandler: function (handler, container, buttonIndex, classNamePredix, buttonTitle) {
3726 var type = handler.type;
3727
3728 this._modes[type] = {};
3729
3730 this._modes[type].handler = handler;
3731
3732 this._modes[type].button = this._createButton({
3733 type: type,
3734 title: buttonTitle,
3735 className: classNamePredix + '-' + type,
3736 container: container,
3737 callback: this._modes[type].handler.enable,
3738 context: this._modes[type].handler
3739 });
3740
3741 this._modes[type].buttonIndex = buttonIndex;
3742
3743 this._modes[type].handler
3744 .on('enabled', this._handlerActivated, this)
3745 .on('disabled', this._handlerDeactivated, this);
3746 },
3747
3748 /* Detect iOS based on browser User Agent, based on:
3749 * http://stackoverflow.com/a/9039885 */
3750 _detectIOS: function () {
3751 var iOS = (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream);
3752 return iOS;
3753 },
3754
3755 _createButton: function (options) {
3756
3757 var link = L.DomUtil.create('a', options.className || '', options.container);
3758 // Screen reader tag
3759 var sr = L.DomUtil.create('span', 'sr-only', options.container);
3760
3761 link.href = '#';
3762 link.appendChild(sr);
3763
3764 if (options.title) {
3765 link.title = options.title;
3766 sr.innerHTML = options.title;
3767 }
3768
3769 if (options.text) {
3770 link.innerHTML = options.text;
3771 sr.innerHTML = options.text;
3772 }
3773
3774 /* iOS does not use click events */
3775 var buttonEvent = this._detectIOS() ? 'touchstart' : 'click';
3776
3777 L.DomEvent
3778 .on(link, 'click', L.DomEvent.stopPropagation)
3779 .on(link, 'mousedown', L.DomEvent.stopPropagation)
3780 .on(link, 'dblclick', L.DomEvent.stopPropagation)
3781 .on(link, 'touchstart', L.DomEvent.stopPropagation)
3782 .on(link, 'click', L.DomEvent.preventDefault)
3783 .on(link, buttonEvent, options.callback, options.context);
3784
3785 return link;
3786 },
3787
3788 _disposeButton: function (button, callback) {
3789 /* iOS does not use click events */
3790 var buttonEvent = this._detectIOS() ? 'touchstart' : 'click';
3791
3792 L.DomEvent
3793 .off(button, 'click', L.DomEvent.stopPropagation)
3794 .off(button, 'mousedown', L.DomEvent.stopPropagation)
3795 .off(button, 'dblclick', L.DomEvent.stopPropagation)
3796 .off(button, 'touchstart', L.DomEvent.stopPropagation)
3797 .off(button, 'click', L.DomEvent.preventDefault)
3798 .off(button, buttonEvent, callback);
3799 },
3800
3801 _handlerActivated: function (e) {
3802 // Disable active mode (if present)
3803 this.disable();
3804
3805 // Cache new active feature
3806 this._activeMode = this._modes[e.handler];
3807
3808 L.DomUtil.addClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');
3809
3810 this._showActionsToolbar();
3811
3812 this.fire('enable');
3813 },
3814
3815 _handlerDeactivated: function () {
3816 this._hideActionsToolbar();
3817
3818 L.DomUtil.removeClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');
3819
3820 this._activeMode = null;
3821
3822 this.fire('disable');
3823 },
3824
3825 _createActions: function (handler) {
3826 var container = this._actionsContainer,
3827 buttons = this.getActions(handler),
3828 l = buttons.length,
3829 li, di, dl, button;
3830
3831 // Dispose the actions toolbar (todo: dispose only not used buttons)
3832 for (di = 0, dl = this._actionButtons.length; di < dl; di++) {
3833 this._disposeButton(this._actionButtons[di].button, this._actionButtons[di].callback);
3834 }
3835 this._actionButtons = [];
3836
3837 // Remove all old buttons
3838 while (container.firstChild) {
3839 container.removeChild(container.firstChild);
3840 }
3841
3842 for (var i = 0; i < l; i++) {
3843 if ('enabled' in buttons[i] && !buttons[i].enabled) {
3844 continue;
3845 }
3846
3847 li = L.DomUtil.create('li', '', container);
3848
3849 button = this._createButton({
3850 title: buttons[i].title,
3851 text: buttons[i].text,
3852 container: li,
3853 callback: buttons[i].callback,
3854 context: buttons[i].context
3855 });
3856
3857 this._actionButtons.push({
3858 button: button,
3859 callback: buttons[i].callback
3860 });
3861 }
3862 },
3863
3864 _showActionsToolbar: function () {
3865 var buttonIndex = this._activeMode.buttonIndex,
3866 lastButtonIndex = this._lastButtonIndex,
3867 toolbarPosition = this._activeMode.button.offsetTop - 1;
3868
3869 // Recreate action buttons on every click
3870 this._createActions(this._activeMode.handler);
3871
3872 // Correctly position the cancel button
3873 this._actionsContainer.style.top = toolbarPosition + 'px';
3874
3875 if (buttonIndex === 0) {
3876 L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');
3877 L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-top');
3878 }
3879
3880 if (buttonIndex === lastButtonIndex) {
3881 L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');
3882 L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-bottom');
3883 }
3884
3885 this._actionsContainer.style.display = 'block';
3886 this._map.fire(L.Draw.Event.TOOLBAROPENED);
3887 },
3888
3889 _hideActionsToolbar: function () {
3890 this._actionsContainer.style.display = 'none';
3891
3892 L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');
3893 L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');
3894 L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-top');
3895 L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-bottom');
3896 this._map.fire(L.Draw.Event.TOOLBARCLOSED);
3897 }
3898 });
3899
3900
3901
3902 L.Draw = L.Draw || {};
3903 /**
3904 * @class L.Draw.Tooltip
3905 * @aka Tooltip
3906 *
3907 * The tooltip class — it is used to display the tooltip while drawing
3908 * This will be depreciated
3909 *
3910 * @example
3911 *
3912 * ```js
3913 * var tooltip = L.Draw.Tooltip();
3914 * ```
3915 *
3916 */
3917 L.Draw.Tooltip = L.Class.extend({
3918
3919 // @section Methods for modifying draw state
3920
3921 // @method initialize(map): void
3922 // Tooltip constructor
3923 initialize: function (map) {
3924 this._map = map;
3925 this._popupPane = map._panes.popupPane;
3926 this._visible = false;
3927
3928 this._container = map.options.drawControlTooltips ?
3929 L.DomUtil.create('div', 'leaflet-draw-tooltip', this._popupPane) : null;
3930 this._singleLineLabel = false;
3931
3932 this._map.on('mouseout', this._onMouseOut, this);
3933 },
3934
3935 // @method dispose(): void
3936 // Remove Tooltip DOM and unbind events
3937 dispose: function () {
3938 this._map.off('mouseout', this._onMouseOut, this);
3939
3940 if (this._container) {
3941 this._popupPane.removeChild(this._container);
3942 this._container = null;
3943 }
3944 },
3945
3946 // @method updateContent(labelText): this
3947 // Changes the tooltip text to string in function call
3948 updateContent: function (labelText) {
3949 if (!this._container) {
3950 return this;
3951 }
3952 labelText.subtext = labelText.subtext || '';
3953
3954 // update the vertical position (only if changed)
3955 if (labelText.subtext.length === 0 && !this._singleLineLabel) {
3956 L.DomUtil.addClass(this._container, 'leaflet-draw-tooltip-single');
3957 this._singleLineLabel = true;
3958 }
3959 else if (labelText.subtext.length > 0 && this._singleLineLabel) {
3960 L.DomUtil.removeClass(this._container, 'leaflet-draw-tooltip-single');
3961 this._singleLineLabel = false;
3962 }
3963
3964 this._container.innerHTML =
3965 (labelText.subtext.length > 0 ?
3966 '<span class="leaflet-draw-tooltip-subtext">' + labelText.subtext + '</span>' + '<br />' : '') +
3967 '<span>' + labelText.text + '</span>';
3968
3969 if (!labelText.text && !labelText.subtext) {
3970 this._visible = false;
3971 this._container.style.visibility = 'hidden';
3972 } else {
3973 this._visible = true;
3974 this._container.style.visibility = 'inherit';
3975 }
3976
3977 return this;
3978 },
3979
3980 // @method updatePosition(latlng): this
3981 // Changes the location of the tooltip
3982 updatePosition: function (latlng) {
3983 var pos = this._map.latLngToLayerPoint(latlng),
3984 tooltipContainer = this._container;
3985
3986 if (this._container) {
3987 if (this._visible) {
3988 tooltipContainer.style.visibility = 'inherit';
3989 }
3990 L.DomUtil.setPosition(tooltipContainer, pos);
3991 }
3992
3993 return this;
3994 },
3995
3996 // @method showAsError(): this
3997 // Applies error class to tooltip
3998 showAsError: function () {
3999 if (this._container) {
4000 L.DomUtil.addClass(this._container, 'leaflet-error-draw-tooltip');
4001 }
4002 return this;
4003 },
4004
4005 // @method removeError(): this
4006 // Removes the error class from the tooltip
4007 removeError: function () {
4008 if (this._container) {
4009 L.DomUtil.removeClass(this._container, 'leaflet-error-draw-tooltip');
4010 }
4011 return this;
4012 },
4013
4014 _onMouseOut: function () {
4015 if (this._container) {
4016 this._container.style.visibility = 'hidden';
4017 }
4018 }
4019 });
4020
4021
4022
4023 /**
4024 * @class L.DrawToolbar
4025 * @aka Toolbar
4026 */
4027 L.DrawToolbar = L.Toolbar.extend({
4028
4029 statics: {
4030 TYPE: 'draw'
4031 },
4032
4033 options: {
4034 polyline: {},
4035 polygon: {},
4036 rectangle: {},
4037 circle: {},
4038 marker: {},
4039 circlemarker: {}
4040 },
4041
4042 // @method initialize(): void
4043 initialize: function (options) {
4044 // Ensure that the options are merged correctly since L.extend is only shallow
4045 for (var type in this.options) {
4046 if (this.options.hasOwnProperty(type)) {
4047 if (options[type]) {
4048 options[type] = L.extend({}, this.options[type], options[type]);
4049 }
4050 }
4051 }
4052
4053 this._toolbarClass = 'leaflet-draw-draw';
4054 L.Toolbar.prototype.initialize.call(this, options);
4055 },
4056
4057 // @method getModeHandlers(): object
4058 // Get mode handlers information
4059 getModeHandlers: function (map) {
4060 return [
4061 {
4062 enabled: this.options.polyline,
4063 handler: new L.Draw.Polyline(map, this.options.polyline),
4064 title: L.drawLocal.draw.toolbar.buttons.polyline
4065 },
4066 {
4067 enabled: this.options.polygon,
4068 handler: new L.Draw.Polygon(map, this.options.polygon),
4069 title: L.drawLocal.draw.toolbar.buttons.polygon
4070 },
4071 {
4072 enabled: this.options.rectangle,
4073 handler: new L.Draw.Rectangle(map, this.options.rectangle),
4074 title: L.drawLocal.draw.toolbar.buttons.rectangle
4075 },
4076 {
4077 enabled: this.options.circle,
4078 handler: new L.Draw.Circle(map, this.options.circle),
4079 title: L.drawLocal.draw.toolbar.buttons.circle
4080 },
4081 {
4082 enabled: this.options.marker,
4083 handler: new L.Draw.Marker(map, this.options.marker),
4084 title: L.drawLocal.draw.toolbar.buttons.marker
4085 },
4086 {
4087 enabled: this.options.circlemarker,
4088 handler: new L.Draw.CircleMarker(map, this.options.circlemarker),
4089 title: L.drawLocal.draw.toolbar.buttons.circlemarker
4090 }
4091 ];
4092 },
4093
4094 // @method getActions(): object
4095 // Get action information
4096 getActions: function (handler) {
4097 return [
4098 {
4099 enabled: handler.completeShape,
4100 title: L.drawLocal.draw.toolbar.finish.title,
4101 text: L.drawLocal.draw.toolbar.finish.text,
4102 callback: handler.completeShape,
4103 context: handler
4104 },
4105 {
4106 enabled: handler.deleteLastVertex,
4107 title: L.drawLocal.draw.toolbar.undo.title,
4108 text: L.drawLocal.draw.toolbar.undo.text,
4109 callback: handler.deleteLastVertex,
4110 context: handler
4111 },
4112 {
4113 title: L.drawLocal.draw.toolbar.actions.title,
4114 text: L.drawLocal.draw.toolbar.actions.text,
4115 callback: this.disable,
4116 context: this
4117 }
4118 ];
4119 },
4120
4121 // @method setOptions(): void
4122 // Sets the options to the toolbar
4123 setOptions: function (options) {
4124 L.setOptions(this, options);
4125
4126 for (var type in this._modes) {
4127 if (this._modes.hasOwnProperty(type) && options.hasOwnProperty(type)) {
4128 this._modes[type].handler.setOptions(options[type]);
4129 }
4130 }
4131 }
4132 });
4133
4134
4135
4136 /*L.Map.mergeOptions({
4137 editControl: true
4138 });*/
4139 /**
4140 * @class L.EditToolbar
4141 * @aka EditToolbar
4142 */
4143 L.EditToolbar = L.Toolbar.extend({
4144 statics: {
4145 TYPE: 'edit'
4146 },
4147
4148 options: {
4149 edit: {
4150 selectedPathOptions: {
4151 dashArray: '10, 10',
4152
4153 fill: true,
4154 fillColor: '#fe57a1',
4155 fillOpacity: 0.1,
4156
4157 // Whether to user the existing layers color
4158 maintainColor: false
4159 }
4160 },
4161 remove: {},
4162 poly: null,
4163 featureGroup: null /* REQUIRED! TODO: perhaps if not set then all layers on the map are selectable? */
4164 },
4165
4166 // @method intialize(): void
4167 initialize: function (options) {
4168 // Need to set this manually since null is an acceptable value here
4169 if (options.edit) {
4170 if (typeof options.edit.selectedPathOptions === 'undefined') {
4171 options.edit.selectedPathOptions = this.options.edit.selectedPathOptions;
4172 }
4173 options.edit.selectedPathOptions = L.extend({}, this.options.edit.selectedPathOptions, options.edit.selectedPathOptions);
4174 }
4175
4176 if (options.remove) {
4177 options.remove = L.extend({}, this.options.remove, options.remove);
4178 }
4179
4180 if (options.poly) {
4181 options.poly = L.extend({}, this.options.poly, options.poly);
4182 }
4183
4184 this._toolbarClass = 'leaflet-draw-edit';
4185 L.Toolbar.prototype.initialize.call(this, options);
4186
4187 this._selectedFeatureCount = 0;
4188 },
4189
4190 // @method getModeHandlers(): object
4191 // Get mode handlers information
4192 getModeHandlers: function (map) {
4193 var featureGroup = this.options.featureGroup;
4194 return [
4195 {
4196 enabled: this.options.edit,
4197 handler: new L.EditToolbar.Edit(map, {
4198 featureGroup: featureGroup,
4199 selectedPathOptions: this.options.edit.selectedPathOptions,
4200 poly: this.options.poly
4201 }),
4202 title: L.drawLocal.edit.toolbar.buttons.edit
4203 },
4204 {
4205 enabled: this.options.remove,
4206 handler: new L.EditToolbar.Delete(map, {
4207 featureGroup: featureGroup
4208 }),
4209 title: L.drawLocal.edit.toolbar.buttons.remove
4210 }
4211 ];
4212 },
4213
4214 // @method getActions(): object
4215 // Get actions information
4216 getActions: function (handler) {
4217 var actions = [
4218 {
4219 title: L.drawLocal.edit.toolbar.actions.save.title,
4220 text: L.drawLocal.edit.toolbar.actions.save.text,
4221 callback: this._save,
4222 context: this
4223 },
4224 {
4225 title: L.drawLocal.edit.toolbar.actions.cancel.title,
4226 text: L.drawLocal.edit.toolbar.actions.cancel.text,
4227 callback: this.disable,
4228 context: this
4229 }
4230 ];
4231
4232 if (handler.removeAllLayers) {
4233 actions.push({
4234 title: L.drawLocal.edit.toolbar.actions.clearAll.title,
4235 text: L.drawLocal.edit.toolbar.actions.clearAll.text,
4236 callback: this._clearAllLayers,
4237 context: this
4238 });
4239 }
4240
4241 return actions;
4242 },
4243
4244 // @method addToolbar(map): L.DomUtil
4245 // Adds the toolbar to the map
4246 addToolbar: function (map) {
4247 var container = L.Toolbar.prototype.addToolbar.call(this, map);
4248
4249 this._checkDisabled();
4250
4251 this.options.featureGroup.on('layeradd layerremove', this._checkDisabled, this);
4252
4253 return container;
4254 },
4255
4256 // @method removeToolbar(): void
4257 // Removes the toolbar from the map
4258 removeToolbar: function () {
4259 this.options.featureGroup.off('layeradd layerremove', this._checkDisabled, this);
4260
4261 L.Toolbar.prototype.removeToolbar.call(this);
4262 },
4263
4264 // @method disable(): void
4265 // Disables the toolbar
4266 disable: function () {
4267 if (!this.enabled()) {
4268 return;
4269 }
4270
4271 this._activeMode.handler.revertLayers();
4272
4273 L.Toolbar.prototype.disable.call(this);
4274 },
4275
4276 _save: function () {
4277 this._activeMode.handler.save();
4278 if (this._activeMode) {
4279 this._activeMode.handler.disable();
4280 }
4281 },
4282
4283 _clearAllLayers: function () {
4284 this._activeMode.handler.removeAllLayers();
4285 if (this._activeMode) {
4286 this._activeMode.handler.disable();
4287 }
4288 },
4289
4290 _checkDisabled: function () {
4291 var featureGroup = this.options.featureGroup,
4292 hasLayers = featureGroup.getLayers().length !== 0,
4293 button;
4294
4295 if (this.options.edit) {
4296 button = this._modes[L.EditToolbar.Edit.TYPE].button;
4297
4298 if (hasLayers) {
4299 L.DomUtil.removeClass(button, 'leaflet-disabled');
4300 } else {
4301 L.DomUtil.addClass(button, 'leaflet-disabled');
4302 }
4303
4304 button.setAttribute(
4305 'title',
4306 hasLayers ?
4307 L.drawLocal.edit.toolbar.buttons.edit
4308 : L.drawLocal.edit.toolbar.buttons.editDisabled
4309 );
4310 }
4311
4312 if (this.options.remove) {
4313 button = this._modes[L.EditToolbar.Delete.TYPE].button;
4314
4315 if (hasLayers) {
4316 L.DomUtil.removeClass(button, 'leaflet-disabled');
4317 } else {
4318 L.DomUtil.addClass(button, 'leaflet-disabled');
4319 }
4320
4321 button.setAttribute(
4322 'title',
4323 hasLayers ?
4324 L.drawLocal.edit.toolbar.buttons.remove
4325 : L.drawLocal.edit.toolbar.buttons.removeDisabled
4326 );
4327 }
4328 }
4329 });
4330
4331
4332
4333 /**
4334 * @class L.EditToolbar.Edit
4335 * @aka EditToolbar.Edit
4336 */
4337 L.EditToolbar.Edit = L.Handler.extend({
4338 statics: {
4339 TYPE: 'edit'
4340 },
4341
4342 // @method intialize(): void
4343 initialize: function (map, options) {
4344 L.Handler.prototype.initialize.call(this, map);
4345
4346 L.setOptions(this, options);
4347
4348 // Store the selectable layer group for ease of access
4349 this._featureGroup = options.featureGroup;
4350
4351 if (!(this._featureGroup instanceof L.FeatureGroup)) {
4352 throw new Error('options.featureGroup must be a L.FeatureGroup');
4353 }
4354
4355 this._uneditedLayerProps = {};
4356
4357 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
4358 this.type = L.EditToolbar.Edit.TYPE;
4359
4360 var version = L.version.split('.');
4361 //If Version is >= 1.2.0
4362 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
4363 L.EditToolbar.Edit.include(L.Evented.prototype);
4364 } else {
4365 L.EditToolbar.Edit.include(L.Mixin.Events);
4366 }
4367 },
4368
4369 // @method enable(): void
4370 // Enable the edit toolbar
4371 enable: function () {
4372 if (this._enabled || !this._hasAvailableLayers()) {
4373 return;
4374 }
4375 this.fire('enabled', {handler: this.type});
4376 //this disable other handlers
4377
4378 this._map.fire(L.Draw.Event.EDITSTART, {handler: this.type});
4379 //allow drawLayer to be updated before beginning edition.
4380
4381 L.Handler.prototype.enable.call(this);
4382 this._featureGroup
4383 .on('layeradd', this._enableLayerEdit, this)
4384 .on('layerremove', this._disableLayerEdit, this);
4385 },
4386
4387 // @method disable(): void
4388 // Disable the edit toolbar
4389 disable: function () {
4390 if (!this._enabled) {
4391 return;
4392 }
4393 this._featureGroup
4394 .off('layeradd', this._enableLayerEdit, this)
4395 .off('layerremove', this._disableLayerEdit, this);
4396 L.Handler.prototype.disable.call(this);
4397 this._map.fire(L.Draw.Event.EDITSTOP, {handler: this.type});
4398 this.fire('disabled', {handler: this.type});
4399 },
4400
4401 // @method addHooks(): void
4402 // Add listener hooks for this handler
4403 addHooks: function () {
4404 var map = this._map;
4405
4406 if (map) {
4407 map.getContainer().focus();
4408
4409 this._featureGroup.eachLayer(this._enableLayerEdit, this);
4410
4411 this._tooltip = new L.Draw.Tooltip(this._map);
4412 this._tooltip.updateContent({
4413 text: L.drawLocal.edit.handlers.edit.tooltip.text,
4414 subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
4415 });
4416
4417 // Quickly access the tooltip to update for intersection checking
4418 map._editTooltip = this._tooltip;
4419
4420 this._updateTooltip();
4421
4422 this._map
4423 .on('mousemove', this._onMouseMove, this)
4424 .on('touchmove', this._onMouseMove, this)
4425 .on('MSPointerMove', this._onMouseMove, this)
4426 .on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
4427 }
4428 },
4429
4430 // @method removeHooks(): void
4431 // Remove listener hooks for this handler
4432 removeHooks: function () {
4433 if (this._map) {
4434 // Clean up selected layers.
4435 this._featureGroup.eachLayer(this._disableLayerEdit, this);
4436
4437 // Clear the backups of the original layers
4438 this._uneditedLayerProps = {};
4439
4440 this._tooltip.dispose();
4441 this._tooltip = null;
4442
4443 this._map
4444 .off('mousemove', this._onMouseMove, this)
4445 .off('touchmove', this._onMouseMove, this)
4446 .off('MSPointerMove', this._onMouseMove, this)
4447 .off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
4448 }
4449 },
4450
4451 // @method revertLayers(): void
4452 // Revert each layer's geometry changes
4453 revertLayers: function () {
4454 this._featureGroup.eachLayer(function (layer) {
4455 this._revertLayer(layer);
4456 }, this);
4457 },
4458
4459 // @method save(): void
4460 // Save the layer geometries
4461 save: function () {
4462 var editedLayers = new L.LayerGroup();
4463 this._featureGroup.eachLayer(function (layer) {
4464 if (layer.edited) {
4465 editedLayers.addLayer(layer);
4466 layer.edited = false;
4467 }
4468 });
4469 this._map.fire(L.Draw.Event.EDITED, {layers: editedLayers});
4470 },
4471
4472 _backupLayer: function (layer) {
4473 var id = L.Util.stamp(layer);
4474
4475 if (!this._uneditedLayerProps[id]) {
4476 // Polyline, Polygon or Rectangle
4477 if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
4478 this._uneditedLayerProps[id] = {
4479 latlngs: L.LatLngUtil.cloneLatLngs(layer.getLatLngs())
4480 };
4481 } else if (layer instanceof L.Circle) {
4482 this._uneditedLayerProps[id] = {
4483 latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()),
4484 radius: layer.getRadius()
4485 };
4486 } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker
4487 this._uneditedLayerProps[id] = {
4488 latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng())
4489 };
4490 }
4491 }
4492 },
4493
4494 _getTooltipText: function () {
4495 return ({
4496 text: L.drawLocal.edit.handlers.edit.tooltip.text,
4497 subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
4498 });
4499 },
4500
4501 _updateTooltip: function () {
4502 this._tooltip.updateContent(this._getTooltipText());
4503 },
4504
4505 _revertLayer: function (layer) {
4506 var id = L.Util.stamp(layer);
4507 layer.edited = false;
4508 if (this._uneditedLayerProps.hasOwnProperty(id)) {
4509 // Polyline, Polygon or Rectangle
4510 if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
4511 layer.setLatLngs(this._uneditedLayerProps[id].latlngs);
4512 } else if (layer instanceof L.Circle) {
4513 layer.setLatLng(this._uneditedLayerProps[id].latlng);
4514 layer.setRadius(this._uneditedLayerProps[id].radius);
4515 } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker or CircleMarker
4516 layer.setLatLng(this._uneditedLayerProps[id].latlng);
4517 }
4518
4519 layer.fire('revert-edited', {layer: layer});
4520 }
4521 },
4522
4523 _enableLayerEdit: function (e) {
4524 var layer = e.layer || e.target || e,
4525 pathOptions, poly;
4526
4527 // Back up this layer (if haven't before)
4528 this._backupLayer(layer);
4529
4530 if (this.options.poly) {
4531 poly = L.Util.extend({}, this.options.poly);
4532 layer.options.poly = poly;
4533 }
4534
4535 // Set different style for editing mode
4536 if (this.options.selectedPathOptions) {
4537 pathOptions = L.Util.extend({}, this.options.selectedPathOptions);
4538
4539 // Use the existing color of the layer
4540 if (pathOptions.maintainColor) {
4541 pathOptions.color = layer.options.color;
4542 pathOptions.fillColor = layer.options.fillColor;
4543 }
4544
4545 layer.options.original = L.extend({}, layer.options);
4546 layer.options.editing = pathOptions;
4547
4548 }
4549
4550 if (layer instanceof L.Marker) {
4551 if (layer.editing) {
4552 layer.editing.enable();
4553 }
4554 layer.dragging.enable();
4555 layer
4556 .on('dragend', this._onMarkerDragEnd)
4557 // #TODO: remove when leaflet finally fixes their draggable so it's touch friendly again.
4558 .on('touchmove', this._onTouchMove, this)
4559 .on('MSPointerMove', this._onTouchMove, this)
4560 .on('touchend', this._onMarkerDragEnd, this)
4561 .on('MSPointerUp', this._onMarkerDragEnd, this);
4562 } else {
4563 layer.editing.enable();
4564 }
4565 },
4566
4567 _disableLayerEdit: function (e) {
4568 var layer = e.layer || e.target || e;
4569
4570 layer.edited = false;
4571 if (layer.editing) {
4572 layer.editing.disable();
4573 }
4574
4575 delete layer.options.editing;
4576 delete layer.options.original;
4577 // Reset layer styles to that of before select
4578 if (this._selectedPathOptions) {
4579 if (layer instanceof L.Marker) {
4580 this._toggleMarkerHighlight(layer);
4581 } else {
4582 // reset the layer style to what is was before being selected
4583 layer.setStyle(layer.options.previousOptions);
4584 // remove the cached options for the layer object
4585 delete layer.options.previousOptions;
4586 }
4587 }
4588
4589 if (layer instanceof L.Marker) {
4590 layer.dragging.disable();
4591 layer
4592 .off('dragend', this._onMarkerDragEnd, this)
4593 .off('touchmove', this._onTouchMove, this)
4594 .off('MSPointerMove', this._onTouchMove, this)
4595 .off('touchend', this._onMarkerDragEnd, this)
4596 .off('MSPointerUp', this._onMarkerDragEnd, this);
4597 } else {
4598 layer.editing.disable();
4599 }
4600 },
4601
4602 _onMouseMove: function (e) {
4603 this._tooltip.updatePosition(e.latlng);
4604 },
4605
4606 _onMarkerDragEnd: function (e) {
4607 var layer = e.target;
4608 layer.edited = true;
4609 this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer});
4610 },
4611
4612 _onTouchMove: function (e) {
4613 var touchEvent = e.originalEvent.changedTouches[0],
4614 layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
4615 latlng = this._map.layerPointToLatLng(layerPoint);
4616 e.target.setLatLng(latlng);
4617 },
4618
4619 _hasAvailableLayers: function () {
4620 return this._featureGroup.getLayers().length !== 0;
4621 }
4622 });
4623
4624
4625
4626 /**
4627 * @class L.EditToolbar.Delete
4628 * @aka EditToolbar.Delete
4629 */
4630 L.EditToolbar.Delete = L.Handler.extend({
4631 statics: {
4632 TYPE: 'remove' // not delete as delete is reserved in js
4633 },
4634
4635 // @method intialize(): void
4636 initialize: function (map, options) {
4637 L.Handler.prototype.initialize.call(this, map);
4638
4639 L.Util.setOptions(this, options);
4640
4641 // Store the selectable layer group for ease of access
4642 this._deletableLayers = this.options.featureGroup;
4643
4644 if (!(this._deletableLayers instanceof L.FeatureGroup)) {
4645 throw new Error('options.featureGroup must be a L.FeatureGroup');
4646 }
4647
4648 // Save the type so super can fire, need to do this as cannot do this.TYPE :(
4649 this.type = L.EditToolbar.Delete.TYPE;
4650
4651 var version = L.version.split('.');
4652 //If Version is >= 1.2.0
4653 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
4654 L.EditToolbar.Delete.include(L.Evented.prototype);
4655 } else {
4656 L.EditToolbar.Delete.include(L.Mixin.Events);
4657 }
4658
4659 },
4660
4661 // @method enable(): void
4662 // Enable the delete toolbar
4663 enable: function () {
4664 if (this._enabled || !this._hasAvailableLayers()) {
4665 return;
4666 }
4667 this.fire('enabled', {handler: this.type});
4668
4669 this._map.fire(L.Draw.Event.DELETESTART, {handler: this.type});
4670
4671 L.Handler.prototype.enable.call(this);
4672
4673 this._deletableLayers
4674 .on('layeradd', this._enableLayerDelete, this)
4675 .on('layerremove', this._disableLayerDelete, this);
4676 },
4677
4678 // @method disable(): void
4679 // Disable the delete toolbar
4680 disable: function () {
4681 if (!this._enabled) {
4682 return;
4683 }
4684
4685 this._deletableLayers
4686 .off('layeradd', this._enableLayerDelete, this)
4687 .off('layerremove', this._disableLayerDelete, this);
4688
4689 L.Handler.prototype.disable.call(this);
4690
4691 this._map.fire(L.Draw.Event.DELETESTOP, {handler: this.type});
4692
4693 this.fire('disabled', {handler: this.type});
4694 },
4695
4696 // @method addHooks(): void
4697 // Add listener hooks to this handler
4698 addHooks: function () {
4699 var map = this._map;
4700
4701 if (map) {
4702 map.getContainer().focus();
4703
4704 this._deletableLayers.eachLayer(this._enableLayerDelete, this);
4705 this._deletedLayers = new L.LayerGroup();
4706
4707 this._tooltip = new L.Draw.Tooltip(this._map);
4708 this._tooltip.updateContent({text: L.drawLocal.edit.handlers.remove.tooltip.text});
4709
4710 this._map.on('mousemove', this._onMouseMove, this);
4711 }
4712 },
4713
4714 // @method removeHooks(): void
4715 // Remove listener hooks from this handler
4716 removeHooks: function () {
4717 if (this._map) {
4718 this._deletableLayers.eachLayer(this._disableLayerDelete, this);
4719 this._deletedLayers = null;
4720
4721 this._tooltip.dispose();
4722 this._tooltip = null;
4723
4724 this._map.off('mousemove', this._onMouseMove, this);
4725 }
4726 },
4727
4728 // @method revertLayers(): void
4729 // Revert the deleted layers back to their prior state.
4730 revertLayers: function () {
4731 // Iterate of the deleted layers and add them back into the featureGroup
4732 this._deletedLayers.eachLayer(function (layer) {
4733 this._deletableLayers.addLayer(layer);
4734 layer.fire('revert-deleted', {layer: layer});
4735 }, this);
4736 },
4737
4738 // @method save(): void
4739 // Save deleted layers
4740 save: function () {
4741 this._map.fire(L.Draw.Event.DELETED, {layers: this._deletedLayers});
4742 },
4743
4744 // @method removeAllLayers(): void
4745 // Remove all delateable layers
4746 removeAllLayers: function () {
4747 // Iterate of the delateable layers and add remove them
4748 this._deletableLayers.eachLayer(function (layer) {
4749 this._removeLayer({layer: layer});
4750 }, this);
4751 this.save();
4752 },
4753
4754 _enableLayerDelete: function (e) {
4755 var layer = e.layer || e.target || e;
4756
4757 layer.on('click', this._removeLayer, this);
4758 },
4759
4760 _disableLayerDelete: function (e) {
4761 var layer = e.layer || e.target || e;
4762
4763 layer.off('click', this._removeLayer, this);
4764
4765 // Remove from the deleted layers so we can't accidentally revert if the user presses cancel
4766 this._deletedLayers.removeLayer(layer);
4767 },
4768
4769 _removeLayer: function (e) {
4770 var layer = e.layer || e.target || e;
4771
4772 this._deletableLayers.removeLayer(layer);
4773
4774 this._deletedLayers.addLayer(layer);
4775
4776 layer.fire('deleted');
4777 },
4778
4779 _onMouseMove: function (e) {
4780 this._tooltip.updatePosition(e.latlng);
4781 },
4782
4783 _hasAvailableLayers: function () {
4784 return this._deletableLayers.getLayers().length !== 0;
4785 }
4786 });
4787
4788
4789
4790 }(window, document));
4791 //# sourceMappingURL=leaflet.draw-src.map