/* 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 }, // inner marker markerStyle: { color: '#136AEC', fillColor: '#2A93EE', fillOpacity: 0.7, weight: 2, opacity: 0.9, 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, onLocationError: function(err) { // this event is called in case of any location error // that is not a time out error. alert(err.message); }, 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 container = L.DomUtil.create('div', 'control-locate'); var self = this; this._layer = new L.LayerGroup(); this._layer.addTo(map); this._event = undefined; 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 }); // 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 link = L.DomUtil.create('a', 'control-button', container); link.innerHTML = ""; 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 || isOutsideMapBounds())) { stopLocate(); } else { if (self.options.setView) { self._locateOnNextLocationFound = true; } if(!self._active) { 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(); } } }) .on(link, 'dblclick', L.DomEvent.stopPropagation); var onLocationFound = function (e) { // 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)) { return; } if (!self._active) { return; } self._event = e; if (self.options.follow && self._following) { self._locateOnNextLocationFound = true; } visualizeLocation(); }; var startFollowing = function() { self._following = true; if (self.options.stopFollowingOnDrag) { map.on('dragstart', stopFollowing); } }; 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) { if (isOutsideMapBounds()) { self.options.onLocationOutsideMapBounds(self); } else { map.fitBounds(self._event.bounds); } self._locateOnNextLocationFound = false; } // circle with the radius of the location's accuracy var style; if (self.options.drawCircle) { 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; if (self.options.metric) { distance = radius.toFixed(0); unit = "meters"; } else { distance = (radius * 3.2808399).toFixed(0); unit = "feet"; } // small inner marker 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; 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 = self.options.setView; self._following = false; }; resetVariables(); var stopLocate = function() { 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) { // ignore time out error if the location is watched if (err.code == 3 && this._locateOptions.watch) { return; } stopLocate(); self.options.onLocationError(err); }; // event hooks map.on('locationfound', onLocationFound, self); map.on('locationerror', onLocationError, self); return container; } }); L.Map.addInitHook(function () { if (this.options.locateControl) { this.locateControl = L.control.locate(); this.addControl(this.locateControl); } }); L.control.locate = function (options) { return new L.Control.Locate(options); };