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({
 
  94     includes: L.Mixin.Events,
 
  98             enableText: "Select area",
 
  99             disableText: "Remove selection"
 
 102             text: "Select area within current zoom"
 
 104         buttonPosition: 'topleft'
 
 107     initialize: function(options) {
 
 108         L.Util.setOptions(this, options);
 
 111     addTo: function(map) {
 
 116     onAdd: function(map) {
 
 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         this._layer = new L.LayerGroup();
 
 290         // Calculate filter bounds
 
 291         this._calculateBounds();
 
 294         this._northRect = this._drawRectangle(this._northBounds);
 
 295         this._westRect = this._drawRectangle(this._westBounds);
 
 296         this._eastRect = this._drawRectangle(this._eastBounds);
 
 297         this._southRect = this._drawRectangle(this._southBounds);
 
 298         this._innerRect = this._drawRectangle(this.getBounds(), {
 
 306         // Create resize markers
 
 307         this._nwMarker = this._drawResizeMarker(this._nw);
 
 308         this._neMarker = this._drawResizeMarker(this._ne);
 
 309         this._swMarker = this._drawResizeMarker(this._sw);
 
 310         this._seMarker = this._drawResizeMarker(this._se);
 
 312         // Setup tracking of resize markers. Each marker has pair of
 
 313         // follower markers that must be moved whenever the marker is
 
 314         // moved. For example, whenever the north west resize marker
 
 315         // moves, the south west marker must move along on the x-axis
 
 316         // and the north east marker must move on the y axis
 
 317         this._setupResizeMarkerTracking(this._nwMarker, {moveAlong: {lat: this._neMarker, lng: this._swMarker}});
 
 318         this._setupResizeMarkerTracking(this._neMarker, {moveAlong: {lat: this._nwMarker, lng: this._seMarker}});
 
 319         this._setupResizeMarkerTracking(this._swMarker, {moveAlong: {lat: this._seMarker, lng: this._nwMarker}});
 
 320         this._setupResizeMarkerTracking(this._seMarker, {moveAlong: {lat: this._swMarker, lng: this._neMarker}});
 
 322         // Create move marker
 
 323         this._moveMarker = this._drawMoveMarker(this._nw);
 
 325         this._initialDrawCalled = true;
 
 328     /* Reposition all rectangles and markers to the current filter bounds. */    
 
 329     _draw: function(options) {
 
 330         options = L.Util.extend({repositionResizeMarkers: true}, options);
 
 332         // Calculate filter bounds
 
 333         this._calculateBounds();
 
 335         // Reposition rectangles
 
 336         this._northRect.setBounds(this._northBounds);
 
 337         this._westRect.setBounds(this._westBounds);
 
 338         this._eastRect.setBounds(this._eastBounds);
 
 339         this._southRect.setBounds(this._southBounds);
 
 340         this._innerRect.setBounds(this.getBounds());
 
 342         // Reposition resize markers
 
 343         if (options.repositionResizeMarkers) {
 
 344             this._nwMarker.setLatLng(this._nw);
 
 345             this._neMarker.setLatLng(this._ne);
 
 346             this._swMarker.setLatLng(this._sw);
 
 347             this._seMarker.setLatLng(this._se);
 
 350         // Reposition the move marker
 
 351         this._moveMarker.setLatLng(this._nw);
 
 354     /* Adjust the location filter to the current map bounds */
 
 355     _adjustToMap: function() {
 
 356         this.setBounds(this._map.getBounds());
 
 360     /* Enable the location filter */
 
 366         // Initialize corners
 
 368         if (this._sw && this._ne) {
 
 369             bounds = new L.LatLngBounds(this._sw, this._ne);
 
 370         } else if (this.options.bounds) {
 
 371             bounds = this.options.bounds;
 
 373             bounds = this._map.getBounds();
 
 375         this._map.invalidateSize();
 
 376         this._nw = bounds.getNorthWest();
 
 377         this._ne = bounds.getNorthEast();
 
 378         this._sw = bounds.getSouthWest();
 
 379         this._se = bounds.getSouthEast();
 
 383         if (this._buttonContainer) {
 
 384             this._buttonContainer.addClass("enabled");
 
 387         if (this._enableButton) {
 
 388             this._enableButton.setText(this.options.enableButton.disableText);
 
 391         if (this.options.adjustButton) {
 
 392             this._createAdjustButton();
 
 399         // Set up map move event listener
 
 401         this._moveHandler = function() {
 
 404         this._map.on("move", this._moveHandler);
 
 406         // Add the filter layer to the map
 
 407         this._layer.addTo(this._map);
 
 409         // Zoom out the map if necessary
 
 410         var mapBounds = this._map.getBounds();
 
 411         bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
 
 412         if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
 
 413             this._map.fitBounds(bounds);
 
 416         this._enabled = true;
 
 418         // Fire the enabled event
 
 419         this.fire("enabled");
 
 422     /* Disable the location filter */
 
 423     disable: function() {
 
 424         if (!this._enabled) {
 
 429         if (this._buttonContainer) {
 
 430             this._buttonContainer.removeClass("enabled");
 
 433         if (this._enableButton) {
 
 434             this._enableButton.setText(this.options.enableButton.enableText);
 
 437         if (this._adjustButton) {
 
 438             this._adjustButton.remove();
 
 441         // Remove event listener
 
 442         this._map.off("move", this._moveHandler);
 
 444         // Remove rectangle layer from map
 
 445         this._map.removeLayer(this._layer);
 
 447         this._enabled = false;
 
 449         // Fire the disabled event
 
 450         this.fire("disabled");
 
 453     /* Create a button that allows the user to adjust the location
 
 454        filter to the current zoom */
 
 455     _createAdjustButton: function() {
 
 457         this._adjustButton = new L.Control.Button({
 
 458             className: "adjust-button",
 
 459             text: this.options.adjustButton.text,
 
 461             onClick: function(event) {
 
 463                 that.fire("adjustToZoomClick");
 
 465         }).addTo(this._buttonContainer);
 
 468     /* Create the location filter button container and the button that
 
 469        toggles the location filter */
 
 470     _initializeButtonContainer: function() {
 
 472         this._buttonContainer = new L.Control.ButtonContainer({
 
 473             className: "location-filter button-container",
 
 474             position: this.options.buttonPosition
 
 477         if (this.options.enableButton) {
 
 478             this._enableButton = new L.Control.Button({
 
 479                 className: "enable-button",
 
 480                 text: this.options.enableButton.enableText,
 
 482                 onClick: function(event) {
 
 483                     if (!that._enabled) {
 
 484                         // Enable the location filter
 
 486                         that.fire("enableClick");
 
 488                         // Disable the location filter
 
 490                         that.fire("disableClick");
 
 493             }).addTo(this._buttonContainer);
 
 496         this._buttonContainer.addTo(this._map);