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();
119 if (this.options.enableButton || this.options.adjustButton) {
120 this._initializeButtonContainer();
123 if (this.options.enable) {
128 onRemove: function(map) {
130 if (this._buttonContainer) {
131 this._buttonContainer.removeFrom(map);
135 /* Get the current filter bounds */
136 getBounds: function() {
137 return new L.LatLngBounds(this._sw, this._ne);
140 setBounds: function(bounds) {
141 this._nw = bounds.getNorthWest();
142 this._ne = bounds.getNorthEast();
143 this._sw = bounds.getSouthWest();
144 this._se = bounds.getSouthEast();
145 if (this.isEnabled()) {
147 this.fire("change", {bounds: bounds});
151 isEnabled: function() {
152 return this._enabled;
155 /* Draw a rectangle */
156 _drawRectangle: function(bounds, options) {
157 options = options || {};
158 var defaultOptions = {
165 options = L.Util.extend(defaultOptions, options);
166 var rect = new L.Rectangle(bounds, options);
167 rect.addTo(this._layer);
171 /* Draw a draggable marker */
172 _drawImageMarker: function(point, options) {
173 var marker = new L.Marker(point, {
174 icon: new L.DivIcon({
175 iconAnchor: options.anchor,
176 iconSize: options.size,
177 className: options.className
181 marker.addTo(this._layer);
185 /* Draw a move marker. Sets up drag listener that updates the
186 filter corners and redraws the filter when the move marker is
188 _drawMoveMarker: function(point) {
190 this._moveMarker = this._drawImageMarker(point, {
191 "className": "location-filter move-marker",
192 "anchor": [-10, -10],
195 this._moveMarker.on('drag', function(e) {
196 var markerPos = that._moveMarker.getLatLng(),
197 latDelta = markerPos.lat-that._nw.lat,
198 lngDelta = markerPos.lng-that._nw.lng;
199 that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta, true);
200 that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta, true);
201 that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta, true);
202 that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta, true);
205 this._setupDragendListener(this._moveMarker);
206 return this._moveMarker;
209 /* Draw a resize marker */
210 _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
211 return this._drawImageMarker(point, {
212 "className": "location-filter resize-marker",
218 /* Track moving of the given resize marker and update the markers
219 given in options.moveAlong to match the position of the moved
220 marker. Update filter corners and redraw the filter */
221 _setupResizeMarkerTracking: function(marker, options) {
223 marker.on('drag', function(e) {
224 var curPosition = marker.getLatLng(),
225 latMarker = options.moveAlong.lat,
226 lngMarker = options.moveAlong.lng;
227 // Move follower markers when this marker is moved
228 latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng, true));
229 lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng, true));
230 // Sort marker positions in nw, ne, sw, se order
231 var corners = [that._nwMarker.getLatLng(),
232 that._neMarker.getLatLng(),
233 that._swMarker.getLatLng(),
234 that._seMarker.getLatLng()];
235 corners.sort(function(a, b) {
241 // Update corner points and redraw everything except the resize markers
242 that._nw = corners[0];
243 that._ne = corners[1];
244 that._sw = corners[2];
245 that._se = corners[3];
246 that._draw({repositionResizeMarkers: false});
248 this._setupDragendListener(marker);
251 /* Emit a change event whenever dragend is triggered on the
253 _setupDragendListener: function(marker) {
255 marker.on('dragend', function(e) {
256 that.fire("change", {bounds: that.getBounds()});
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, true),
267 new L.LatLng(mapBounds.getNorthEast().lat+0.1,
268 mapBounds.getNorthEast().lng+0.1, true)
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, true), this._one);
277 this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng, true), this._nw);
278 this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng, true));
279 this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng, true));
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(), {
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 if (this._buttonContainer) {
382 this._buttonContainer.addClass("enabled");
385 if (this._enableButton) {
386 this._enableButton.setText(this.options.enableButton.disableText);
389 if (this.options.adjustButton) {
390 this._createAdjustButton();
397 // Set up map move event listener
399 this._moveHandler = function() {
402 this._map.on("move", this._moveHandler);
404 // Add the filter layer to the map
405 this._layer.addTo(this._map);
407 // Zoom out the map if necessary
408 var mapBounds = this._map.getBounds();
409 bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
410 if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
411 this._map.fitBounds(bounds);
414 this._enabled = true;
416 // Fire the enabled event
417 this.fire("enabled");
420 /* Disable the location filter */
421 disable: function() {
422 if (!this._enabled) {
427 if (this._buttonContainer) {
428 this._buttonContainer.removeClass("enabled");
431 if (this._enableButton) {
432 this._enableButton.setText(this.options.enableButton.enableText);
435 if (this._adjustButton) {
436 this._adjustButton.remove();
439 // Remove event listener
440 this._map.off("move", this._moveHandler);
442 // Remove rectangle layer from map
443 this._map.removeLayer(this._layer);
445 this._enabled = false;
447 // Fire the disabled event
448 this.fire("disabled");
451 /* Create a button that allows the user to adjust the location
452 filter to the current zoom */
453 _createAdjustButton: function() {
455 this._adjustButton = new L.Control.Button({
456 className: "adjust-button",
457 text: this.options.adjustButton.text,
459 onClick: function(event) {
461 that.fire("adjustToZoomClick");
463 }).addTo(this._buttonContainer);
466 /* Create the location filter button container and the button that
467 toggles the location filter */
468 _initializeButtonContainer: function() {
470 this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
472 if (this.options.enableButton) {
473 this._enableButton = new L.Control.Button({
474 className: "enable-button",
475 text: this.options.enableButton.enableText,
477 onClick: function(event) {
478 if (!that._enabled) {
479 // Enable the location filter
481 that.fire("enableClick");
483 // Disable the location filter
485 that.fire("disableClick");
488 }).addTo(this._buttonContainer);
491 this._buttonContainer.addTo(this._map);