]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.locate.js
822b24da23daa0490be3a2bd62bee181f09da3c5
[rails.git] / vendor / assets / leaflet / leaflet.locate.js
1 /*
2 Copyright (c) 2014 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         icon: 'icon-location',  // icon-location or icon-direction
38         iconLoading: 'icon-spinner animate-spin',
39         circlePadding: [0, 0],
40         metric: true,
41         onLocationError: function(err) {
42             // this event is called in case of any location error
43             // that is not a time out error.
44             alert(err.message);
45         },
46         onLocationOutsideMapBounds: function(control) {
47             // this event is repeatedly called when the location changes
48             control.stopLocate();
49             alert(context.options.strings.outsideMapBoundsMsg);
50         },
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,
54         strings: {
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"
58         },
59         locateOptions: {
60             maxZoom: Infinity,
61             watch: true  // if you overwrite this, visualization cannot be updated
62         }
63     },
64
65     onAdd: function (map) {
66         var container = L.DomUtil.create('div', 'control-locate');
67
68         var self = this;
69         this._layer = new L.LayerGroup();
70         this._layer.addTo(map);
71         this._event = undefined;
72
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
78         });
79
80         // extend the follow marker style and circle from the normal style
81         var tmp = {};
82         L.extend(tmp, this.options.markerStyle, this.options.followMarkerStyle);
83         this.options.followMarkerStyle = tmp;
84         tmp = {};
85         L.extend(tmp, this.options.circleStyle, this.options.followCircleStyle);
86         this.options.followCircleStyle = tmp;
87
88         var link = L.DomUtil.create('a', 'control-button ' + this.options.icon, container);
89         link.innerHTML = "<span class='icon geolocate'></span>";
90         link.href = '#';
91         link.title = this.options.strings.title;
92
93         L.DomEvent
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())) {
99                     stopLocate();
100                 } else {
101                     locate();
102                 }
103             })
104             .on(link, 'dblclick', L.DomEvent.stopPropagation);
105
106         var locate = function () {
107             if (self.options.setView) {
108                 self._locateOnNextLocationFound = true;
109             }
110             if(!self._active) {
111                 map.locate(self._locateOptions);
112             }
113             self._active = true;
114             if (self.options.follow) {
115                 startFollowing();
116             }
117             if (!self._event) {
118                 setClasses('requesting');
119             } else {
120                 visualizeLocation();
121             }
122         };
123
124         var onLocationFound = function (e) {
125             // no need to do anything if the location has not changed
126             if (self._event &&
127                 (self._event.latlng.lat === e.latlng.lat &&
128                  self._event.latlng.lng === e.latlng.lng &&
129                  self._event.accuracy === e.accuracy)) {
130                 return;
131             }
132
133             if (!self._active) {
134                 return;
135             }
136
137             self._event = e;
138
139             if (self.options.follow && self._following) {
140                 self._locateOnNextLocationFound = true;
141             }
142
143             visualizeLocation();
144         };
145
146         var startFollowing = function() {
147             map.fire('startfollowing', self);
148             self._following = true;
149             if (self.options.stopFollowingOnDrag) {
150                 map.on('dragstart', stopFollowing);
151             }
152         };
153
154         var stopFollowing = function() {
155             map.fire('stopfollowing', self);
156             self._following = false;
157             if (self.options.stopFollowingOnDrag) {
158                 map.off('dragstart', stopFollowing);
159             }
160             visualizeLocation();
161         };
162
163         var isOutsideMapBounds = function () {
164             if (self._event === undefined)
165                 return false;
166             return map.options.maxBounds &&
167                 !map.options.maxBounds.contains(self._event.latlng);
168         };
169
170         var visualizeLocation = function() {
171             if (self._event.accuracy === undefined)
172                 self._event.accuracy = 0;
173
174             var radius = self._event.accuracy;
175             if (self._locateOnNextLocationFound) {
176                 if (isOutsideMapBounds()) {
177                     self.options.onLocationOutsideMapBounds(self);
178                 } else {
179                     map.fitBounds(self._event.bounds, {
180                         padding: self.options.circlePadding,
181                         maxZoom: self.options.keepCurrentZoomLevel ? map.getZoom() : self._locateOptions.maxZoom
182                     });
183                 }
184                 self._locateOnNextLocationFound = false;
185             }
186
187             // circle with the radius of the location's accuracy
188             var style, o;
189             if (self.options.drawCircle) {
190                 if (self._following) {
191                     style = self.options.followCircleStyle;
192                 } else {
193                     style = self.options.circleStyle;
194                 }
195
196                 if (!self._circle) {
197                     self._circle = L.circle(self._event.latlng, radius, style)
198                         .addTo(self._layer);
199                 } else {
200                     self._circle.setLatLng(self._event.latlng).setRadius(radius);
201                     for (o in style) {
202                         self._circle.options[o] = style[o];
203                     }
204                 }
205             }
206
207             var distance, unit;
208             if (self.options.metric) {
209                 distance = radius.toFixed(0);
210                 unit = "meters";
211             } else {
212                 distance = (radius * 3.2808399).toFixed(0);
213                 unit = "feet";
214             }
215
216             // small inner marker
217             var mStyle;
218             if (self._following) {
219                 mStyle = self.options.followMarkerStyle;
220             } else {
221                 mStyle = self.options.markerStyle;
222             }
223
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}))
228                     .addTo(self._layer);
229             } else {
230                 self._circleMarker.setLatLng(self._event.latlng)
231                     .bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
232                     ._popup.setLatLng(self._event.latlng);
233                 for (o in mStyle) {
234                     self._circleMarker.options[o] = mStyle[o];
235                 }
236             }
237
238             if (!self._container)
239                 return;
240             if (self._following) {
241                 setClasses('following');
242             } else {
243                 setClasses('active');
244             }
245         };
246
247         var setClasses = function(state) {
248             if (state == 'requesting') {
249                 L.DomUtil.removeClasses(self._container, "active following");
250                 L.DomUtil.addClasses(self._container, "requesting");
251
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");
257
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");
263
264                 L.DomUtil.removeClasses(link, self.options.iconLoading);
265                 L.DomUtil.addClasses(link, self.options.icon);
266             }
267         }
268
269         var resetVariables = function() {
270             self._active = false;
271             self._locateOnNextLocationFound = self.options.setView;
272             self._following = false;
273         };
274
275         resetVariables();
276
277         var stopLocate = function() {
278             map.stopLocate();
279             map.off('dragstart', stopFollowing);
280             if (self.options.follow && self._following) {
281                 stopFollowing();
282             }
283
284             L.DomUtil.removeClass(self._container, "requesting");
285             L.DomUtil.removeClass(self._container, "active");
286             L.DomUtil.removeClass(self._container, "following");
287             resetVariables();
288
289             self._layer.clearLayers();
290             self._circleMarker = undefined;
291             self._circle = undefined;
292         };
293
294         var onLocationError = function (err) {
295             // ignore time out error if the location is watched
296             if (err.code == 3 && self._locateOptions.watch) {
297                 return;
298             }
299
300             stopLocate();
301             self.options.onLocationError(err);
302         };
303
304         // event hooks
305         map.on('locationfound', onLocationFound, self);
306         map.on('locationerror', onLocationError, self);
307
308         // make locate functions available to outside world
309         this.locate = locate;
310         this.stopLocate = stopLocate;
311         this.stopFollowing = stopFollowing;
312
313         return container;
314     }
315 });
316
317 L.Map.addInitHook(function () {
318     if (this.options.locateControl) {
319         this.locateControl = L.control.locate();
320         this.addControl(this.locateControl);
321     }
322 });
323
324 L.control.locate = function (options) {
325     return new L.Control.Locate(options);
326 };
327
328 (function(){
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);
335     });
336   };
337
338   L.DomUtil.addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); }
339   L.DomUtil.removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); }
340 })();