Update leaflet.locate (fixes #438)
authorJohn Firebaugh <john.firebaugh@gmail.com>
Thu, 8 Aug 2013 21:06:18 +0000 (14:06 -0700)
committerJohn Firebaugh <john.firebaugh@gmail.com>
Thu, 8 Aug 2013 21:26:14 +0000 (14:26 -0700)
app/assets/javascripts/index.js
config/locales/en.yml
vendor/assets/leaflet/leaflet.locate.js

index 5190277c98c6927a040be94f64d6b6c17e5ef335..e46ca6d9d09d4483ace3474e5ca5299434e01465 100644 (file)
@@ -75,7 +75,10 @@ $(document).ready(function () {
 
   L.control.locate({
     position: position,
-    title: I18n.t('javascripts.map.locate.title')
+    strings: {
+      title: I18n.t('javascripts.map.locate.title'),
+      popup: I18n.t('javascripts.map.locate.popup')
+    }
   }).addTo(map);
 
   var sidebar = L.OSM.sidebar('#map-ui')
index 697c9a747a7a9b0ca000d65276c7870768a8ff03..c01abe71a133a33beed599c54a26c8618138b276 100644 (file)
@@ -2075,6 +2075,7 @@ en:
         out: Zoom Out
       locate:
         title: Show My Location
+        popup: You are within {distance} {unit} of this point
       base:
         standard: Standard
         cycle_map: Cycle Map
index 77754d84d14c6c16e452cff070da679dfcc5a74e..64fb144d4b209cf075aba4475e36deca7551a5c8 100644 (file)
@@ -1,16 +1,23 @@
+/*
+Copyright (c) 2013 Dominik Moritz
+
+This file is part of the leaflet locate control. It is licensed under the MIT license.
+You can find the project at: https://github.com/domoritz/leaflet-locatecontrol
+*/
 L.Control.Locate = L.Control.extend({
     options: {
         position: 'topleft',
         drawCircle: true,
         follow: false,  // follow with zoom and pan the user's location
+        stopFollowingOnDrag: false, // if follow is true, stop following when map is dragged
         // range circle
         circleStyle: {
-                color: '#136AEC',
-                fillColor: '#136AEC',
-                fillOpacity: 0.15,
-                weight: 2,
-                opacity: 0.5
-            },
+            color: '#136AEC',
+            fillColor: '#136AEC',
+            fillOpacity: 0.15,
+            weight: 2,
+            opacity: 0.5
+        },
         // inner marker
         markerStyle: {
             color: '#136AEC',
@@ -18,52 +25,70 @@ L.Control.Locate = L.Control.extend({
             fillOpacity: 0.7,
             weight: 2,
             opacity: 0.9,
-            radius: 4
+            radius: 5
+        },
+        // changes to range circle and inner marker while following
+        // it is only necessary to provide the things that should change
+        followCircleStyle: {},
+        followMarkerStyle: {
+            //color: '#FFA500',
+            //fillColor: '#FFB000'
         },
         metric: true,
-        debug: false,
         onLocationError: function(err) {
+            // this event is called in case of any location error
+            // that is not a time out error.
             alert(err.message);
         },
-        title: "Show me where I am",
-        popupText: ["You are within ", " from this point"],
+        onLocationOutsideMapBounds: function(context) {
+            // this event is repeatedly called when the location changes
+            alert(context.options.strings.outsideMapBoundsMsg);
+        },
         setView: true, // automatically sets the map view to the user's location
+        strings: {
+            title: "Show me where I am",
+            popup: "You are within {distance} {unit} from this point",
+            outsideMapBoundsMsg: "You seem located outside the boundaries of the map"
+        },
         locateOptions: {}
     },
 
     onAdd: function (map) {
-        var className = 'control-locate',
-            container = L.DomUtil.create('div', className);
+        var container = L.DomUtil.create('div',
+            'leaflet-control-locate leaflet-bar leaflet-control');
 
         var self = this;
         this._layer = new L.LayerGroup();
         this._layer.addTo(map);
         this._event = undefined;
-        // nested extend so that the first can overwrite the second
-        // and the second can overwrite the third
-        this._locateOptions = L.extend(L.extend({
-            'setView': false // have to set this to false because we have to
-                             // do setView manually
-        }, this.options.locateOptions), {
-            'watch': true  // if you overwrite this, visualization cannot be updated
+
+        this._locateOptions = {
+            watch: true  // if you overwrite this, visualization cannot be updated
+        };
+        L.extend(this._locateOptions, this.options.locateOptions);
+        L.extend(this._locateOptions, {
+            setView: false // have to set this to false because we have to
+                           // do setView manually
         });
 
-        var link = L.DomUtil.create('a', 'control-button', container);
-        link.innerHTML = "<span class='icon geolocate'></span>";
-        link.href = '#';
-        link.title = this.options.title;
+        // extend the follow marker style and circle from the normal style
+        var tmp = {};
+        L.extend(tmp, this.options.markerStyle, this.options.followMarkerStyle);
+        this.options.followMarkerStyle = tmp;
+        tmp = {};
+        L.extend(tmp, this.options.circleStyle, this.options.followCircleStyle);
+        this.options.followCircleStyle = tmp;
 
-        var _log = function(data) {
-            if (self.options.debug) {
-                console.log(data);
-            }
-        };
+        var link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container);
+        link.href = '#';
+        link.title = this.options.strings.title;
 
         L.DomEvent
             .on(link, 'click', L.DomEvent.stopPropagation)
             .on(link, 'click', L.DomEvent.preventDefault)
             .on(link, 'click', function() {
-                if (self._active && (map.getBounds().contains(self._event.latlng) || !self.options.setView)) {
+                if (self._active && (map.getBounds().contains(self._event.latlng) || !self.options.setView ||
+                    isOutsideMapBounds())) {
                     stopLocate();
                 } else {
                     if (self.options.setView) {
@@ -73,8 +98,13 @@ L.Control.Locate = L.Control.extend({
                         map.locate(self._locateOptions);
                     }
                     self._active = true;
+                    if (self.options.follow) {
+                        startFollowing();
+                    }
                     if (!self._event) {
                         L.DomUtil.addClass(self._container, "requesting");
+                        L.DomUtil.removeClass(self._container, "active");
+                        L.DomUtil.removeClass(self._container, "following");
                     } else {
                         visualizeLocation();
                     }
@@ -83,41 +113,77 @@ L.Control.Locate = L.Control.extend({
             .on(link, 'dblclick', L.DomEvent.stopPropagation);
 
         var onLocationFound = function (e) {
-            _log('onLocationFound');
-
-            self._active = true;
-
+            // no need to do anything if the location has not changed
             if (self._event &&
-                (self._event.latlng.lat != e.latlng.lat ||
-                 self._event.latlng.lng != e.latlng.lng)) {
-                _log('location has changed');
+                (self._event.latlng.lat == e.latlng.lat &&
+                 self._event.latlng.lng == e.latlng.lng)) {
+                return;
+            }
+
+            if (!self._active) {
+                return;
             }
 
             self._event = e;
 
-            if (self.options.follow) {
+            if (self.options.follow && self._following) {
                 self._locateOnNextLocationFound = true;
             }
 
             visualizeLocation();
         };
 
-        var visualizeLocation = function() {
-            _log('visualizeLocation,' + 'setView:' + self._locateOnNextLocationFound);
+        var startFollowing = function() {
+            self._following = true;
+            if (self.options.stopFollowingOnDrag) {
+                map.on('dragstart', stopFollowing);
+            }
+        };
 
-            var radius = self._event.accuracy / 2;
+        var stopFollowing = function() {
+            self._following = false;
+            if (self.options.stopFollowingOnDrag) {
+                map.off('dragstart', stopFollowing);
+            }
+            visualizeLocation();
+        };
 
+        var isOutsideMapBounds = function () {
+            if (self._event === undefined)
+                return false;
+            return map.options.maxBounds &&
+                !map.options.maxBounds.contains(self._event.latlng);
+        };
+
+        var visualizeLocation = function() {
+            if (self._event.accuracy === undefined)
+                self._event.accuracy = 0;
+
+            var radius = self._event.accuracy;
             if (self._locateOnNextLocationFound) {
-                map.fitBounds(self._event.bounds);
+                if (isOutsideMapBounds()) {
+                    self.options.onLocationOutsideMapBounds(self);
+                } else {
+                    map.fitBounds(self._event.bounds);
+                }
                 self._locateOnNextLocationFound = false;
             }
 
-            self._layer.clearLayers();
-
             // circle with the radius of the location's accuracy
+            var style;
             if (self.options.drawCircle) {
-                L.circle(self._event.latlng, radius, self.options.circleStyle)
-                    .addTo(self._layer);
+                if (self._following) {
+                    style = self.options.followCircleStyle;
+                } else {
+                    style = self.options.circleStyle;
+                }
+
+                if (!self._circle) {
+                    self._circle = L.circle(self._event.latlng, radius, style)
+                        .addTo(self._layer);
+                } else {
+                    self._circle.setLatLng(self._event.latlng).setRadius(radius);
+                }
             }
 
             var distance, unit;
@@ -130,43 +196,62 @@ L.Control.Locate = L.Control.extend({
             }
 
             // small inner marker
-            var t = self.options.popupText;
-            L.circleMarker(self._event.latlng, self.options.markerStyle)
-                .bindPopup(t[0] + distance + " " + unit  + t[1])
-                .addTo(self._layer);
+            var m;
+            if (self._following) {
+                m = self.options.followMarkerStyle;
+            } else {
+                m = self.options.markerStyle;
+            }
+
+            var t = self.options.strings.popup;
+            if (!self._circleMarker) {
+                self._circleMarker = L.circleMarker(self._event.latlng, m)
+                    .bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
+                    .addTo(self._layer);
+            } else {
+                self._circleMarker.setLatLng(self._event.latlng)
+                    .bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
+                    ._popup.setLatLng(self._event.latlng);
+            }
 
             if (!self._container)
                 return;
-
-            L.DomUtil.removeClass(self._container, "requesting");
-            L.DomUtil.addClass(self._container, "active");
+            if (self._following) {
+                L.DomUtil.removeClass(self._container, "requesting");
+                L.DomUtil.addClass(self._container, "active");
+                L.DomUtil.addClass(self._container, "following");
+            } else {
+                L.DomUtil.removeClass(self._container, "requesting");
+                L.DomUtil.addClass(self._container, "active");
+                L.DomUtil.removeClass(self._container, "following");
+            }
         };
 
         var resetVariables = function() {
             self._active = false;
-            self._locateOnNextLocationFound = true;
+            self._locateOnNextLocationFound = self.options.setView;
+            self._following = false;
         };
 
         resetVariables();
 
         var stopLocate = function() {
-            _log('stopLocate');
             map.stopLocate();
+            map.off('dragstart', stopFollowing);
 
             L.DomUtil.removeClass(self._container, "requesting");
             L.DomUtil.removeClass(self._container, "active");
-
+            L.DomUtil.removeClass(self._container, "following");
             resetVariables();
 
             self._layer.clearLayers();
+            self._circleMarker = undefined;
+            self._circle = undefined;
         };
 
-
         var onLocationError = function (err) {
-            _log('onLocationError');
-
-            // ignore timeout error if the location is watched
-            if (err.code==3 && this._locateOptions.watch) {
+            // ignore time out error if the location is watched
+            if (err.code == 3 && this._locateOptions.watch) {
                 return;
             }