]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.locationfilter.js
Restore maxFeatures check
[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     options: {
95         enableButton: {
96             enableText: "Select area",
97             disableText: "Remove selection"
98         },
99         adjustButton: {
100             text: "Select area within current zoom"
101         }
102     },
103
104     initialize: function(options) {
105         L.Util.setOptions(this, options);
106     },
107
108     addTo: function(map) {
109         map.addLayer(this);
110         return this;
111     },
112
113     onAdd: function(map) {
114         this._map = map;
115         this._layer = new L.LayerGroup();
116         this._initializeButtonContainer();
117
118         if (this.options.enable) {
119             this.enable();
120         }
121     },
122
123     onRemove: function(map) {
124         this.disable();
125         this._buttonContainer.removeFrom(map);
126     },
127
128     /* Get the current filter bounds */
129     getBounds: function() { 
130         return new L.LatLngBounds(this._sw, this._ne); 
131     },
132
133     setBounds: function(bounds) {
134         this._nw = bounds.getNorthWest();
135         this._ne = bounds.getNorthEast();
136         this._sw = bounds.getSouthWest();
137         this._se = bounds.getSouthEast();
138         this._draw();
139         this._callCallback("onChange");
140     },
141
142     isEnabled: function() {
143         return this._enabled;
144     },
145
146     /* Draw a rectangle */
147     _drawRectangle: function(bounds, options) {
148         options = options || {};
149         var defaultOptions = {
150             stroke: false,
151             fill: true,
152             fillColor: "black",
153             fillOpacity: 0.3,
154             clickable: false
155         };
156         options = L.Util.extend(defaultOptions, options);
157         var rect = new L.Rectangle(bounds, options);
158         rect.addTo(this._layer);
159         return rect;
160     },
161
162     /* Draw a draggable marker */
163     _drawImageMarker: function(point, options) {
164         var marker = new L.Marker(point, {
165             icon: new L.DivIcon({
166                 iconAnchor: options.anchor,
167                 iconSize: options.size,
168                 className: options.className
169             }),
170             draggable: true
171         });
172         marker.addTo(this._layer);
173         return marker;
174     },
175
176     /* Draw a move marker. Sets up drag listener that updates the
177        filter corners and redraws the filter when the move marker is
178        moved */
179     _drawMoveMarker: function(point) {
180         var that = this;
181         this._moveMarker = this._drawImageMarker(point, {
182             "className": "location-filter move-marker",
183             "anchor": [-10, -10],
184             "size": [13,13]
185         });
186         this._moveMarker.on('drag', function(e) {
187             var markerPos = that._moveMarker.getLatLng(),
188                 latDelta = markerPos.lat-that._nw.lat,
189                 lngDelta = markerPos.lng-that._nw.lng;
190             that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta);
191             that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta);
192             that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta);
193             that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta);
194             that._draw();
195         });
196         this._setupDragendListener(this._moveMarker);
197         return this._moveMarker;
198     },
199
200     /* Draw a resize marker */
201     _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
202         return this._drawImageMarker(point, {
203             "className": "location-filter resize-marker",
204             "anchor": [7, 6],
205             "size": [13, 12] 
206         });
207     },
208
209     /* Track moving of the given resize marker and update the markers
210        given in options.moveAlong to match the position of the moved
211        marker. Update filter corners and redraw the filter */
212     _setupResizeMarkerTracking: function(marker, options) {
213         var that = this;
214         marker.on('drag', function(e) {
215             var curPosition = marker.getLatLng(),
216                 latMarker = options.moveAlong.lat,
217                 lngMarker = options.moveAlong.lng;
218             // Move follower markers when this marker is moved
219             latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng));
220             lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng));
221             // Sort marker positions in nw, ne, sw, se order
222             var corners = [that._nwMarker.getLatLng(), 
223                            that._neMarker.getLatLng(), 
224                            that._swMarker.getLatLng(), 
225                            that._seMarker.getLatLng()];
226             corners.sort(function(a, b) {
227                 if (a.lat != b.lat)
228                     return b.lat-a.lat;
229                 else
230                     return a.lng-b.lng;
231             });
232             // Update corner points and redraw everything except the resize markers
233             that._nw = corners[0];
234             that._ne = corners[1];
235             that._sw = corners[2];
236             that._se = corners[3];
237             that._draw({repositionResizeMarkers: false});
238         });
239         this._setupDragendListener(marker);
240     },
241
242     /* Call the callback (given by name) if it was supplied in options */
243     _callCallback: function(callbackName) {
244         if (this.options[callbackName]) {
245             this.options[callbackName](this.getBounds());
246         }
247     },
248
249     /* Call the onChange callback whenever dragend is triggered on the
250        given marker */
251     _setupDragendListener: function(marker) {
252         if (this.options.onChange) {
253             var that = this;
254             marker.on('dragend', function(e) {
255                 that._callCallback("onChange");
256             });
257         }
258     },
259
260     /* Create bounds for the mask rectangles and the location
261        filter rectangle */
262     _calculateBounds: function() {
263         var mapBounds = this._map.getBounds(),
264             outerBounds = new L.LatLngBounds(
265                 new L.LatLng(mapBounds.getSouthWest().lat-0.1,
266                              mapBounds.getSouthWest().lng-0.1),
267                 new L.LatLng(mapBounds.getNorthEast().lat+0.1,
268                              mapBounds.getNorthEast().lng+0.1)
269             );
270
271         // The south west and north east points of the mask */
272         this._osw = outerBounds.getSouthWest();
273         this._one = outerBounds.getNorthEast();
274
275         // Bounds for the mask rectangles
276         this._northBounds = new L.LatLngBounds(new L.LatLng(this._ne.lat, this._osw.lng), this._one),
277         this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng), this._nw),
278         this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng)),
279         this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng));
280     },
281
282     /* Initializes rectangles and markers */
283     _initialDraw: function() {
284         if (this._initialDrawCalled) {
285             return;
286         }
287
288         // Calculate filter bounds
289         this._calculateBounds();
290
291         // Create rectangles
292         this._northRect = this._drawRectangle(this._northBounds);
293         this._westRect = this._drawRectangle(this._westBounds);
294         this._eastRect = this._drawRectangle(this._eastBounds);
295         this._southRect = this._drawRectangle(this._southBounds);
296         this._innerRect = this._drawRectangle(this.getBounds(), {
297             fillColor: "transparent",
298             stroke: true,
299             color: "white",
300             weight: 1,
301             opacity: 0.9
302         });
303
304         // Create resize markers
305         this._nwMarker = this._drawResizeMarker(this._nw);
306         this._neMarker = this._drawResizeMarker(this._ne);
307         this._swMarker = this._drawResizeMarker(this._sw);
308         this._seMarker = this._drawResizeMarker(this._se);
309
310         // Setup tracking of resize markers. Each marker has pair of
311         // follower markers that must be moved whenever the marker is
312         // moved. For example, whenever the north west resize marker
313         // moves, the south west marker must move along on the x-axis
314         // and the north east marker must move on the y axis
315         this._setupResizeMarkerTracking(this._nwMarker, {moveAlong: {lat: this._neMarker, lng: this._swMarker}});
316         this._setupResizeMarkerTracking(this._neMarker, {moveAlong: {lat: this._nwMarker, lng: this._seMarker}});
317         this._setupResizeMarkerTracking(this._swMarker, {moveAlong: {lat: this._seMarker, lng: this._nwMarker}});
318         this._setupResizeMarkerTracking(this._seMarker, {moveAlong: {lat: this._swMarker, lng: this._neMarker}});
319
320         // Create move marker
321         this._moveMarker = this._drawMoveMarker(this._nw);
322
323         this._initialDrawCalled = true;
324     },
325
326     /* Reposition all rectangles and markers to the current filter bounds. */    
327     _draw: function(options) {
328         options = L.Util.extend({repositionResizeMarkers: true}, options);
329
330         // Calculate filter bounds
331         this._calculateBounds();
332
333         // Reposition rectangles
334         this._northRect.setBounds(this._northBounds);
335         this._westRect.setBounds(this._westBounds);
336         this._eastRect.setBounds(this._eastBounds);
337         this._southRect.setBounds(this._southBounds);
338         this._innerRect.setBounds(this.getBounds());
339
340         // Reposition resize markers
341         if (options.repositionResizeMarkers) {
342             this._nwMarker.setLatLng(this._nw);
343             this._neMarker.setLatLng(this._ne);
344             this._swMarker.setLatLng(this._sw);
345             this._seMarker.setLatLng(this._se);
346         }
347
348         // Reposition the move marker
349         this._moveMarker.setLatLng(this._nw);
350     }, 
351
352     /* Adjust the location filter to the current map bounds */
353     _adjustToMap: function() {
354         this.setBounds(this._map.getBounds());
355         this._map.zoomOut();
356     },
357
358     /* Enable the location filter */
359     enable: function() {
360         if (this._enabled) {
361             return;
362         }
363
364         // Initialize corners
365         var bounds;
366         if (this._sw && this._ne) {
367             bounds = new L.LatLngBounds(this._sw, this._ne);
368         } else if (this.options.bounds) {
369             bounds = this.options.bounds;
370         } else {
371             bounds = this._map.getBounds();
372         }
373         this._map.invalidateSize();
374         this._nw = bounds.getNorthWest();
375         this._ne = bounds.getNorthEast();
376         this._sw = bounds.getSouthWest();
377         this._se = bounds.getSouthEast();
378             
379
380         // Update buttons
381         this._buttonContainer.addClass("enabled");
382
383         if (this._enableButton) {
384             this._enableButton.setText(this.options.enableButton.disableText);
385         }
386
387         if (this.options.adjustButton) {
388             this._createAdjustButton();
389         }
390         
391         // Draw filter
392         this._initialDraw();
393         this._draw();
394
395         // Set up map move event listener
396         var that = this;
397         this._moveHandler = function() {
398             that._draw();
399         };
400         this._map.on("move", this._moveHandler);
401
402         // Add the filter layer to the map
403         this._layer.addTo(this._map);
404         
405         // Zoom out the map if necessary
406         var mapBounds = this._map.getBounds();
407         bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
408         if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
409             this._map.fitBounds(bounds);
410         }
411
412         this._enabled = true;
413         
414         // Call the enabled callback
415         this._callCallback("onEnabled");
416     },
417
418     /* Disable the location filter */
419     disable: function() {
420         if (!this._enabled) {
421             return;
422         }
423
424         // Update buttons
425         this._buttonContainer.removeClass("enabled");
426
427         if (this._enableButton) {
428             this._enableButton.setText(this.options.enableButton.enableText);
429         }
430
431         if (this._adjustButton) {
432             this._adjustButton.remove();
433         }
434
435         // Remove event listener
436         this._map.off("move", this._moveHandler);
437
438         // Remove rectangle layer from map
439         this._map.removeLayer(this._layer);
440
441         this._enabled = false;
442
443         // Call the disabled callback
444         this._callCallback("onDisabled");
445     },
446
447     /* Create a button that allows the user to adjust the location
448        filter to the current zoom */
449     _createAdjustButton: function() {
450         var that = this;
451         this._adjustButton = new L.Control.Button({
452             className: "adjust-button",
453             text: this.options.adjustButton.text,
454             
455             onClick: function(event) {
456                 that._adjustToMap();
457                 that._callCallback("onAdjustToZoomClick");
458             }
459         }).addTo(this._buttonContainer);
460     },
461
462     /* Create the location filter button container and the button that
463        toggles the location filter */
464     _initializeButtonContainer: function() {
465         var that = this;
466         this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
467
468         if (this.options.enableButton) {
469             this._enableButton = new L.Control.Button({
470                 className: "enable-button",
471                 text: this.options.enableButton.enableText,
472
473                 onClick: function(event) {
474                     if (!that._enabled) {
475                         // Enable the location filter
476                         that.enable();
477                         that._callCallback("onEnableClick");
478                     } else {
479                         // Disable the location filter
480                         that.disable();
481                         that._callCallback("onDisableClick");
482                     }
483                 }
484             }).addTo(this._buttonContainer);
485         }
486
487         this._buttonContainer.addTo(this._map);
488     }
489 });