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) {
118 if (this.options.enableButton || this.options.adjustButton) {
119 this._initializeButtonContainer();
122 if (this.options.enable) {
127 onRemove: function(map) {
129 if (this._buttonContainer) {
130 this._buttonContainer.removeFrom(map);
134 /* Get the current filter bounds */
135 getBounds: function() {
136 return new L.LatLngBounds(this._sw, this._ne);
139 setBounds: function(bounds) {
140 this._nw = bounds.getNorthWest();
141 this._ne = bounds.getNorthEast();
142 this._sw = bounds.getSouthWest();
143 this._se = bounds.getSouthEast();
144 if (this.isEnabled()) {
146 this.fire("change", {bounds: bounds});
150 isEnabled: function() {
151 return this._enabled;
154 /* Draw a rectangle */
155 _drawRectangle: function(bounds, options) {
156 options = options || {};
157 var defaultOptions = {
164 options = L.Util.extend(defaultOptions, options);
165 var rect = new L.Rectangle(bounds, options);
166 rect.addTo(this._layer);
170 /* Draw a draggable marker */
171 _drawImageMarker: function(point, options) {
172 var marker = new L.Marker(point, {
173 icon: new L.DivIcon({
174 iconAnchor: options.anchor,
175 iconSize: options.size,
176 className: options.className
180 marker.addTo(this._layer);
184 /* Draw a move marker. Sets up drag listener that updates the
185 filter corners and redraws the filter when the move marker is
187 _drawMoveMarker: function(point) {
189 this._moveMarker = this._drawImageMarker(point, {
190 "className": "location-filter move-marker",
191 "anchor": [-10, -10],
194 this._moveMarker.on('drag', function(e) {
195 var markerPos = that._moveMarker.getLatLng(),
196 latDelta = markerPos.lat-that._nw.lat,
197 lngDelta = markerPos.lng-that._nw.lng;
198 that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta, true);
199 that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta, true);
200 that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta, true);
201 that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta, true);
204 this._setupDragendListener(this._moveMarker);
205 return this._moveMarker;
208 /* Draw a resize marker */
209 _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
210 return this._drawImageMarker(point, {
211 "className": "location-filter resize-marker",
217 /* Track moving of the given resize marker and update the markers
218 given in options.moveAlong to match the position of the moved
219 marker. Update filter corners and redraw the filter */
220 _setupResizeMarkerTracking: function(marker, options) {
222 marker.on('drag', function(e) {
223 var curPosition = marker.getLatLng(),
224 latMarker = options.moveAlong.lat,
225 lngMarker = options.moveAlong.lng;
226 // Move follower markers when this marker is moved
227 latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng, true));
228 lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng, true));
229 // Sort marker positions in nw, ne, sw, se order
230 var corners = [that._nwMarker.getLatLng(),
231 that._neMarker.getLatLng(),
232 that._swMarker.getLatLng(),
233 that._seMarker.getLatLng()];
234 corners.sort(function(a, b) {
240 // Update corner points and redraw everything except the resize markers
241 that._nw = corners[0];
242 that._ne = corners[1];
243 that._sw = corners[2];
244 that._se = corners[3];
245 that._draw({repositionResizeMarkers: false});
247 this._setupDragendListener(marker);
250 /* Emit a change event whenever dragend is triggered on the
252 _setupDragendListener: function(marker) {
254 marker.on('dragend', function(e) {
255 that.fire("change", {bounds: that.getBounds()});
259 /* Create bounds for the mask rectangles and the location
261 _calculateBounds: function() {
262 var mapBounds = this._map.getBounds(),
263 outerBounds = new L.LatLngBounds(
264 new L.LatLng(mapBounds.getSouthWest().lat-0.1,
265 mapBounds.getSouthWest().lng-0.1, true),
266 new L.LatLng(mapBounds.getNorthEast().lat+0.1,
267 mapBounds.getNorthEast().lng+0.1, true)
270 // The south west and north east points of the mask */
271 this._osw = outerBounds.getSouthWest();
272 this._one = outerBounds.getNorthEast();
274 // Bounds for the mask rectangles
275 this._northBounds = new L.LatLngBounds(new L.LatLng(this._ne.lat, this._osw.lng, true), this._one);
276 this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng, true), this._nw);
277 this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng, true));
278 this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng, true));
281 /* Initializes rectangles and markers */
282 _initialDraw: function() {
283 if (this._initialDrawCalled) {
287 this._layer = new L.LayerGroup();
289 // Calculate filter bounds
290 this._calculateBounds();
293 this._northRect = this._drawRectangle(this._northBounds);
294 this._westRect = this._drawRectangle(this._westBounds);
295 this._eastRect = this._drawRectangle(this._eastBounds);
296 this._southRect = this._drawRectangle(this._southBounds);
297 this._innerRect = this._drawRectangle(this.getBounds(), {
305 // Create resize markers
306 this._nwMarker = this._drawResizeMarker(this._nw);
307 this._neMarker = this._drawResizeMarker(this._ne);
308 this._swMarker = this._drawResizeMarker(this._sw);
309 this._seMarker = this._drawResizeMarker(this._se);
311 // Setup tracking of resize markers. Each marker has pair of
312 // follower markers that must be moved whenever the marker is
313 // moved. For example, whenever the north west resize marker
314 // moves, the south west marker must move along on the x-axis
315 // and the north east marker must move on the y axis
316 this._setupResizeMarkerTracking(this._nwMarker, {moveAlong: {lat: this._neMarker, lng: this._swMarker}});
317 this._setupResizeMarkerTracking(this._neMarker, {moveAlong: {lat: this._nwMarker, lng: this._seMarker}});
318 this._setupResizeMarkerTracking(this._swMarker, {moveAlong: {lat: this._seMarker, lng: this._nwMarker}});
319 this._setupResizeMarkerTracking(this._seMarker, {moveAlong: {lat: this._swMarker, lng: this._neMarker}});
321 // Create move marker
322 this._moveMarker = this._drawMoveMarker(this._nw);
324 this._initialDrawCalled = true;
327 /* Reposition all rectangles and markers to the current filter bounds. */
328 _draw: function(options) {
329 options = L.Util.extend({repositionResizeMarkers: true}, options);
331 // Calculate filter bounds
332 this._calculateBounds();
334 // Reposition rectangles
335 this._northRect.setBounds(this._northBounds);
336 this._westRect.setBounds(this._westBounds);
337 this._eastRect.setBounds(this._eastBounds);
338 this._southRect.setBounds(this._southBounds);
339 this._innerRect.setBounds(this.getBounds());
341 // Reposition resize markers
342 if (options.repositionResizeMarkers) {
343 this._nwMarker.setLatLng(this._nw);
344 this._neMarker.setLatLng(this._ne);
345 this._swMarker.setLatLng(this._sw);
346 this._seMarker.setLatLng(this._se);
349 // Reposition the move marker
350 this._moveMarker.setLatLng(this._nw);
353 /* Adjust the location filter to the current map bounds */
354 _adjustToMap: function() {
355 this.setBounds(this._map.getBounds());
359 /* Enable the location filter */
365 // Initialize corners
367 if (this._sw && this._ne) {
368 bounds = new L.LatLngBounds(this._sw, this._ne);
369 } else if (this.options.bounds) {
370 bounds = this.options.bounds;
372 bounds = this._map.getBounds();
374 this._map.invalidateSize();
375 this._nw = bounds.getNorthWest();
376 this._ne = bounds.getNorthEast();
377 this._sw = bounds.getSouthWest();
378 this._se = bounds.getSouthEast();
382 if (this._buttonContainer) {
383 this._buttonContainer.addClass("enabled");
386 if (this._enableButton) {
387 this._enableButton.setText(this.options.enableButton.disableText);
390 if (this.options.adjustButton) {
391 this._createAdjustButton();
398 // Set up map move event listener
400 this._moveHandler = function() {
403 this._map.on("move", this._moveHandler);
405 // Add the filter layer to the map
406 this._layer.addTo(this._map);
408 // Zoom out the map if necessary
409 var mapBounds = this._map.getBounds();
410 bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
411 if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
412 this._map.fitBounds(bounds);
415 this._enabled = true;
417 // Fire the enabled event
418 this.fire("enabled");
421 /* Disable the location filter */
422 disable: function() {
423 if (!this._enabled) {
428 if (this._buttonContainer) {
429 this._buttonContainer.removeClass("enabled");
432 if (this._enableButton) {
433 this._enableButton.setText(this.options.enableButton.enableText);
436 if (this._adjustButton) {
437 this._adjustButton.remove();
440 // Remove event listener
441 this._map.off("move", this._moveHandler);
443 // Remove rectangle layer from map
444 this._map.removeLayer(this._layer);
446 this._enabled = false;
448 // Fire the disabled event
449 this.fire("disabled");
452 /* Create a button that allows the user to adjust the location
453 filter to the current zoom */
454 _createAdjustButton: function() {
456 this._adjustButton = new L.Control.Button({
457 className: "adjust-button",
458 text: this.options.adjustButton.text,
460 onClick: function(event) {
462 that.fire("adjustToZoomClick");
464 }).addTo(this._buttonContainer);
467 /* Create the location filter button container and the button that
468 toggles the location filter */
469 _initializeButtonContainer: function() {
471 this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
473 if (this.options.enableButton) {
474 this._enableButton = new L.Control.Button({
475 className: "enable-button",
476 text: this.options.enableButton.enableText,
478 onClick: function(event) {
479 if (!that._enabled) {
480 // Enable the location filter
482 that.fire("enableClick");
484 // Disable the location filter
486 that.fire("disableClick");
489 }).addTo(this._buttonContainer);
492 this._buttonContainer.addTo(this._map);