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.Layer.extend({
 
  96             enableText: "Select area",
 
  97             disableText: "Remove selection"
 
 100             text: "Select area within current zoom"
 
 102         buttonPosition: 'topleft'
 
 105     initialize: function(options) {
 
 106         L.Util.setOptions(this, options);
 
 109     addTo: function(map) {
 
 114     onAdd: function(map) {
 
 117         if (this.options.enableButton || this.options.adjustButton) {
 
 118             this._initializeButtonContainer();
 
 121         if (this.options.enable) {
 
 126     onRemove: function(map) {
 
 128         if (this._buttonContainer) {
 
 129             this._buttonContainer.removeFrom(map);
 
 133     /* Get the current filter bounds */
 
 134     getBounds: function() { 
 
 135         return new L.LatLngBounds(this._sw, this._ne); 
 
 138     setBounds: function(bounds) {
 
 139         this._nw = bounds.getNorthWest();
 
 140         this._ne = bounds.getNorthEast();
 
 141         this._sw = bounds.getSouthWest();
 
 142         this._se = bounds.getSouthEast();
 
 143         if (this.isEnabled()) {
 
 145             this.fire("change", {bounds: bounds});
 
 149     isEnabled: function() {
 
 150         return this._enabled;
 
 153     /* Draw a rectangle */
 
 154     _drawRectangle: function(bounds, options) {
 
 155         options = options || {};
 
 156         var defaultOptions = {
 
 163         options = L.Util.extend(defaultOptions, options);
 
 164         var rect = new L.Rectangle(bounds, options);
 
 165         rect.addTo(this._layer);
 
 169     /* Draw a draggable marker */
 
 170     _drawImageMarker: function(point, options) {
 
 171         var marker = new L.Marker(point, {
 
 172             icon: new L.DivIcon({
 
 173                 iconAnchor: options.anchor,
 
 174                 iconSize: options.size,
 
 175                 className: options.className
 
 179         marker.addTo(this._layer);
 
 183     /* Draw a move marker. Sets up drag listener that updates the
 
 184        filter corners and redraws the filter when the move marker is
 
 186     _drawMoveMarker: function(point) {
 
 188         this._moveMarker = this._drawImageMarker(point, {
 
 189             "className": "location-filter move-marker",
 
 190             "anchor": [-10, -10],
 
 193         this._moveMarker.on('drag', function(e) {
 
 194             var markerPos = that._moveMarker.getLatLng(),
 
 195                 latDelta = markerPos.lat-that._nw.lat,
 
 196                 lngDelta = markerPos.lng-that._nw.lng;
 
 197             that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta, true);
 
 198             that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta, true);
 
 199             that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta, true);
 
 200             that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta, true);
 
 203         this._setupDragendListener(this._moveMarker);
 
 204         return this._moveMarker;
 
 207     /* Draw a resize marker */
 
 208     _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
 
 209         return this._drawImageMarker(point, {
 
 210             "className": "location-filter resize-marker",
 
 216     /* Track moving of the given resize marker and update the markers
 
 217        given in options.moveAlong to match the position of the moved
 
 218        marker. Update filter corners and redraw the filter */
 
 219     _setupResizeMarkerTracking: function(marker, options) {
 
 221         marker.on('drag', function(e) {
 
 222             var curPosition = marker.getLatLng(),
 
 223                 latMarker = options.moveAlong.lat,
 
 224                 lngMarker = options.moveAlong.lng;
 
 225             // Move follower markers when this marker is moved
 
 226             latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng, true));
 
 227             lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng, true));
 
 228             // Sort marker positions in nw, ne, sw, se order
 
 229             var corners = [that._nwMarker.getLatLng(), 
 
 230                            that._neMarker.getLatLng(), 
 
 231                            that._swMarker.getLatLng(), 
 
 232                            that._seMarker.getLatLng()];
 
 233             corners.sort(function(a, b) {
 
 239             // Update corner points and redraw everything except the resize markers
 
 240             that._nw = corners[0];
 
 241             that._ne = corners[1];
 
 242             that._sw = corners[2];
 
 243             that._se = corners[3];
 
 244             that._draw({repositionResizeMarkers: false});
 
 246         this._setupDragendListener(marker);
 
 249     /* Emit a change event whenever dragend is triggered on the
 
 251     _setupDragendListener: function(marker) {
 
 253         marker.on('dragend', function(e) {
 
 254             that.fire("change", {bounds: that.getBounds()});
 
 258     /* Create bounds for the mask rectangles and the location
 
 260     _calculateBounds: function() {
 
 261         var mapBounds = this._map.getBounds(),
 
 262             outerBounds = new L.LatLngBounds(
 
 263                 new L.LatLng(mapBounds.getSouthWest().lat-0.1,
 
 264                              mapBounds.getSouthWest().lng-0.1, true),
 
 265                 new L.LatLng(mapBounds.getNorthEast().lat+0.1,
 
 266                              mapBounds.getNorthEast().lng+0.1, true)
 
 269         // The south west and north east points of the mask */
 
 270         this._osw = outerBounds.getSouthWest();
 
 271         this._one = outerBounds.getNorthEast();
 
 273         // Bounds for the mask rectangles
 
 274         this._northBounds = new L.LatLngBounds(new L.LatLng(this._ne.lat, this._osw.lng, true), this._one);
 
 275         this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng, true), this._nw);
 
 276         this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng, true));
 
 277         this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng, true));
 
 280     /* Initializes rectangles and markers */
 
 281     _initialDraw: function() {
 
 282         if (this._initialDrawCalled) {
 
 286         this._layer = new L.LayerGroup();
 
 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({
 
 471             className: "location-filter button-container",
 
 472             position: this.options.buttonPosition
 
 475         if (this.options.enableButton) {
 
 476             this._enableButton = new L.Control.Button({
 
 477                 className: "enable-button",
 
 478                 text: this.options.enableButton.enableText,
 
 480                 onClick: function(event) {
 
 481                     if (!that._enabled) {
 
 482                         // Enable the location filter
 
 484                         that.fire("enableClick");
 
 486                         // Disable the location filter
 
 488                         that.fire("disableClick");
 
 491             }).addTo(this._buttonContainer);
 
 494         this._buttonContainer.addTo(this._map);