2 * Leaflet.locationfilter - leaflet location filter plugin
3 * Copyright (C) 2012, Tripbirds.com
6 * Licensed under the MIT License.
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);
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));
20 return new L.LatLngBounds(sw, ne);
23 L.Control.Button = L.Class.extend({
24 initialize: function(options) {
25 L.Util.setOptions(this, options);
28 addTo: function(container) {
29 container.addButton(this);
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);
40 this._onClick = function(event) {
41 that.options.onClick.call(that, event);
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);
53 L.DomEvent.off(this._button, "click", this._onClick);
54 this._buttonContainer.getContainer().removeChild(this._button);
57 setText: function(text) {
58 this._button.title = text;
59 this._button.innerHTML = text;
63 L.Control.ButtonContainer = L.Control.extend({
68 getContainer: function() {
69 if (!this._container) {
70 this._container = L.DomUtil.create('div', this.options.className);
72 return this._container;
75 onAdd: function (map) {
77 return this.getContainer();
80 addButton: function(button) {
84 addClass: function(className) {
85 L.DomUtil.addClass(this.getContainer(), className);
88 removeClass: function(className) {
89 L.DomUtil.removeClass(this.getContainer(), className);
93 L.LocationFilter = L.Class.extend({
94 includes: L.Mixin.Events,
98 enableText: "Select area",
99 disableText: "Remove selection"
102 text: "Select area within current zoom"
106 initialize: function(options) {
107 L.Util.setOptions(this, options);
110 addTo: function(map) {
115 onAdd: function(map) {
117 this._layer = new L.LayerGroup();
118 this._initializeButtonContainer();
120 if (this.options.enable) {
125 onRemove: function(map) {
127 this._buttonContainer.removeFrom(map);
130 /* Get the current filter bounds */
131 getBounds: function() {
132 return new L.LatLngBounds(this._sw, this._ne);
135 setBounds: function(bounds) {
136 this._nw = bounds.getNorthWest();
137 this._ne = bounds.getNorthEast();
138 this._sw = bounds.getSouthWest();
139 this._se = bounds.getSouthEast();
141 this.fire("change", {bounds: bounds});
144 isEnabled: function() {
145 return this._enabled;
148 /* Draw a rectangle */
149 _drawRectangle: function(bounds, options) {
150 options = options || {};
151 var defaultOptions = {
158 options = L.Util.extend(defaultOptions, options);
159 var rect = new L.Rectangle(bounds, options);
160 rect.addTo(this._layer);
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
174 marker.addTo(this._layer);
178 /* Draw a move marker. Sets up drag listener that updates the
179 filter corners and redraws the filter when the move marker is
181 _drawMoveMarker: function(point) {
183 this._moveMarker = this._drawImageMarker(point, {
184 "className": "location-filter move-marker",
185 "anchor": [-10, -10],
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);
198 this._setupDragendListener(this._moveMarker);
199 return this._moveMarker;
202 /* Draw a resize marker */
203 _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
204 return this._drawImageMarker(point, {
205 "className": "location-filter resize-marker",
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) {
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) {
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});
241 this._setupDragendListener(marker);
244 /* Emit a change event whenever dragend is triggered on the
246 _setupDragendListener: function(marker) {
248 marker.on('dragend', function(e) {
249 that.fire("change", {bounds: that.getBounds()});
253 /* Create bounds for the mask rectangles and the location
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)
264 // The south west and north east points of the mask */
265 this._osw = outerBounds.getSouthWest();
266 this._one = outerBounds.getNorthEast();
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));
275 /* Initializes rectangles and markers */
276 _initialDraw: function() {
277 if (this._initialDrawCalled) {
281 // Calculate filter bounds
282 this._calculateBounds();
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",
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);
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}});
313 // Create move marker
314 this._moveMarker = this._drawMoveMarker(this._nw);
316 this._initialDrawCalled = true;
319 /* Reposition all rectangles and markers to the current filter bounds. */
320 _draw: function(options) {
321 options = L.Util.extend({repositionResizeMarkers: true}, options);
323 // Calculate filter bounds
324 this._calculateBounds();
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());
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);
341 // Reposition the move marker
342 this._moveMarker.setLatLng(this._nw);
345 /* Adjust the location filter to the current map bounds */
346 _adjustToMap: function() {
347 this.setBounds(this._map.getBounds());
351 /* Enable the location filter */
357 // Initialize corners
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;
364 bounds = this._map.getBounds();
366 this._map.invalidateSize();
367 this._nw = bounds.getNorthWest();
368 this._ne = bounds.getNorthEast();
369 this._sw = bounds.getSouthWest();
370 this._se = bounds.getSouthEast();
374 this._buttonContainer.addClass("enabled");
376 if (this._enableButton) {
377 this._enableButton.setText(this.options.enableButton.disableText);
380 if (this.options.adjustButton) {
381 this._createAdjustButton();
388 // Set up map move event listener
390 this._moveHandler = function() {
393 this._map.on("move", this._moveHandler);
395 // Add the filter layer to the map
396 this._layer.addTo(this._map);
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);
405 this._enabled = true;
407 // Fire the enabled event
408 this.fire("enabled");
411 /* Disable the location filter */
412 disable: function() {
413 if (!this._enabled) {
418 this._buttonContainer.removeClass("enabled");
420 if (this._enableButton) {
421 this._enableButton.setText(this.options.enableButton.enableText);
424 if (this._adjustButton) {
425 this._adjustButton.remove();
428 // Remove event listener
429 this._map.off("move", this._moveHandler);
431 // Remove rectangle layer from map
432 this._map.removeLayer(this._layer);
434 this._enabled = false;
436 // Fire the disabled event
437 this.fire("disabled");
440 /* Create a button that allows the user to adjust the location
441 filter to the current zoom */
442 _createAdjustButton: function() {
444 this._adjustButton = new L.Control.Button({
445 className: "adjust-button",
446 text: this.options.adjustButton.text,
448 onClick: function(event) {
450 that.fire("adjustToZoomClick");
452 }).addTo(this._buttonContainer);
455 /* Create the location filter button container and the button that
456 toggles the location filter */
457 _initializeButtonContainer: function() {
459 this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
461 if (this.options.enableButton) {
462 this._enableButton = new L.Control.Button({
463 className: "enable-button",
464 text: this.options.enableButton.enableText,
466 onClick: function(event) {
467 if (!that._enabled) {
468 // Enable the location filter
470 that.fire("enableClick");
472 // Disable the location filter
474 that.fire("disableClick");
477 }).addTo(this._buttonContainer);
480 if (this.options.enableButton || this.options.adjustButton) {
481 this._buttonContainer.addTo(this._map);