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);