]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.draw.js
Port to Leaflet
[rails.git] / vendor / assets / leaflet / leaflet.draw.js
1 /*
2  Copyright (c) 2012, Smartrak, Jacob Toye
3  Leaflet.draw is an open-source JavaScript library for drawing shapes/markers on leaflet powered maps.
4  https://github.com/jacobtoye/Leaflet.draw
5 */
6 (function (window, undefined) {
7
8 L.drawVersion = '0.1.4';
9
10 L.Util.extend(L.LineUtil, {
11         // Checks to see if two line segments intersect. Does not handle degenerate cases.
12         // http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf
13         segmentsIntersect: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2, /*Point*/ p3) {
14                 return  this._checkCounterclockwise(p, p2, p3) !==
15                                 this._checkCounterclockwise(p1, p2, p3) &&
16                                 this._checkCounterclockwise(p, p1, p2) !==
17                                 this._checkCounterclockwise(p, p1, p3);
18         },
19
20         // check to see if points are in counterclockwise order
21         _checkCounterclockwise: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
22                 return (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x);
23         }
24 });
25
26 L.Polyline.include({
27         // Check to see if this polyline has any linesegments that intersect.
28         // NOTE: does not support detecting intersection for degenerate cases.
29         intersects: function () {
30                 var points = this._originalPoints,
31                         len = points ? points.length : 0,
32                         i, j, p, p1, p2, p3;
33
34                 if (this._tooFewPointsForIntersection()) {
35                         return false;
36                 }
37
38                 for (i = len - 1; i >= 3; i--) {
39                         p = points[i - 1];
40                         p1 = points[i];
41
42                         
43                         if (this._lineSegmentsIntersectsRange(p, p1, i - 2)) {
44                                 return true;
45                         }
46                 }
47
48                 return false;
49         },
50
51         // Check for intersection if new latlng was added to this polyline.
52         // NOTE: does not support detecting intersection for degenerate cases.
53         newLatLngIntersects: function (latlng, skipFirst) {
54                 // Cannot check a polyline for intersecting lats/lngs when not added to the map
55                 if (!this._map) {
56                         return false;
57                 }
58
59                 return this.newPointIntersects(this._map.latLngToLayerPoint(latlng), skipFirst);
60         },
61
62         // Check for intersection if new point was added to this polyline.
63         // newPoint must be a layer point.
64         // NOTE: does not support detecting intersection for degenerate cases.
65         newPointIntersects: function (newPoint, skipFirst) {
66                 var points = this._originalPoints,
67                         len = points ? points.length : 0,
68                         lastPoint = points ? points[len - 1] : null,
69                         // The previous previous line segment. Previous line segement doesn't need testing.
70                         maxIndex = len - 2;
71
72                 if (this._tooFewPointsForIntersection(1)) {
73                         return false;
74                 }
75
76                 return this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex, skipFirst ? 1 : 0);
77         },
78
79         // Polylines with 2 sides can only intersect in cases where points are collinear (we don't support detecting these).
80         // Cannot have intersection when < 3 line segments (< 4 points)
81         _tooFewPointsForIntersection: function (extraPoints) {
82                 var points = this._originalPoints,
83                         len = points ? points.length : 0;
84                 // Increment length by extraPoints if present
85                 len += extraPoints || 0;
86
87                 return !this._originalPoints || len <= 3;
88         },
89
90         // Checks a line segment intersections with any line segements before its predecessor.
91         // Don't need to check the predecessor as will never intersect.
92         _lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) {
93                 var points = this._originalPoints,
94                         p2, p3;
95
96                 minIndex = minIndex || 0;
97
98                 // Check all previous line segments (beside the immediately previous) for intersections
99                 for (var j = maxIndex; j > minIndex; j--) {
100                         p2 = points[j - 1];
101                         p3 = points[j];
102
103                         if (L.LineUtil.segmentsIntersect(p, p1, p2, p3)) {
104                                 return true;
105                         }
106                 }
107
108                 return false;
109         }
110 });
111
112 L.Polygon.include({
113         // Checks a polygon for any intersecting line segments. Ignores holes.
114         intersects: function () {
115                 var polylineIntersects,
116                         points = this._originalPoints,
117                         len, firstPoint, lastPoint, maxIndex;
118
119                 if (this._tooFewPointsForIntersection()) {
120                         return false;
121                 }
122
123                 polylineIntersects = L.Polyline.prototype.intersects.call(this);
124
125                 // If already found an intersection don't need to check for any more.
126                 if (polylineIntersects) {
127                         return true;
128                 }
129
130                 len = points.length;
131                 firstPoint = points[0];
132                 lastPoint = points[len - 1];
133                 maxIndex = len - 2;
134
135                 // Check the line segment between last and first point. Don't need to check the first line segment (minIndex = 1)
136                 return this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex, 1);
137         }
138 });
139
140 L.Handler.Draw = L.Handler.extend({
141         includes: L.Mixin.Events,
142
143         initialize: function (map, options) {
144                 this._map = map;
145                 this._container = map._container;
146                 this._overlayPane = map._panes.overlayPane;
147                 this._popupPane = map._panes.popupPane;
148
149                 // Merge default shapeOptions options with custom shapeOptions
150                 if (options && options.shapeOptions) {
151                         options.shapeOptions = L.Util.extend({}, this.options.shapeOptions, options.shapeOptions);
152                 }
153                 L.Util.extend(this.options, options);
154         },
155
156         enable: function () {
157                 this.fire('activated');
158                 L.Handler.prototype.enable.call(this);
159         },
160         
161         addHooks: function () {
162                 if (this._map) {
163                         L.DomUtil.disableTextSelection();
164
165                         this._label = L.DomUtil.create('div', 'leaflet-draw-label', this._popupPane);
166                         this._singleLineLabel = false;
167
168                         L.DomEvent.addListener(this._container, 'keyup', this._cancelDrawing, this);
169                 }
170         },
171
172         removeHooks: function () {
173                 if (this._map) {
174                         L.DomUtil.enableTextSelection();
175
176                         this._popupPane.removeChild(this._label);
177                         delete this._label;
178
179                         L.DomEvent.removeListener(this._container, 'keyup', this._cancelDrawing);
180                 }
181         },
182
183         _updateLabelText: function (labelText) {
184                 labelText.subtext = labelText.subtext || '';
185
186                 // update the vertical position (only if changed)
187                 if (labelText.subtext.length === 0 && !this._singleLineLabel) {
188                         L.DomUtil.addClass(this._label, 'leaflet-draw-label-single');
189                         this._singleLineLabel = true;
190                 }
191                 else if (labelText.subtext.length > 0 && this._singleLineLabel) {
192                         L.DomUtil.removeClass(this._label, 'leaflet-draw-label-single');
193                         this._singleLineLabel = false;
194                 }
195
196                 this._label.innerHTML =
197                         (labelText.subtext.length > 0 ? '<span class="leaflet-draw-label-subtext">' + labelText.subtext + '</span>' + '<br />' : '') +
198                         '<span>' + labelText.text + '</span>';
199         },
200
201         _updateLabelPosition: function (pos) {
202                 L.DomUtil.setPosition(this._label, pos);
203         },
204
205         // Cancel drawing when the escape key is pressed
206         _cancelDrawing: function (e) {
207                 if (e.keyCode === 27) {
208                         this.disable();
209                 }
210         }
211 });
212
213 L.Polyline.Draw = L.Handler.Draw.extend({
214         Poly: L.Polyline,
215
216         options: {
217                 allowIntersection: true,
218                 drawError: {
219                         color: '#b00b00',
220                         message: '<strong>Error:</strong> shape edges cannot cross!',
221                         timeout: 2500
222                 },
223                 icon: new L.DivIcon({
224                         iconSize: new L.Point(8, 8),
225                         className: 'leaflet-div-icon leaflet-editing-icon'
226                 }),
227                 guidelineDistance: 20,
228                 shapeOptions: {
229                         stroke: true,
230                         color: '#f06eaa',
231                         weight: 4,
232                         opacity: 0.5,
233                         fill: false,
234                         clickable: true
235                 },
236                 zIndexOffset: 2000 // This should be > than the highest z-index any map layers
237         },
238
239         initialize: function (map, options) {
240                 // Merge default drawError options with custom options
241                 if (options && options.drawError) {
242                         options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
243                 }
244                 L.Handler.Draw.prototype.initialize.call(this, map, options);
245         },
246         
247         addHooks: function () {
248                 L.Handler.Draw.prototype.addHooks.call(this);
249                 if (this._map) {
250                         this._markers = [];
251
252                         this._markerGroup = new L.LayerGroup();
253                         this._map.addLayer(this._markerGroup);
254
255                         this._poly = new L.Polyline([], this.options.shapeOptions);
256
257                         this._updateLabelText(this._getLabelText());
258
259                         // Make a transparent marker that will used to catch click events. These click
260                         // events will create the vertices. We need to do this so we can ensure that
261                         // we can create vertices over other map layers (markers, vector layers). We
262                         // also do not want to trigger any click handlers of objects we are clicking on
263                         // while drawing.
264                         if (!this._mouseMarker) {
265                                 this._mouseMarker = L.marker(this._map.getCenter(), {
266                                         icon: L.divIcon({
267                                                 className: 'leaflet-mouse-marker',
268                                                 iconAnchor: [20, 20],
269                                                 iconSize: [40, 40]
270                                         }),
271                                         opacity: 0,
272                                         zIndexOffset: this.options.zIndexOffset
273                                 });
274                         }
275
276                         this._mouseMarker
277                                 .on('click', this._onClick, this)
278                                 .addTo(this._map);
279
280                         this._map.on('mousemove', this._onMouseMove, this);
281                 }
282         },
283
284         removeHooks: function () {
285                 L.Handler.Draw.prototype.removeHooks.call(this);
286
287                 this._clearHideErrorTimeout();
288
289                 this._cleanUpShape();
290                 
291                 // remove markers from map
292                 this._map.removeLayer(this._markerGroup);
293                 delete this._markerGroup;
294                 delete this._markers;
295
296                 this._map.removeLayer(this._poly);
297                 delete this._poly;
298
299                 this._mouseMarker.off('click', this._onClick);
300                 this._map.removeLayer(this._mouseMarker);
301                 delete this._mouseMarker;
302
303                 // clean up DOM
304                 this._clearGuides();
305
306                 this._map.off('mousemove', this._onMouseMove);
307         },
308
309         _finishShape: function () {
310                 if (!this.options.allowIntersection && this._poly.newLatLngIntersects(this._poly.getLatLngs()[0], true)) {
311                         this._showErrorLabel();
312                         return;
313                 }
314                 if (!this._shapeIsValid()) {
315                         this._showErrorLabel();
316                         return;
317                 }
318
319                 this._map.fire(
320                         'draw:poly-created',
321                         { poly: new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions) }
322                 );
323                 this.disable();
324         },
325
326         //Called to verify the shape is valid when the user tries to finish it
327         //Return false if the shape is not valid
328         _shapeIsValid: function () {
329                 return true;
330         },
331
332         _onMouseMove: function (e) {
333                 var newPos = e.layerPoint,
334                         latlng = e.latlng,
335                         markerCount = this._markers.length;
336
337                 // Save latlng
338                 this._currentLatLng = latlng;
339
340                 // update the label
341                 this._updateLabelPosition(newPos);
342
343                 if (markerCount > 0) {
344                         this._updateLabelText(this._getLabelText());
345                         // draw the guide line
346                         this._clearGuides();
347                         this._drawGuide(
348                                 this._map.latLngToLayerPoint(this._markers[markerCount - 1].getLatLng()),
349                                 newPos
350                         );
351                 }
352
353                 // Update the mouse marker position
354                 this._mouseMarker.setLatLng(latlng);
355
356                 L.DomEvent.preventDefault(e.originalEvent);
357         },
358
359         _onClick: function (e) {
360                 var latlng = e.target.getLatLng(),
361                         markerCount = this._markers.length;
362
363                 if (markerCount > 0 && !this.options.allowIntersection && this._poly.newLatLngIntersects(latlng)) {
364                         this._showErrorLabel();
365                         return;
366                 }
367                 else if (this._errorShown) {
368                         this._hideErrorLabel();
369                 }
370
371                 this._markers.push(this._createMarker(latlng));
372
373                 this._poly.addLatLng(latlng);
374
375                 if (this._poly.getLatLngs().length === 2) {
376                         this._map.addLayer(this._poly);
377                 }
378
379                 this._updateMarkerHandler();
380
381                 this._vertexAdded(latlng);
382         },
383
384         _updateMarkerHandler: function () {
385                 // The last marker shold have a click handler to close the polyline
386                 if (this._markers.length > 1) {
387                         this._markers[this._markers.length - 1].on('click', this._finishShape, this);
388                 }
389                 
390                 // Remove the old marker click handler (as only the last point should close the polyline)
391                 if (this._markers.length > 2) {
392                         this._markers[this._markers.length - 2].off('click', this._finishShape);
393                 }
394         },
395         
396         _createMarker: function (latlng) {
397                 var marker = new L.Marker(latlng, {
398                         icon: this.options.icon,
399                         zIndexOffset: this.options.zIndexOffset * 2
400                 });
401                 
402                 this._markerGroup.addLayer(marker);
403
404                 return marker;
405         },
406
407         _drawGuide: function (pointA, pointB) {
408                 var length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) + Math.pow((pointB.y - pointA.y), 2))),
409                         i,
410                         fraction,
411                         dashPoint,
412                         dash;
413
414                 //create the guides container if we haven't yet (TODO: probaly shouldn't do this every time the user starts to draw?)
415                 if (!this._guidesContainer) {
416                         this._guidesContainer = L.DomUtil.create('div', 'leaflet-draw-guides', this._overlayPane);
417                 }
418         
419                 //draw a dash every GuildeLineDistance
420                 for (i = this.options.guidelineDistance; i < length; i += this.options.guidelineDistance) {
421                         //work out fraction along line we are
422                         fraction = i / length;
423
424                         //calculate new x,y point
425                         dashPoint = {
426                                 x: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)),
427                                 y: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y))
428                         };
429
430                         //add guide dash to guide container
431                         dash = L.DomUtil.create('div', 'leaflet-draw-guide-dash', this._guidesContainer);
432                         dash.style.backgroundColor =
433                                 !this._errorShown ? this.options.shapeOptions.color : this.options.drawError.color;
434
435                         L.DomUtil.setPosition(dash, dashPoint);
436                 }
437         },
438
439         _updateGuideColor: function (color) {
440                 if (this._guidesContainer) {
441                         for (var i = 0, l = this._guidesContainer.childNodes.length; i < l; i++) {
442                                 this._guidesContainer.childNodes[i].style.backgroundColor = color;
443                         }
444                 }
445         },
446
447         // removes all child elements (guide dashes) from the guides container
448         _clearGuides: function () {
449                 if (this._guidesContainer) {
450                         while (this._guidesContainer.firstChild) {
451                                 this._guidesContainer.removeChild(this._guidesContainer.firstChild);
452                         }
453                 }
454         },
455
456         _updateLabelText: function (labelText) {
457                 if (!this._errorShown) {
458                         L.Handler.Draw.prototype._updateLabelText.call(this, labelText);
459                 }
460         },
461
462         _getLabelText: function () {
463                 var labelText,
464                         distance,
465                         distanceStr;
466
467                 if (this._markers.length === 0) {
468                         labelText = {
469                                 text: 'Click to start drawing line.'
470                         };
471                 } else {
472                         // calculate the distance from the last fixed point to the mouse position
473                         distance = this._measurementRunningTotal + this._currentLatLng.distanceTo(this._markers[this._markers.length - 1].getLatLng());
474                         // show metres when distance is < 1km, then show km
475                         distanceStr = distance  > 1000 ? (distance  / 1000).toFixed(2) + ' km' : Math.ceil(distance) + ' m';
476                         
477                         if (this._markers.length === 1) {
478                                 labelText = {
479                                         text: 'Click to continue drawing line.',
480                                         subtext: distanceStr
481                                 };
482                         } else {
483                                 labelText = {
484                                         text: 'Click last point to finish line.',
485                                         subtext: distanceStr
486                                 };
487                         }
488                 }
489                 return labelText;
490         },
491
492         _showErrorLabel: function () {
493                 this._errorShown = true;
494
495                 // Update label
496                 L.DomUtil.addClass(this._label, 'leaflet-error-draw-label');
497                 L.DomUtil.addClass(this._label, 'leaflet-flash-anim');
498                 L.Handler.Draw.prototype._updateLabelText.call(this, { text: this.options.drawError.message });
499
500                 // Update shape
501                 this._updateGuideColor(this.options.drawError.color);
502                 this._poly.setStyle({ color: this.options.drawError.color });
503
504                 // Hide the error after 2 seconds
505                 this._clearHideErrorTimeout();
506                 this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorLabel, this), this.options.drawError.timeout);
507         },
508
509         _hideErrorLabel: function () {
510                 this._errorShown = false;
511
512                 this._clearHideErrorTimeout();
513                 
514                 // Revert label
515                 L.DomUtil.removeClass(this._label, 'leaflet-error-draw-label');
516                 L.DomUtil.removeClass(this._label, 'leaflet-flash-anim');
517                 this._updateLabelText(this._getLabelText());
518
519                 // Revert shape
520                 this._updateGuideColor(this.options.shapeOptions.color);
521                 this._poly.setStyle({ color: this.options.shapeOptions.color });
522         },
523
524         _clearHideErrorTimeout: function () {
525                 if (this._hideErrorTimeout) {
526                         clearTimeout(this._hideErrorTimeout);
527                         this._hideErrorTimeout = null;
528                 }
529         },
530
531         _vertexAdded: function (latlng) {
532                 if (this._markers.length === 1) {
533                         this._measurementRunningTotal = 0;
534                 }
535                 else {
536                         this._measurementRunningTotal +=
537                                 latlng.distanceTo(this._markers[this._markers.length - 2].getLatLng());
538                 }
539         },
540
541         _cleanUpShape: function () {
542                 if (this._markers.length > 0) {
543                         this._markers[this._markers.length - 1].off('click', this._finishShape);
544                 }
545         }
546 });
547
548 L.Polygon.Draw = L.Polyline.Draw.extend({
549         Poly: L.Polygon,
550
551         options: {
552                 shapeOptions: {
553                         stroke: true,
554                         color: '#f06eaa',
555                         weight: 4,
556                         opacity: 0.5,
557                         fill: true,
558                         fillColor: null, //same as color by default
559                         fillOpacity: 0.2,
560                         clickable: false
561                 }
562         },
563
564         _updateMarkerHandler: function () {
565                 // The first marker shold have a click handler to close the polygon
566                 if (this._markers.length === 1) {
567                         this._markers[0].on('click', this._finishShape, this);
568                 }
569         },
570
571         _getLabelText: function () {
572                 var text;
573                 if (this._markers.length === 0) {
574                         text = 'Click to start drawing shape.';
575                 } else if (this._markers.length < 3) {
576                         text = 'Click to continue drawing shape.';
577                 } else {
578                         text = 'Click first point to close this shape.';
579                 }
580                 return {
581                         text: text
582                 };
583         },
584
585         _shapeIsValid: function () {
586                 return this._markers.length >= 3;
587         },
588
589         _vertexAdded: function (latlng) {
590                 //calc area here
591         },
592
593         _cleanUpShape: function () {
594                 if (this._markers.length > 0) {
595                         this._markers[0].off('click', this._finishShape);
596                 }
597         }
598 });
599
600 L.SimpleShape = {};
601
602 L.SimpleShape.Draw = L.Handler.Draw.extend({
603         addHooks: function () {
604                 L.Handler.Draw.prototype.addHooks.call(this);
605                 if (this._map) {
606                         this._map.dragging.disable();
607                         //TODO refactor: move cursor to styles
608                         this._container.style.cursor = 'crosshair';
609
610                         this._updateLabelText({ text: this._initialLabelText });
611
612                         this._map
613                                 .on('mousedown', this._onMouseDown, this)
614                                 .on('mousemove', this._onMouseMove, this);
615
616                 }
617         },
618
619         removeHooks: function () {
620                 L.Handler.Draw.prototype.removeHooks.call(this);
621                 if (this._map) {
622                         this._map.dragging.enable();
623                         //TODO refactor: move cursor to styles
624                         this._container.style.cursor = '';
625
626                         this._map
627                                 .off('mousedown', this._onMouseDown, this)
628                                 .off('mousemove', this._onMouseMove, this);
629
630                         L.DomEvent.off(document, 'mouseup', this._onMouseUp);
631
632                         // If the box element doesn't exist they must not have moved the mouse, so don't need to destroy/return
633                         if (this._shape) {
634                                 this._map.removeLayer(this._shape);
635                                 delete this._shape;
636                         }
637                 }
638                 this._isDrawing = false;
639         },
640
641         _onMouseDown: function (e) {
642                 this._isDrawing = true;
643                 this._startLatLng = e.latlng;
644
645                 L.DomEvent
646                         .on(document, 'mouseup', this._onMouseUp, this)
647                         .preventDefault(e.originalEvent);
648         },
649
650         _onMouseMove: function (e) {
651                 var layerPoint = e.layerPoint,
652                                 latlng = e.latlng;
653
654                 this._updateLabelPosition(layerPoint);
655                 if (this._isDrawing) {
656                         this._updateLabelText({ text: 'Release mouse to finish drawing.' });
657                         this._drawShape(latlng);
658                 }
659         },
660
661         _onMouseUp: function (e) {
662                 if (this._shape) {
663                         this._fireCreatedEvent();
664                 }
665                 
666                 this.disable();
667         }
668 });
669
670 L.Circle.Draw = L.SimpleShape.Draw.extend({
671         options: {
672                 shapeOptions: {
673                         stroke: true,
674                         color: '#f06eaa',
675                         weight: 4,
676                         opacity: 0.5,
677                         fill: true,
678                         fillColor: null, //same as color by default
679                         fillOpacity: 0.2,
680                         clickable: true
681                 }
682         },
683
684         _initialLabelText: 'Click and drag to draw circle.',
685
686         _drawShape: function (latlng) {
687                 if (!this._shape) {
688                         this._shape = new L.Circle(this._startLatLng, this._startLatLng.distanceTo(latlng), this.options.shapeOptions);
689                         this._map.addLayer(this._shape);
690                 } else {
691                         this._shape.setRadius(this._startLatLng.distanceTo(latlng));
692                 }
693         },
694
695         _fireCreatedEvent: function () {
696                 this._map.fire(
697                         'draw:circle-created',
698                         { circ: new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions) }
699                 );
700         }
701 });
702
703 L.Rectangle.Draw = L.SimpleShape.Draw.extend({
704         options: {
705                 shapeOptions: {
706                         stroke: true,
707                         color: '#f06eaa',
708                         weight: 4,
709                         opacity: 0.5,
710                         fill: true,
711                         fillColor: null, //same as color by default
712                         fillOpacity: 0.2,
713                         clickable: true
714                 }
715         },
716         
717         _initialLabelText: 'Click and drag to draw rectangle.',
718
719         _drawShape: function (latlng) {
720                 if (!this._shape) {
721                         this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, latlng), this.options.shapeOptions);
722                         this._map.addLayer(this._shape);
723                 } else {
724                         this._shape.setBounds(new L.LatLngBounds(this._startLatLng, latlng));
725                 }
726         },
727
728         _fireCreatedEvent: function () {
729                 this._map.fire(
730                         'draw:rectangle-created',
731                         { rect: new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions) }
732                 );
733         }
734 });
735
736 L.Marker.Draw = L.Handler.Draw.extend({
737         options: {
738                 icon: new L.Icon.Default(),
739                 zIndexOffset: 2000 // This should be > than the highest z-index any markers
740         },
741         
742         addHooks: function () {
743                 L.Handler.Draw.prototype.addHooks.call(this);
744                 
745                 if (this._map) {
746                         this._updateLabelText({ text: 'Click map to place marker.' });
747                         this._map.on('mousemove', this._onMouseMove, this);
748                 }
749         },
750
751         removeHooks: function () {
752                 L.Handler.Draw.prototype.removeHooks.call(this);
753                 
754                 if (this._map) {
755                         if (this._marker) {
756                                 this._marker.off('click', this._onClick);
757                                 this._map
758                                         .off('click', this._onClick)
759                                         .removeLayer(this._marker);
760                                 delete this._marker;
761                         }
762
763                         this._map.off('mousemove', this._onMouseMove);
764                 }
765         },
766
767         _onMouseMove: function (e) {
768                 var newPos = e.layerPoint,
769                         latlng = e.latlng;
770
771                 this._updateLabelPosition(newPos);
772
773                 if (!this._marker) {
774                         this._marker = new L.Marker(latlng, {
775                                 icon: this.options.icon,
776                                 zIndexOffset: this.options.zIndexOffset
777                         });
778                         // Bind to both marker and map to make sure we get the click event.
779                         this._marker.on('click', this._onClick, this);
780                         this._map
781                                 .on('click', this._onClick, this)
782                                 .addLayer(this._marker);
783                 }
784                 else {
785                         this._marker.setLatLng(latlng);
786                 }
787         },
788
789         _onClick: function (e) {
790                 this._map.fire(
791                         'draw:marker-created',
792                         { marker: new L.Marker(this._marker.getLatLng(), { icon: this.options.icon }) }
793                 );
794                 this.disable();
795         }
796 });
797
798 L.Map.mergeOptions({
799         drawControl: false
800 });
801
802 L.Control.Draw = L.Control.extend({
803
804         options: {
805                 position: 'topleft',
806                 polyline: {
807                         title: 'Draw a polyline'
808                 },
809                 polygon: {
810                         title: 'Draw a polygon'
811                 },
812                 rectangle: {
813                         title: 'Draw a rectangle'
814                 },
815                 circle: {
816                         title: 'Draw a circle'
817                 },
818                 marker: {
819                         title: 'Add a marker'
820                 }
821         },
822
823         handlers: {},
824         
825         initialize: function (options) {
826                 L.Util.extend(this.options, options);
827         },
828         
829         onAdd: function (map) {
830                 var className = 'leaflet-control-draw',
831                         container = L.DomUtil.create('div', className);
832
833                 if (this.options.polyline) {
834                         this.handlers.polyline = new L.Polyline.Draw(map, this.options.polyline);
835                         this._createButton(
836                                 this.options.polyline.title,
837                                 className + '-polyline',
838                                 container,
839                                 this.handlers.polyline.enable,
840                                 this.handlers.polyline
841                         );
842                         this.handlers.polyline.on('activated', this._disableInactiveModes, this);
843                 }
844
845                 if (this.options.polygon) {
846                         this.handlers.polygon = new L.Polygon.Draw(map, this.options.polygon);
847                         this._createButton(
848                                 this.options.polygon.title,
849                                 className + '-polygon',
850                                 container,
851                                 this.handlers.polygon.enable,
852                                 this.handlers.polygon
853                         );
854                         this.handlers.polygon.on('activated', this._disableInactiveModes, this);
855                 }
856
857                 if (this.options.rectangle) {
858                         this.handlers.rectangle = new L.Rectangle.Draw(map, this.options.rectangle);
859                         this._createButton(
860                                 this.options.rectangle.title,
861                                 className + '-rectangle',
862                                 container,
863                                 this.handlers.rectangle.enable,
864                                 this.handlers.rectangle
865                         );
866                         this.handlers.rectangle.on('activated', this._disableInactiveModes, this);
867                 }
868
869                 if (this.options.circle) {
870                         this.handlers.circle = new L.Circle.Draw(map, this.options.circle);
871                         this._createButton(
872                                 this.options.circle.title,
873                                 className + '-circle',
874                                 container,
875                                 this.handlers.circle.enable,
876                                 this.handlers.circle
877                         );
878                         this.handlers.circle.on('activated', this._disableInactiveModes, this);
879                 }
880
881                 if (this.options.marker) {
882                         this.handlers.marker = new L.Marker.Draw(map, this.options.marker);
883                         this._createButton(
884                                 this.options.marker.title,
885                                 className + '-marker',
886                                 container,
887                                 this.handlers.marker.enable,
888                                 this.handlers.marker
889                         );
890                         this.handlers.marker.on('activated', this._disableInactiveModes, this);
891                 }
892                 
893                 return container;
894         },
895
896         _createButton: function (title, className, container, fn, context) {
897                 var link = L.DomUtil.create('a', className, container);
898                 link.href = '#';
899                 link.title = title;
900
901                 L.DomEvent
902                         .on(link, 'click', L.DomEvent.stopPropagation)
903                         .on(link, 'mousedown', L.DomEvent.stopPropagation)
904                         .on(link, 'dblclick', L.DomEvent.stopPropagation)
905                         .on(link, 'click', L.DomEvent.preventDefault)
906                         .on(link, 'click', fn, context);
907
908                 return link;
909         },
910
911         // Need to disable the drawing modes if user clicks on another without disabling the current mode
912         _disableInactiveModes: function () {
913                 for (var i in this.handlers) {
914                         // Check if is a property of this object and is enabled
915                         if (this.handlers.hasOwnProperty(i) && this.handlers[i].enabled()) {
916                                 this.handlers[i].disable();
917                         }
918                 }
919         }
920 });
921
922 L.Map.addInitHook(function () {
923         if (this.options.drawControl) {
924                 this.drawControl = new L.Control.Draw();
925                 this.addControl(this.drawControl);
926         }
927 });
928
929
930
931 }(this));