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({
96 enableText: "Select area",
97 disableText: "Remove selection"
100 text: "Select area within current zoom"
104 initialize: function(options) {
105 L.Util.setOptions(this, options);
108 addTo: function(map) {
113 onAdd: function(map) {
115 this._layer = new L.LayerGroup();
116 this._initializeButtonContainer();
118 if (this.options.enable) {
123 onRemove: function(map) {
125 this._buttonContainer.removeFrom(map);
128 /* Get the current filter bounds */
129 getBounds: function() {
130 return new L.LatLngBounds(this._sw, this._ne);
133 setBounds: function(bounds) {
134 this._nw = bounds.getNorthWest();
135 this._ne = bounds.getNorthEast();
136 this._sw = bounds.getSouthWest();
137 this._se = bounds.getSouthEast();
139 this._callCallback("onChange");
142 isEnabled: function() {
143 return this._enabled;
146 /* Draw a rectangle */
147 _drawRectangle: function(bounds, options) {
148 options = options || {};
149 var defaultOptions = {
156 options = L.Util.extend(defaultOptions, options);
157 var rect = new L.Rectangle(bounds, options);
158 rect.addTo(this._layer);
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
172 marker.addTo(this._layer);
176 /* Draw a move marker. Sets up drag listener that updates the
177 filter corners and redraws the filter when the move marker is
179 _drawMoveMarker: function(point) {
181 this._moveMarker = this._drawImageMarker(point, {
182 "className": "location-filter move-marker",
183 "anchor": [-10, -10],
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);
196 this._setupDragendListener(this._moveMarker);
197 return this._moveMarker;
200 /* Draw a resize marker */
201 _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
202 return this._drawImageMarker(point, {
203 "className": "location-filter resize-marker",
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) {
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) {
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});
239 this._setupDragendListener(marker);
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());
249 /* Call the onChange callback whenever dragend is triggered on the
251 _setupDragendListener: function(marker) {
252 if (this.options.onChange) {
254 marker.on('dragend', function(e) {
255 that._callCallback("onChange");
260 /* Create bounds for the mask rectangles and the location
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)
271 // The south west and north east points of the mask */
272 this._osw = outerBounds.getSouthWest();
273 this._one = outerBounds.getNorthEast();
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));
282 /* Initializes rectangles and markers */
283 _initialDraw: function() {
284 if (this._initialDrawCalled) {
288 // Calculate filter bounds
289 this._calculateBounds();
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",
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);
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}});
320 // Create move marker
321 this._moveMarker = this._drawMoveMarker(this._nw);
323 this._initialDrawCalled = true;
326 /* Reposition all rectangles and markers to the current filter bounds. */
327 _draw: function(options) {
328 options = L.Util.extend({repositionResizeMarkers: true}, options);
330 // Calculate filter bounds
331 this._calculateBounds();
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());
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);
348 // Reposition the move marker
349 this._moveMarker.setLatLng(this._nw);
352 /* Adjust the location filter to the current map bounds */
353 _adjustToMap: function() {
354 this.setBounds(this._map.getBounds());
358 /* Enable the location filter */
364 // Initialize corners
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;
371 bounds = this._map.getBounds();
373 this._map.invalidateSize();
374 this._nw = bounds.getNorthWest();
375 this._ne = bounds.getNorthEast();
376 this._sw = bounds.getSouthWest();
377 this._se = bounds.getSouthEast();
381 this._buttonContainer.addClass("enabled");
383 if (this._enableButton) {
384 this._enableButton.setText(this.options.enableButton.disableText);
387 if (this.options.adjustButton) {
388 this._createAdjustButton();
395 // Set up map move event listener
397 this._moveHandler = function() {
400 this._map.on("move", this._moveHandler);
402 // Add the filter layer to the map
403 this._layer.addTo(this._map);
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);
412 this._enabled = true;
414 // Call the enabled callback
415 this._callCallback("onEnabled");
418 /* Disable the location filter */
419 disable: function() {
420 if (!this._enabled) {
425 this._buttonContainer.removeClass("enabled");
427 if (this._enableButton) {
428 this._enableButton.setText(this.options.enableButton.enableText);
431 if (this._adjustButton) {
432 this._adjustButton.remove();
435 // Remove event listener
436 this._map.off("move", this._moveHandler);
438 // Remove rectangle layer from map
439 this._map.removeLayer(this._layer);
441 this._enabled = false;
443 // Call the disabled callback
444 this._callCallback("onDisabled");
447 /* Create a button that allows the user to adjust the location
448 filter to the current zoom */
449 _createAdjustButton: function() {
451 this._adjustButton = new L.Control.Button({
452 className: "adjust-button",
453 text: this.options.adjustButton.text,
455 onClick: function(event) {
457 that._callCallback("onAdjustToZoomClick");
459 }).addTo(this._buttonContainer);
462 /* Create the location filter button container and the button that
463 toggles the location filter */
464 _initializeButtonContainer: function() {
466 this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
468 if (this.options.enableButton) {
469 this._enableButton = new L.Control.Button({
470 className: "enable-button",
471 text: this.options.enableButton.enableText,
473 onClick: function(event) {
474 if (!that._enabled) {
475 // Enable the location filter
477 that._callCallback("onEnableClick");
479 // Disable the location filter
481 that._callCallback("onDisableClick");
484 }).addTo(this._buttonContainer);
487 this._buttonContainer.addTo(this._map);