]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.locationfilter.js
Update Leaflet
[rails.git] / vendor / assets / leaflet / leaflet.locationfilter.js
1 /*
2  * Leaflet.locationfilter - leaflet location filter plugin
3  * Copyright (C) 2012, Tripbirds.com
4  * http://tripbirds.com
5  *
6  * Licensed under the MIT License.
7  *
8  * Date: 2012-09-24
9  * Version: 0.1
10  */
11 L.LatLngBounds.prototype.modify = function(map, amount) {
12     var sw = this.getSouthWest(),
13         ne = this.getNorthEast(),
14         swPoint = map.latLngToLayerPoint(sw),
15         nePoint = map.latLngToLayerPoint(ne);
16
17     sw = map.layerPointToLatLng(new L.Point(swPoint.x-amount, swPoint.y+amount));
18     ne = map.layerPointToLatLng(new L.Point(nePoint.x+amount, nePoint.y-amount));
19     
20     return new L.LatLngBounds(sw, ne);
21 };
22
23 L.Control.Button = L.Class.extend({
24     initialize: function(options) {
25         L.Util.setOptions(this, options);
26     },
27
28     addTo: function(container) {
29         container.addButton(this);
30         return this;
31     },
32     
33     onAdd: function (buttonContainer) {
34         this._buttonContainer = buttonContainer;
35         this._button = L.DomUtil.create('a', this.options.className, this._buttonContainer.getContainer());
36         this._button.href = '#';
37         this.setText(this.options.text);
38
39         var that = this;
40         this._onClick = function(event) {
41             that.options.onClick.call(that, event);
42         };
43
44         L.DomEvent
45             .on(this._button, 'click', L.DomEvent.stopPropagation)
46             .on(this._button, 'mousedown', L.DomEvent.stopPropagation)
47             .on(this._button, 'dblclick', L.DomEvent.stopPropagation)
48             .on(this._button, 'click', L.DomEvent.preventDefault)
49             .on(this._button, 'click', this._onClick, this);
50     },
51
52     remove: function() {
53         L.DomEvent.off(this._button, "click", this._onClick);
54         this._buttonContainer.getContainer().removeChild(this._button);
55     },
56
57     setText: function(text) {
58         this._button.title = text;
59         this._button.innerHTML = text;
60     }
61 });
62
63 L.Control.ButtonContainer = L.Control.extend({
64     options: {
65         position: 'topleft'
66     },
67
68     getContainer: function() {
69         if (!this._container) {
70             this._container = L.DomUtil.create('div', this.options.className);
71         }
72         return this._container;
73     },
74
75     onAdd: function (map) {
76         this._map = map;
77         return this.getContainer();
78     },
79
80     addButton: function(button) {
81         button.onAdd(this);
82     },
83
84     addClass: function(className) {
85         L.DomUtil.addClass(this.getContainer(), className);
86     },
87
88     removeClass: function(className) {
89         L.DomUtil.removeClass(this.getContainer(), className);
90     }
91 });
92
93 L.LocationFilter = L.Class.extend({
94     includes: L.Mixin.Events,
95
96     options: {
97         enableButton: {
98             enableText: "Select area",
99             disableText: "Remove selection"
100         },
101         adjustButton: {
102             text: "Select area within current zoom"
103         }
104     },
105
106     initialize: function(options) {
107         L.Util.setOptions(this, options);
108     },
109
110     addTo: function(map) {
111         map.addLayer(this);
112         return this;
113     },
114
115     onAdd: function(map) {
116         this._map = map;
117         this._layer = new L.LayerGroup();
118         this._initializeButtonContainer();
119
120         if (this.options.enable) {
121             this.enable();
122         }
123     },
124
125     onRemove: function(map) {
126         this.disable();
127         this._buttonContainer.removeFrom(map);
128     },
129
130     /* Get the current filter bounds */
131     getBounds: function() { 
132         return new L.LatLngBounds(this._sw, this._ne); 
133     },
134
135     setBounds: function(bounds) {
136         this._nw = bounds.getNorthWest();
137         this._ne = bounds.getNorthEast();
138         this._sw = bounds.getSouthWest();
139         this._se = bounds.getSouthEast();
140         this._draw();
141         this.fire("change", {bounds: bounds});
142     },
143
144     isEnabled: function() {
145         return this._enabled;
146     },
147
148     /* Draw a rectangle */
149     _drawRectangle: function(bounds, options) {
150         options = options || {};
151         var defaultOptions = {
152             stroke: false,
153             fill: true,
154             fillColor: "black",
155             fillOpacity: 0.3,
156             clickable: false
157         };
158         options = L.Util.extend(defaultOptions, options);
159         var rect = new L.Rectangle(bounds, options);
160         rect.addTo(this._layer);
161         return rect;
162     },
163
164     /* Draw a draggable marker */
165     _drawImageMarker: function(point, options) {
166         var marker = new L.Marker(point, {
167             icon: new L.DivIcon({
168                 iconAnchor: options.anchor,
169                 iconSize: options.size,
170                 className: options.className
171             }),
172             draggable: true
173         });
174         marker.addTo(this._layer);
175         return marker;
176     },
177
178     /* Draw a move marker. Sets up drag listener that updates the
179        filter corners and redraws the filter when the move marker is
180        moved */
181     _drawMoveMarker: function(point) {
182         var that = this;
183         this._moveMarker = this._drawImageMarker(point, {
184             "className": "location-filter move-marker",
185             "anchor": [-10, -10],
186             "size": [13,13]
187         });
188         this._moveMarker.on('drag', function(e) {
189             var markerPos = that._moveMarker.getLatLng(),
190                 latDelta = markerPos.lat-that._nw.lat,
191                 lngDelta = markerPos.lng-that._nw.lng;
192             that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta, true);
193             that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta, true);
194             that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta, true);
195             that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta, true);
196             that._draw();
197         });
198         this._setupDragendListener(this._moveMarker);
199         return this._moveMarker;
200     },
201
202     /* Draw a resize marker */
203     _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
204         return this._drawImageMarker(point, {
205             "className": "location-filter resize-marker",
206             "anchor": [7, 6],
207             "size": [13, 12] 
208         });
209     },
210
211     /* Track moving of the given resize marker and update the markers
212        given in options.moveAlong to match the position of the moved
213        marker. Update filter corners and redraw the filter */
214     _setupResizeMarkerTracking: function(marker, options) {
215         var that = this;
216         marker.on('drag', function(e) {
217             var curPosition = marker.getLatLng(),
218                 latMarker = options.moveAlong.lat,
219                 lngMarker = options.moveAlong.lng;
220             // Move follower markers when this marker is moved
221             latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng, true));
222             lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng, true));
223             // Sort marker positions in nw, ne, sw, se order
224             var corners = [that._nwMarker.getLatLng(), 
225                            that._neMarker.getLatLng(), 
226                            that._swMarker.getLatLng(), 
227                            that._seMarker.getLatLng()];
228             corners.sort(function(a, b) {
229                 if (a.lat != b.lat)
230                     return b.lat-a.lat;
231                 else
232                     return a.lng-b.lng;
233             });
234             // Update corner points and redraw everything except the resize markers
235             that._nw = corners[0];
236             that._ne = corners[1];
237             that._sw = corners[2];
238             that._se = corners[3];
239             that._draw({repositionResizeMarkers: false});
240         });
241         this._setupDragendListener(marker);
242     },
243
244     /* Emit a change event whenever dragend is triggered on the
245        given marker */
246     _setupDragendListener: function(marker) {
247         var that = this;
248         marker.on('dragend', function(e) {
249             that.fire("change", {bounds: that.getBounds()});
250         });
251     },
252
253     /* Create bounds for the mask rectangles and the location
254        filter rectangle */
255     _calculateBounds: function() {
256         var mapBounds = this._map.getBounds(),
257             outerBounds = new L.LatLngBounds(
258                 new L.LatLng(mapBounds.getSouthWest().lat-0.1,
259                              mapBounds.getSouthWest().lng-0.1, true),
260                 new L.LatLng(mapBounds.getNorthEast().lat+0.1,
261                              mapBounds.getNorthEast().lng+0.1, true)
262             );
263
264         // The south west and north east points of the mask */
265         this._osw = outerBounds.getSouthWest();
266         this._one = outerBounds.getNorthEast();
267
268         // Bounds for the mask rectangles
269         this._northBounds = new L.LatLngBounds(new L.LatLng(this._ne.lat, this._osw.lng, true), this._one);
270         this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng, true), this._nw);
271         this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng, true));
272         this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng, true));
273     },
274
275     /* Initializes rectangles and markers */
276     _initialDraw: function() {
277         if (this._initialDrawCalled) {
278             return;
279         }
280
281         // Calculate filter bounds
282         this._calculateBounds();
283
284         // Create rectangles
285         this._northRect = this._drawRectangle(this._northBounds);
286         this._westRect = this._drawRectangle(this._westBounds);
287         this._eastRect = this._drawRectangle(this._eastBounds);
288         this._southRect = this._drawRectangle(this._southBounds);
289         this._innerRect = this._drawRectangle(this.getBounds(), {
290             fillColor: "transparent",
291             stroke: true,
292             color: "white",
293             weight: 1,
294             opacity: 0.9
295         });
296
297         // Create resize markers
298         this._nwMarker = this._drawResizeMarker(this._nw);
299         this._neMarker = this._drawResizeMarker(this._ne);
300         this._swMarker = this._drawResizeMarker(this._sw);
301         this._seMarker = this._drawResizeMarker(this._se);
302
303         // Setup tracking of resize markers. Each marker has pair of
304         // follower markers that must be moved whenever the marker is
305         // moved. For example, whenever the north west resize marker
306         // moves, the south west marker must move along on the x-axis
307         // and the north east marker must move on the y axis
308         this._setupResizeMarkerTracking(this._nwMarker, {moveAlong: {lat: this._neMarker, lng: this._swMarker}});
309         this._setupResizeMarkerTracking(this._neMarker, {moveAlong: {lat: this._nwMarker, lng: this._seMarker}});
310         this._setupResizeMarkerTracking(this._swMarker, {moveAlong: {lat: this._seMarker, lng: this._nwMarker}});
311         this._setupResizeMarkerTracking(this._seMarker, {moveAlong: {lat: this._swMarker, lng: this._neMarker}});
312
313         // Create move marker
314         this._moveMarker = this._drawMoveMarker(this._nw);
315
316         this._initialDrawCalled = true;
317     },
318
319     /* Reposition all rectangles and markers to the current filter bounds. */    
320     _draw: function(options) {
321         options = L.Util.extend({repositionResizeMarkers: true}, options);
322
323         // Calculate filter bounds
324         this._calculateBounds();
325
326         // Reposition rectangles
327         this._northRect.setBounds(this._northBounds);
328         this._westRect.setBounds(this._westBounds);
329         this._eastRect.setBounds(this._eastBounds);
330         this._southRect.setBounds(this._southBounds);
331         this._innerRect.setBounds(this.getBounds());
332
333         // Reposition resize markers
334         if (options.repositionResizeMarkers) {
335             this._nwMarker.setLatLng(this._nw);
336             this._neMarker.setLatLng(this._ne);
337             this._swMarker.setLatLng(this._sw);
338             this._seMarker.setLatLng(this._se);
339         }
340
341         // Reposition the move marker
342         this._moveMarker.setLatLng(this._nw);
343     }, 
344
345     /* Adjust the location filter to the current map bounds */
346     _adjustToMap: function() {
347         this.setBounds(this._map.getBounds());
348         this._map.zoomOut();
349     },
350
351     /* Enable the location filter */
352     enable: function() {
353         if (this._enabled) {
354             return;
355         }
356
357         // Initialize corners
358         var bounds;
359         if (this._sw && this._ne) {
360             bounds = new L.LatLngBounds(this._sw, this._ne);
361         } else if (this.options.bounds) {
362             bounds = this.options.bounds;
363         } else {
364             bounds = this._map.getBounds();
365         }
366         this._map.invalidateSize();
367         this._nw = bounds.getNorthWest();
368         this._ne = bounds.getNorthEast();
369         this._sw = bounds.getSouthWest();
370         this._se = bounds.getSouthEast();
371             
372
373         // Update buttons
374         this._buttonContainer.addClass("enabled");
375
376         if (this._enableButton) {
377             this._enableButton.setText(this.options.enableButton.disableText);
378         }
379
380         if (this.options.adjustButton) {
381             this._createAdjustButton();
382         }
383         
384         // Draw filter
385         this._initialDraw();
386         this._draw();
387
388         // Set up map move event listener
389         var that = this;
390         this._moveHandler = function() {
391             that._draw();
392         };
393         this._map.on("move", this._moveHandler);
394
395         // Add the filter layer to the map
396         this._layer.addTo(this._map);
397         
398         // Zoom out the map if necessary
399         var mapBounds = this._map.getBounds();
400         bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
401         if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
402             this._map.fitBounds(bounds);
403         }
404
405         this._enabled = true;
406         
407         // Fire the enabled event
408         this.fire("enabled");
409     },
410
411     /* Disable the location filter */
412     disable: function() {
413         if (!this._enabled) {
414             return;
415         }
416
417         // Update buttons
418         this._buttonContainer.removeClass("enabled");
419
420         if (this._enableButton) {
421             this._enableButton.setText(this.options.enableButton.enableText);
422         }
423
424         if (this._adjustButton) {
425             this._adjustButton.remove();
426         }
427
428         // Remove event listener
429         this._map.off("move", this._moveHandler);
430
431         // Remove rectangle layer from map
432         this._map.removeLayer(this._layer);
433
434         this._enabled = false;
435
436         // Fire the disabled event
437         this.fire("disabled");
438     },
439
440     /* Create a button that allows the user to adjust the location
441        filter to the current zoom */
442     _createAdjustButton: function() {
443         var that = this;
444         this._adjustButton = new L.Control.Button({
445             className: "adjust-button",
446             text: this.options.adjustButton.text,
447             
448             onClick: function(event) {
449                 that._adjustToMap();
450                 that.fire("adjustToZoomClick");
451             }
452         }).addTo(this._buttonContainer);
453     },
454
455     /* Create the location filter button container and the button that
456        toggles the location filter */
457     _initializeButtonContainer: function() {
458         var that = this;
459         this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
460
461         if (this.options.enableButton) {
462             this._enableButton = new L.Control.Button({
463                 className: "enable-button",
464                 text: this.options.enableButton.enableText,
465
466                 onClick: function(event) {
467                     if (!that._enabled) {
468                         // Enable the location filter
469                         that.enable();
470                         that.fire("enableClick");
471                     } else {
472                         // Disable the location filter
473                         that.disable();
474                         that.fire("disableClick");
475                     }
476                 }
477             }).addTo(this._buttonContainer);
478         }
479
480         if (this.options.enableButton || this.options.adjustButton) {
481           this._buttonContainer.addTo(this._map);
482         }
483     }
484 });