2 Copyright (c) 2014 Dominik Moritz
 
   4 This file is part of the leaflet locate control. It is licensed under the MIT license.
 
   5 You can find the project at: https://github.com/domoritz/leaflet-locatecontrol
 
   7 L.Control.Locate = L.Control.extend({
 
  11         follow: false,  // follow with zoom and pan the user's location
 
  12         stopFollowingOnDrag: false, // if follow is true, stop following when map is dragged (deprecated)
 
  30         // changes to range circle and inner marker while following
 
  31         // it is only necessary to provide the things that should change
 
  32         followCircleStyle: {},
 
  35             //fillColor: '#FFB000'
 
  37         icon: 'icon-location',  // icon-location or icon-direction
 
  38         iconLoading: 'icon-spinner animate-spin',
 
  39         circlePadding: [0, 0],
 
  41         onLocationError: function(err) {
 
  42             // this event is called in case of any location error
 
  43             // that is not a time out error.
 
  46         onLocationOutsideMapBounds: function(control) {
 
  47             // this event is repeatedly called when the location changes
 
  49             alert(context.options.strings.outsideMapBoundsMsg);
 
  51         setView: true, // automatically sets the map view to the user's location
 
  52         // keep the current map zoom level when displaying the user's location. (if 'false', use maxZoom)
 
  53         keepCurrentZoomLevel: false,
 
  55             title: "Show me where I am",
 
  56             popup: "You are within {distance} {unit} from this point",
 
  57             outsideMapBoundsMsg: "You seem located outside the boundaries of the map"
 
  61             watch: true  // if you overwrite this, visualization cannot be updated
 
  65     onAdd: function (map) {
 
  66         var container = L.DomUtil.create('div', 'control-locate');
 
  69         this._layer = new L.LayerGroup();
 
  70         this._layer.addTo(map);
 
  71         this._event = undefined;
 
  73         this._locateOptions = this.options.locateOptions;
 
  74         L.extend(this._locateOptions, this.options.locateOptions);
 
  75         L.extend(this._locateOptions, {
 
  76             setView: false // have to set this to false because we have to
 
  77                            // do setView manually
 
  80         // extend the follow marker style and circle from the normal style
 
  82         L.extend(tmp, this.options.markerStyle, this.options.followMarkerStyle);
 
  83         this.options.followMarkerStyle = tmp;
 
  85         L.extend(tmp, this.options.circleStyle, this.options.followCircleStyle);
 
  86         this.options.followCircleStyle = tmp;
 
  88         var link = L.DomUtil.create('a', 'control-button ' + this.options.icon, container);
 
  89         link.innerHTML = "<span class='icon geolocate'></span>";
 
  91         link.title = this.options.strings.title;
 
  94             .on(link, 'click', L.DomEvent.stopPropagation)
 
  95             .on(link, 'click', L.DomEvent.preventDefault)
 
  96             .on(link, 'click', function() {
 
  97                 if (self._active && (self._event === undefined || map.getBounds().contains(self._event.latlng) || !self.options.setView ||
 
  98                     isOutsideMapBounds())) {
 
 104             .on(link, 'dblclick', L.DomEvent.stopPropagation);
 
 106         var locate = function () {
 
 107             if (self.options.setView) {
 
 108                 self._locateOnNextLocationFound = true;
 
 111                 map.locate(self._locateOptions);
 
 114             if (self.options.follow) {
 
 118                 setClasses('requesting');
 
 124         var onLocationFound = function (e) {
 
 125             // no need to do anything if the location has not changed
 
 127                 (self._event.latlng.lat === e.latlng.lat &&
 
 128                  self._event.latlng.lng === e.latlng.lng &&
 
 129                  self._event.accuracy === e.accuracy)) {
 
 139             if (self.options.follow && self._following) {
 
 140                 self._locateOnNextLocationFound = true;
 
 146         var startFollowing = function() {
 
 147             map.fire('startfollowing', self);
 
 148             self._following = true;
 
 149             if (self.options.stopFollowingOnDrag) {
 
 150                 map.on('dragstart', stopFollowing);
 
 154         var stopFollowing = function() {
 
 155             map.fire('stopfollowing', self);
 
 156             self._following = false;
 
 157             if (self.options.stopFollowingOnDrag) {
 
 158                 map.off('dragstart', stopFollowing);
 
 163         var isOutsideMapBounds = function () {
 
 164             if (self._event === undefined)
 
 166             return map.options.maxBounds &&
 
 167                 !map.options.maxBounds.contains(self._event.latlng);
 
 170         var visualizeLocation = function() {
 
 171             if (self._event.accuracy === undefined)
 
 172                 self._event.accuracy = 0;
 
 174             var radius = self._event.accuracy;
 
 175             if (self._locateOnNextLocationFound) {
 
 176                 if (isOutsideMapBounds()) {
 
 177                     self.options.onLocationOutsideMapBounds(self);
 
 179                     map.fitBounds(self._event.bounds, {
 
 180                         padding: self.options.circlePadding,
 
 181                         maxZoom: self.options.keepCurrentZoomLevel ? map.getZoom() : self._locateOptions.maxZoom
 
 184                 self._locateOnNextLocationFound = false;
 
 187             // circle with the radius of the location's accuracy
 
 189             if (self.options.drawCircle) {
 
 190                 if (self._following) {
 
 191                     style = self.options.followCircleStyle;
 
 193                     style = self.options.circleStyle;
 
 197                     self._circle = L.circle(self._event.latlng, radius, style)
 
 200                     self._circle.setLatLng(self._event.latlng).setRadius(radius);
 
 202                         self._circle.options[o] = style[o];
 
 208             if (self.options.metric) {
 
 209                 distance = radius.toFixed(0);
 
 212                 distance = (radius * 3.2808399).toFixed(0);
 
 216             // small inner marker
 
 218             if (self._following) {
 
 219                 mStyle = self.options.followMarkerStyle;
 
 221                 mStyle = self.options.markerStyle;
 
 224             var t = self.options.strings.popup;
 
 225             if (!self._circleMarker) {
 
 226                 self._circleMarker = L.circleMarker(self._event.latlng, mStyle)
 
 227                     .bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
 
 230                 self._circleMarker.setLatLng(self._event.latlng)
 
 231                     .bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
 
 232                     ._popup.setLatLng(self._event.latlng);
 
 234                     self._circleMarker.options[o] = mStyle[o];
 
 238             if (!self._container)
 
 240             if (self._following) {
 
 241                 setClasses('following');
 
 243                 setClasses('active');
 
 247         var setClasses = function(state) {
 
 248             if (state == 'requesting') {
 
 249                 L.DomUtil.removeClasses(self._container, "active following");
 
 250                 L.DomUtil.addClasses(self._container, "requesting");
 
 252                 L.DomUtil.removeClasses(link, self.options.icon);
 
 253                 L.DomUtil.addClasses(link, self.options.iconLoading);
 
 254             } else if (state == 'active') {
 
 255                 L.DomUtil.removeClasses(self._container, "requesting following");
 
 256                 L.DomUtil.addClasses(self._container, "active");
 
 258                 L.DomUtil.removeClasses(link, self.options.iconLoading);
 
 259                 L.DomUtil.addClasses(link, self.options.icon);
 
 260             } else if (state == 'following') {
 
 261                 L.DomUtil.removeClasses(self._container, "requesting");
 
 262                 L.DomUtil.addClasses(self._container, "active following");
 
 264                 L.DomUtil.removeClasses(link, self.options.iconLoading);
 
 265                 L.DomUtil.addClasses(link, self.options.icon);
 
 269         var resetVariables = function() {
 
 270             self._active = false;
 
 271             self._locateOnNextLocationFound = self.options.setView;
 
 272             self._following = false;
 
 277         var stopLocate = function() {
 
 279             map.off('dragstart', stopFollowing);
 
 280             if (self.options.follow && self._following) {
 
 284             L.DomUtil.removeClass(self._container, "requesting");
 
 285             L.DomUtil.removeClass(self._container, "active");
 
 286             L.DomUtil.removeClass(self._container, "following");
 
 289             self._layer.clearLayers();
 
 290             self._circleMarker = undefined;
 
 291             self._circle = undefined;
 
 294         var onLocationError = function (err) {
 
 295             // ignore time out error if the location is watched
 
 296             if (err.code == 3 && self._locateOptions.watch) {
 
 301             self.options.onLocationError(err);
 
 305         map.on('locationfound', onLocationFound, self);
 
 306         map.on('locationerror', onLocationError, self);
 
 308         // make locate functions available to outside world
 
 309         this.locate = locate;
 
 310         this.stopLocate = stopLocate;
 
 311         this.stopFollowing = stopFollowing;
 
 317 L.Map.addInitHook(function () {
 
 318     if (this.options.locateControl) {
 
 319         this.locateControl = L.control.locate();
 
 320         this.addControl(this.locateControl);
 
 324 L.control.locate = function (options) {
 
 325     return new L.Control.Locate(options);
 
 329   // leaflet.js raises bug when trying to addClass / removeClass multiple classes at once
 
 330   // Let's create a wrapper on it which fixes it.
 
 331   var LDomUtilApplyClassesMethod = function(method, element, classNames) {
 
 332     classNames = classNames.split(' ');
 
 333     classNames.forEach(function(className) {
 
 334         L.DomUtil[method].call(this, element, className);
 
 338   L.DomUtil.addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); }
 
 339   L.DomUtil.removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); }