8f1568fe6dfcc4b9cf6e069881fdd0aa40c519e8
[rails.git] / vendor / assets / leaflet / leaflet.locate.js
1 /*
2 Copyright (c) 2013 Dominik Moritz
3
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
6 */
7 L.Control.Locate = L.Control.extend({
8     options: {
9         position: 'topleft',
10         drawCircle: true,
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)
13         // range circle
14         circleStyle: {
15             color: '#136AEC',
16             fillColor: '#136AEC',
17             fillOpacity: 0.15,
18             weight: 2,
19             opacity: 0.5
20         },
21         // inner marker
22         markerStyle: {
23             color: '#136AEC',
24             fillColor: '#2A93EE',
25             fillOpacity: 0.7,
26             weight: 2,
27             opacity: 0.9,
28             radius: 5
29         },
30         // changes to range circle and inner marker while following
31         // it is only necessary to provide the things that should change
32         followCircleStyle: {},
33         followMarkerStyle: {
34             //color: '#FFA500',
35             //fillColor: '#FFB000'
36         },
37         circlePadding: [0, 0],
38         metric: true,
39         onLocationError: function(err) {
40             // this event is called in case of any location error
41             // that is not a time out error.
42             alert(err.message);
43         },
44         onLocationOutsideMapBounds: function(context) {
45             // this event is repeatedly called when the location changes
46             alert(context.options.strings.outsideMapBoundsMsg);
47         },
48         setView: true, // automatically sets the map view to the user's location
49         strings: {
50             title: "Show me where I am",
51             popup: "You are within {distance} {unit} from this point",
52             outsideMapBoundsMsg: "You seem located outside the boundaries of the map"
53         },
54         locateOptions: {}
55     },
56
57     onAdd: function (map) {
58         var container = L.DomUtil.create('div',
59             'leaflet-control-locate leaflet-bar leaflet-control');
60
61         var self = this;
62         this._layer = new L.LayerGroup();
63         this._layer.addTo(map);
64         this._event = undefined;
65
66         this._locateOptions = {
67             watch: true  // if you overwrite this, visualization cannot be updated
68         };
69         L.extend(this._locateOptions, this.options.locateOptions);
70         L.extend(this._locateOptions, {
71             setView: false // have to set this to false because we have to
72                            // do setView manually
73         });
74
75         // extend the follow marker style and circle from the normal style
76         var tmp = {};
77         L.extend(tmp, this.options.markerStyle, this.options.followMarkerStyle);
78         this.options.followMarkerStyle = tmp;
79         tmp = {};
80         L.extend(tmp, this.options.circleStyle, this.options.followCircleStyle);
81         this.options.followCircleStyle = tmp;
82
83         var link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container);
84         link.href = '#';
85         link.title = this.options.strings.title;
86
87         L.DomEvent
88             .on(link, 'click', L.DomEvent.stopPropagation)
89             .on(link, 'click', L.DomEvent.preventDefault)
90             .on(link, 'click', function() {
91                 if (self._active && (map.getBounds().contains(self._event.latlng) || !self.options.setView ||
92                     isOutsideMapBounds())) {
93                     stopLocate();
94                 } else {
95                     locate();
96                 }
97             })
98             .on(link, 'dblclick', L.DomEvent.stopPropagation);
99
100         var locate = function () {
101             if (self.options.setView) {
102                 self._locateOnNextLocationFound = true;
103             }
104             if(!self._active) {
105                 map.locate(self._locateOptions);
106             }
107             self._active = true;
108             if (self.options.follow) {
109                 startFollowing();
110             }
111             if (!self._event) {
112                 L.DomUtil.addClass(self._container, "requesting");
113                 L.DomUtil.removeClass(self._container, "active");
114                 L.DomUtil.removeClass(self._container, "following");
115             } else {
116                 visualizeLocation();
117             }
118         };
119
120         var onLocationFound = function (e) {
121             // no need to do anything if the location has not changed
122             if (self._event &&
123                 (self._event.latlng.lat === e.latlng.lat &&
124                  self._event.latlng.lng === e.latlng.lng &&
125                  self._event.accuracy === e.accuracy)) {
126                 return;
127             }
128
129             if (!self._active) {
130                 return;
131             }
132
133             self._event = e;
134
135             if (self.options.follow && self._following) {
136                 self._locateOnNextLocationFound = true;
137             }
138
139             visualizeLocation();
140         };
141
142         var startFollowing = function() {
143             map.fire('startfollowing');
144             self._following = true;
145             if (self.options.stopFollowingOnDrag) {
146                 map.on('dragstart', stopFollowing);
147             }
148         };
149
150         var stopFollowing = function() {
151             map.fire('stopfollowing');
152             self._following = false;
153             if (self.options.stopFollowingOnDrag) {
154                 map.off('dragstart', stopFollowing);
155             }
156             visualizeLocation();
157         };
158
159         var isOutsideMapBounds = function () {
160             if (self._event === undefined)
161                 return false;
162             return map.options.maxBounds &&
163                 !map.options.maxBounds.contains(self._event.latlng);
164         };
165
166         var visualizeLocation = function() {
167             if (self._event.accuracy === undefined)
168                 self._event.accuracy = 0;
169
170             var radius = self._event.accuracy;
171             if (self._locateOnNextLocationFound) {
172                 if (isOutsideMapBounds()) {
173                     self.options.onLocationOutsideMapBounds(self);
174                 } else {
175                     map.fitBounds(self._event.bounds, { padding: self.options.circlePadding });
176                 }
177                 self._locateOnNextLocationFound = false;
178             }
179
180             // circle with the radius of the location's accuracy
181             var style, o;
182             if (self.options.drawCircle) {
183                 if (self._following) {
184                     style = self.options.followCircleStyle;
185                 } else {
186                     style = self.options.circleStyle;
187                 }
188
189                 if (!self._circle) {
190                     self._circle = L.circle(self._event.latlng, radius, style)
191                         .addTo(self._layer);
192                 } else {
193                     self._circle.setLatLng(self._event.latlng).setRadius(radius);
194                     for (o in style) {
195                         self._circle.options[o] = style[o];
196                     }
197                 }
198             }
199
200             var distance, unit;
201             if (self.options.metric) {
202                 distance = radius.toFixed(0);
203                 unit = "meters";
204             } else {
205                 distance = (radius * 3.2808399).toFixed(0);
206                 unit = "feet";
207             }
208
209             // small inner marker
210             var mStyle;
211             if (self._following) {
212                 mStyle = self.options.followMarkerStyle;
213             } else {
214                 mStyle = self.options.markerStyle;
215             }
216
217             var t = self.options.strings.popup;
218             if (!self._circleMarker) {
219                 self._circleMarker = L.circleMarker(self._event.latlng, mStyle)
220                     .bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
221                     .addTo(self._layer);
222             } else {
223                 self._circleMarker.setLatLng(self._event.latlng)
224                     .bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
225                     ._popup.setLatLng(self._event.latlng);
226                 for (o in mStyle) {
227                     self._circleMarker.options[o] = mStyle[o];
228                 }
229             }
230
231             if (!self._container)
232                 return;
233             if (self._following) {
234                 L.DomUtil.removeClass(self._container, "requesting");
235                 L.DomUtil.addClass(self._container, "active");
236                 L.DomUtil.addClass(self._container, "following");
237             } else {
238                 L.DomUtil.removeClass(self._container, "requesting");
239                 L.DomUtil.addClass(self._container, "active");
240                 L.DomUtil.removeClass(self._container, "following");
241             }
242         };
243
244         var resetVariables = function() {
245             self._active = false;
246             self._locateOnNextLocationFound = self.options.setView;
247             self._following = false;
248         };
249
250         resetVariables();
251
252         var stopLocate = function() {
253             map.stopLocate();
254             map.off('dragstart', stopFollowing);
255
256             L.DomUtil.removeClass(self._container, "requesting");
257             L.DomUtil.removeClass(self._container, "active");
258             L.DomUtil.removeClass(self._container, "following");
259             resetVariables();
260
261             self._layer.clearLayers();
262             self._circleMarker = undefined;
263             self._circle = undefined;
264         };
265
266         var onLocationError = function (err) {
267             // ignore time out error if the location is watched
268             if (err.code == 3 && this._locateOptions.watch) {
269                 return;
270             }
271
272             stopLocate();
273             self.options.onLocationError(err);
274         };
275
276         // event hooks
277         map.on('locationfound', onLocationFound, self);
278         map.on('locationerror', onLocationError, self);
279
280         // make locate functions available to outside world
281         this.locate = locate;
282         this.stopLocate = stopLocate;
283         this.stopFollowing = stopFollowing;
284
285         return container;
286     }
287 });
288
289 L.Map.addInitHook(function () {
290     if (this.options.locateControl) {
291         this.locateControl = L.control.locate();
292         this.addControl(this.locateControl);
293     }
294 });
295
296 L.control.locate = function (options) {
297     return new L.Control.Locate(options);
298 };