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