changeset 162:46e0413a8dad gmap2

Add util libs.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 10 Mar 2014 01:42:59 +0200
parents 074b6936ec4f
children 8fd905f81672
files lib/markermanager.js lib/markermanager_packed.js lib/util.js
diffstat 3 files changed, 1033 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/markermanager.js	Mon Mar 10 01:42:59 2014 +0200
@@ -0,0 +1,980 @@
+/**
+ * @name MarkerManager v3
+ * @version 1.2
+ * @copyright (c) 2007 Google Inc.
+ * @author Doug Ricket, Bjorn Brala (port to v3), others,
+ *
+ * @fileoverview Marker manager is an interface between the map and the user,
+ * designed to manage adding and removing many points when the viewport changes.
+ * <br /><br />
+ * <b>How it Works</b>:<br/> 
+ * The MarkerManager places its markers onto a grid, similar to the map tiles.
+ * When the user moves the viewport, it computes which grid cells have
+ * entered or left the viewport, and shows or hides all the markers in those
+ * cells.
+ * (If the users scrolls the viewport beyond the markers that are loaded,
+ * no markers will be visible until the <code>EVENT_moveend</code> 
+ * triggers an update.)
+ * In practical consequences, this allows 10,000 markers to be distributed over
+ * a large area, and as long as only 100-200 are visible in any given viewport,
+ * the user will see good performance corresponding to the 100 visible markers,
+ * rather than poor performance corresponding to the total 10,000 markers.
+ * Note that some code is optimized for speed over space,
+ * with the goal of accommodating thousands of markers.
+ */
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+
+/**
+ * @name MarkerManagerOptions
+ * @class This class represents optional arguments to the {@link MarkerManager}
+ *     constructor.
+ * @property {Number} maxZoom Sets the maximum zoom level monitored by a
+ *     marker manager. If not given, the manager assumes the maximum map zoom
+ *     level. This value is also used when markers are added to the manager
+ *     without the optional {@link maxZoom} parameter.
+ * @property {Number} borderPadding Specifies, in pixels, the extra padding
+ *     outside the map's current viewport monitored by a manager. Markers that
+ *     fall within this padding are added to the map, even if they are not fully
+ *     visible.
+ * @property {Boolean} trackMarkers=false Indicates whether or not a marker
+ *     manager should track markers' movements. If you wish to move managed
+ *     markers using the {@link setPoint}/{@link setLatLng} methods, 
+ *     this option should be set to {@link true}.
+ */
+
+/**
+ * Creates a new MarkerManager that will show/hide markers on a map.
+ *
+ * Events:
+ * @event changed (Parameters: shown bounds, shown markers) Notify listeners when the state of what is displayed changes.
+ * @event loaded MarkerManager has succesfully been initialized.
+ *
+ * @constructor
+ * @param {Map} map The map to manage.
+ * @param {Object} opt_opts A container for optional arguments:
+ *   {Number} maxZoom The maximum zoom level for which to create tiles.
+ *   {Number} borderPadding The width in pixels beyond the map border,
+ *                   where markers should be display.
+ *   {Boolean} trackMarkers Whether or not this manager should track marker
+ *                   movements.
+ */
+function MarkerManager(map, opt_opts) {
+  var me = this;
+  me.map_ = map;
+  me.mapZoom_ = map.getZoom();
+  
+  me.projectionHelper_ = new ProjectionHelperOverlay(map);
+  google.maps.event.addListener(me.projectionHelper_, 'ready', function () {
+    me.projection_ = this.getProjection();
+    me.initialize(map, opt_opts);
+  });
+}
+
+  
+MarkerManager.prototype.initialize = function (map, opt_opts) {
+  var me = this;
+  
+  opt_opts = opt_opts || {};
+  me.tileSize_ = MarkerManager.DEFAULT_TILE_SIZE_;
+
+  var mapTypes = map.mapTypes;
+
+  // Find max zoom level
+  var mapMaxZoom = 1;
+  for (var sType in mapTypes ) {
+    if (mapTypes.hasOwnProperty(sType) &&
+        mapTypes.get(sType) && mapTypes.get(sType).maxZoom === 'number') {
+      var mapTypeMaxZoom = map.mapTypes.get(sType).maxZoom;
+      if (mapTypeMaxZoom > mapMaxZoom) {
+        mapMaxZoom = mapTypeMaxZoom;
+      }
+    }
+  }
+  
+  me.maxZoom_  = opt_opts.maxZoom || 19;
+
+  me.trackMarkers_ = opt_opts.trackMarkers;
+  me.show_ = opt_opts.show || true;
+
+  var padding;
+  if (typeof opt_opts.borderPadding === 'number') {
+    padding = opt_opts.borderPadding;
+  } else {
+    padding = MarkerManager.DEFAULT_BORDER_PADDING_;
+  }
+  // The padding in pixels beyond the viewport, where we will pre-load markers.
+  me.swPadding_ = new google.maps.Size(-padding, padding);
+  me.nePadding_ = new google.maps.Size(padding, -padding);
+  me.borderPadding_ = padding;
+
+  me.gridWidth_ = {};
+
+  me.grid_ = {};
+  me.grid_[me.maxZoom_] = {};
+  me.numMarkers_ = {};
+  me.numMarkers_[me.maxZoom_] = 0;
+
+
+  google.maps.event.addListener(map, 'dragend', function () {
+    me.onMapMoveEnd_();
+  });
+  
+  google.maps.event.addListener(map, 'idle', function () {
+    me.onMapMoveEnd_();
+  });
+  
+  google.maps.event.addListener(map, 'zoom_changed', function () {
+    me.onMapMoveEnd_();
+  });
+
+
+
+  /**
+   * This closure provide easy access to the map.
+   * They are used as callbacks, not as methods.
+   * @param GMarker marker Marker to be removed from the map
+   * @private
+   */
+  me.removeOverlay_ = function (marker) {
+    marker.setMap(null);
+    me.shownMarkers_--;
+  };
+
+  /**
+   * This closure provide easy access to the map.
+   * They are used as callbacks, not as methods.
+   * @param GMarker marker Marker to be added to the map
+   * @private
+   */
+  me.addOverlay_ = function (marker) {
+    if (me.show_) {
+      marker.setMap(me.map_);
+      me.shownMarkers_++;
+    }
+  };
+
+  me.resetManager_();
+  me.shownMarkers_ = 0;
+
+  me.shownBounds_ = me.getMapGridBounds_();
+  
+  google.maps.event.trigger(me, 'loaded');
+  
+};
+
+/**
+ *  Default tile size used for deviding the map into a grid.
+ */
+MarkerManager.DEFAULT_TILE_SIZE_ = 1024;
+
+/*
+ *  How much extra space to show around the map border so
+ *  dragging doesn't result in an empty place.
+ */
+MarkerManager.DEFAULT_BORDER_PADDING_ = 100;
+
+/**
+ *  Default tilesize of single tile world.
+ */
+MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE = 256;
+
+
+/**
+ * Initializes MarkerManager arrays for all zoom levels
+ * Called by constructor and by clearAllMarkers
+ */
+MarkerManager.prototype.resetManager_ = function () {
+  var mapWidth = MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE;
+  for (var zoom = 0; zoom <= this.maxZoom_; ++zoom) {
+    this.grid_[zoom] = {};
+    this.numMarkers_[zoom] = 0;
+    this.gridWidth_[zoom] = Math.ceil(mapWidth / this.tileSize_);
+    mapWidth <<= 1;
+  }
+
+};
+
+/**
+ * Removes all markers in the manager, and
+ * removes any visible markers from the map.
+ */
+MarkerManager.prototype.clearMarkers = function () {
+  this.processAll_(this.shownBounds_, this.removeOverlay_);
+  this.resetManager_();
+};
+
+
+/**
+ * Gets the tile coordinate for a given latlng point.
+ *
+ * @param {LatLng} latlng The geographical point.
+ * @param {Number} zoom The zoom level.
+ * @param {google.maps.Size} padding The padding used to shift the pixel coordinate.
+ *               Used for expanding a bounds to include an extra padding
+ *               of pixels surrounding the bounds.
+ * @return {GPoint} The point in tile coordinates.
+ *
+ */
+MarkerManager.prototype.getTilePoint_ = function (latlng, zoom, padding) {
+
+  var pixelPoint = this.projectionHelper_.LatLngToPixel(latlng, zoom);
+
+  var point = new google.maps.Point(
+    Math.floor((pixelPoint.x + padding.width) / this.tileSize_),
+    Math.floor((pixelPoint.y + padding.height) / this.tileSize_)
+  );
+
+  return point;
+};
+
+
+/**
+ * Finds the appropriate place to add the marker to the grid.
+ * Optimized for speed; does not actually add the marker to the map.
+ * Designed for batch-processing thousands of markers.
+ *
+ * @param {Marker} marker The marker to add.
+ * @param {Number} minZoom The minimum zoom for displaying the marker.
+ * @param {Number} maxZoom The maximum zoom for displaying the marker.
+ */
+MarkerManager.prototype.addMarkerBatch_ = function (marker, minZoom, maxZoom) {
+  var me = this;
+
+  var mPoint = marker.getPosition();
+  marker.MarkerManager_minZoom = minZoom;
+  
+  
+  // Tracking markers is expensive, so we do this only if the
+  // user explicitly requested it when creating marker manager.
+  if (this.trackMarkers_) {
+    google.maps.event.addListener(marker, 'changed', function (a, b, c) {
+      me.onMarkerMoved_(a, b, c);
+    });
+  }
+
+  var gridPoint = this.getTilePoint_(mPoint, maxZoom, new google.maps.Size(0, 0, 0, 0));
+
+  for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
+    var cell = this.getGridCellCreate_(gridPoint.x, gridPoint.y, zoom);
+    cell.push(marker);
+
+    gridPoint.x = gridPoint.x >> 1;
+    gridPoint.y = gridPoint.y >> 1;
+  }
+};
+
+
+/**
+ * Returns whether or not the given point is visible in the shown bounds. This
+ * is a helper method that takes care of the corner case, when shownBounds have
+ * negative minX value.
+ *
+ * @param {Point} point a point on a grid.
+ * @return {Boolean} Whether or not the given point is visible in the currently
+ * shown bounds.
+ */
+MarkerManager.prototype.isGridPointVisible_ = function (point) {
+  var vertical = this.shownBounds_.minY <= point.y &&
+      point.y <= this.shownBounds_.maxY;
+  var minX = this.shownBounds_.minX;
+  var horizontal = minX <= point.x && point.x <= this.shownBounds_.maxX;
+  if (!horizontal && minX < 0) {
+    // Shifts the negative part of the rectangle. As point.x is always less
+    // than grid width, only test shifted minX .. 0 part of the shown bounds.
+    var width = this.gridWidth_[this.shownBounds_.z];
+    horizontal = minX + width <= point.x && point.x <= width - 1;
+  }
+  return vertical && horizontal;
+};
+
+
+/**
+ * Reacts to a notification from a marker that it has moved to a new location.
+ * It scans the grid all all zoom levels and moves the marker from the old grid
+ * location to a new grid location.
+ *
+ * @param {Marker} marker The marker that moved.
+ * @param {LatLng} oldPoint The old position of the marker.
+ * @param {LatLng} newPoint The new position of the marker.
+ */
+MarkerManager.prototype.onMarkerMoved_ = function (marker, oldPoint, newPoint) {
+  // NOTE: We do not know the minimum or maximum zoom the marker was
+  // added at, so we start at the absolute maximum. Whenever we successfully
+  // remove a marker at a given zoom, we add it at the new grid coordinates.
+  var zoom = this.maxZoom_;
+  var changed = false;
+  var oldGrid = this.getTilePoint_(oldPoint, zoom, new google.maps.Size(0, 0, 0, 0));
+  var newGrid = this.getTilePoint_(newPoint, zoom, new google.maps.Size(0, 0, 0, 0));
+  while (zoom >= 0 && (oldGrid.x !== newGrid.x || oldGrid.y !== newGrid.y)) {
+    var cell = this.getGridCellNoCreate_(oldGrid.x, oldGrid.y, zoom);
+    if (cell) {
+      if (this.removeFromArray_(cell, marker)) {
+        this.getGridCellCreate_(newGrid.x, newGrid.y, zoom).push(marker);
+      }
+    }
+    // For the current zoom we also need to update the map. Markers that no
+    // longer are visible are removed from the map. Markers that moved into
+    // the shown bounds are added to the map. This also lets us keep the count
+    // of visible markers up to date.
+    if (zoom === this.mapZoom_) {
+      if (this.isGridPointVisible_(oldGrid)) {
+        if (!this.isGridPointVisible_(newGrid)) {
+          this.removeOverlay_(marker);
+          changed = true;
+        }
+      } else {
+        if (this.isGridPointVisible_(newGrid)) {
+          this.addOverlay_(marker);
+          changed = true;
+        }
+      }
+    }
+    oldGrid.x = oldGrid.x >> 1;
+    oldGrid.y = oldGrid.y >> 1;
+    newGrid.x = newGrid.x >> 1;
+    newGrid.y = newGrid.y >> 1;
+    --zoom;
+  }
+  if (changed) {
+    this.notifyListeners_();
+  }
+};
+
+
+/**
+ * Removes marker from the manager and from the map
+ * (if it's currently visible).
+ * @param {GMarker} marker The marker to delete.
+ */
+MarkerManager.prototype.removeMarker = function (marker) {
+  var zoom = this.maxZoom_;
+  var changed = false;
+  var point = marker.getPosition();
+  var grid = this.getTilePoint_(point, zoom, new google.maps.Size(0, 0, 0, 0));
+  while (zoom >= 0) {
+    var cell = this.getGridCellNoCreate_(grid.x, grid.y, zoom);
+
+    if (cell) {
+      this.removeFromArray_(cell, marker);
+    }
+    // For the current zoom we also need to update the map. Markers that no
+    // longer are visible are removed from the map. This also lets us keep the count
+    // of visible markers up to date.
+    if (zoom === this.mapZoom_) {
+      if (this.isGridPointVisible_(grid)) {
+        this.removeOverlay_(marker);
+        changed = true;
+      }
+    }
+    grid.x = grid.x >> 1;
+    grid.y = grid.y >> 1;
+    --zoom;
+  }
+  if (changed) {
+    this.notifyListeners_();
+  }
+  this.numMarkers_[marker.MarkerManager_minZoom]--;
+};
+
+
+/**
+ * Add many markers at once.
+ * Does not actually update the map, just the internal grid.
+ *
+ * @param {Array of Marker} markers The markers to add.
+ * @param {Number} minZoom The minimum zoom level to display the markers.
+ * @param {Number} opt_maxZoom The maximum zoom level to display the markers.
+ */
+MarkerManager.prototype.addMarkers = function (markers, minZoom, opt_maxZoom) {
+  var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
+  for (var i = markers.length - 1; i >= 0; i--) {
+    this.addMarkerBatch_(markers[i], minZoom, maxZoom);
+  }
+
+  this.numMarkers_[minZoom] += markers.length;
+};
+
+
+/**
+ * Returns the value of the optional maximum zoom. This method is defined so
+ * that we have just one place where optional maximum zoom is calculated.
+ *
+ * @param {Number} opt_maxZoom The optinal maximum zoom.
+ * @return The maximum zoom.
+ */
+MarkerManager.prototype.getOptMaxZoom_ = function (opt_maxZoom) {
+  return opt_maxZoom || this.maxZoom_;
+};
+
+
+/**
+ * Calculates the total number of markers potentially visible at a given
+ * zoom level.
+ *
+ * @param {Number} zoom The zoom level to check.
+ */
+MarkerManager.prototype.getMarkerCount = function (zoom) {
+  var total = 0;
+  for (var z = 0; z <= zoom; z++) {
+    total += this.numMarkers_[z];
+  }
+  return total;
+};
+
+/** 
+ * Returns a marker given latitude, longitude and zoom. If the marker does not 
+ * exist, the method will return a new marker. If a new marker is created, 
+ * it will NOT be added to the manager. 
+ * 
+ * @param {Number} lat - the latitude of a marker. 
+ * @param {Number} lng - the longitude of a marker. 
+ * @param {Number} zoom - the zoom level 
+ * @return {GMarker} marker - the marker found at lat and lng 
+ */ 
+MarkerManager.prototype.getMarker = function (lat, lng, zoom) {
+  var mPoint = new google.maps.LatLng(lat, lng); 
+  var gridPoint = this.getTilePoint_(mPoint, zoom, new google.maps.Size(0, 0, 0, 0));
+
+  var marker = new google.maps.Marker({position: mPoint}); 
+    
+  var cellArray = this.getGridCellNoCreate_(gridPoint.x, gridPoint.y, zoom);
+  if (cellArray !== undefined) {
+    for (var i = 0; i < cellArray.length; i++) 
+    { 
+      if (lat === cellArray[i].getPosition().lat() && lng === cellArray[i].getPosition().lng()) {
+        marker = cellArray[i]; 
+      } 
+    } 
+  } 
+  return marker; 
+}; 
+
+/**
+ * Add a single marker to the map.
+ *
+ * @param {Marker} marker The marker to add.
+ * @param {Number} minZoom The minimum zoom level to display the marker.
+ * @param {Number} opt_maxZoom The maximum zoom level to display the marker.
+ */
+MarkerManager.prototype.addMarker = function (marker, minZoom, opt_maxZoom) {
+  var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
+  this.addMarkerBatch_(marker, minZoom, maxZoom);
+  var gridPoint = this.getTilePoint_(marker.getPosition(), this.mapZoom_, new google.maps.Size(0, 0, 0, 0));
+  if (this.isGridPointVisible_(gridPoint) &&
+      minZoom <= this.shownBounds_.z &&
+      this.shownBounds_.z <= maxZoom) {
+    this.addOverlay_(marker);
+    this.notifyListeners_();
+  }
+  this.numMarkers_[minZoom]++;
+};
+
+
+/**
+ * Helper class to create a bounds of INT ranges.
+ * @param bounds Array.<Object.<string, number>> Bounds object.
+ * @constructor
+ */
+function GridBounds(bounds) {
+  // [sw, ne]
+  
+  this.minX = Math.min(bounds[0].x, bounds[1].x);
+  this.maxX = Math.max(bounds[0].x, bounds[1].x);
+  this.minY = Math.min(bounds[0].y, bounds[1].y);
+  this.maxY = Math.max(bounds[0].y, bounds[1].y);
+      
+}
+
+/**
+ * Returns true if this bounds equal the given bounds.
+ * @param {GridBounds} gridBounds GridBounds The bounds to test.
+ * @return {Boolean} This Bounds equals the given GridBounds.
+ */
+GridBounds.prototype.equals = function (gridBounds) {
+  if (this.maxX === gridBounds.maxX && this.maxY === gridBounds.maxY && this.minX === gridBounds.minX && this.minY === gridBounds.minY) {
+    return true;
+  } else {
+    return false;
+  }  
+};
+
+/**
+ * Returns true if this bounds (inclusively) contains the given point.
+ * @param {Point} point  The point to test.
+ * @return {Boolean} This Bounds contains the given Point.
+ */
+GridBounds.prototype.containsPoint = function (point) {
+  var outer = this;
+  return (outer.minX <= point.x && outer.maxX >= point.x && outer.minY <= point.y && outer.maxY >= point.y);
+};
+
+/**
+ * Get a cell in the grid, creating it first if necessary.
+ *
+ * Optimization candidate
+ *
+ * @param {Number} x The x coordinate of the cell.
+ * @param {Number} y The y coordinate of the cell.
+ * @param {Number} z The z coordinate of the cell.
+ * @return {Array} The cell in the array.
+ */
+MarkerManager.prototype.getGridCellCreate_ = function (x, y, z) {
+  var grid = this.grid_[z];
+  if (x < 0) {
+    x += this.gridWidth_[z];
+  }
+  var gridCol = grid[x];
+  if (!gridCol) {
+    gridCol = grid[x] = [];
+    return (gridCol[y] = []);
+  }
+  var gridCell = gridCol[y];
+  if (!gridCell) {
+    return (gridCol[y] = []);
+  }
+  return gridCell;
+};
+
+
+/**
+ * Get a cell in the grid, returning undefined if it does not exist.
+ *
+ * NOTE: Optimized for speed -- otherwise could combine with getGridCellCreate_.
+ *
+ * @param {Number} x The x coordinate of the cell.
+ * @param {Number} y The y coordinate of the cell.
+ * @param {Number} z The z coordinate of the cell.
+ * @return {Array} The cell in the array.
+ */
+MarkerManager.prototype.getGridCellNoCreate_ = function (x, y, z) {
+  var grid = this.grid_[z];
+  
+  if (x < 0) {
+    x += this.gridWidth_[z];
+  }
+  var gridCol = grid[x];
+  return gridCol ? gridCol[y] : undefined;
+};
+
+
+/**
+ * Turns at geographical bounds into a grid-space bounds.
+ *
+ * @param {LatLngBounds} bounds The geographical bounds.
+ * @param {Number} zoom The zoom level of the bounds.
+ * @param {google.maps.Size} swPadding The padding in pixels to extend beyond the
+ * given bounds.
+ * @param {google.maps.Size} nePadding The padding in pixels to extend beyond the
+ * given bounds.
+ * @return {GridBounds} The bounds in grid space.
+ */
+MarkerManager.prototype.getGridBounds_ = function (bounds, zoom, swPadding, nePadding) {
+  zoom = Math.min(zoom, this.maxZoom_);
+
+  var bl = bounds.getSouthWest();
+  var tr = bounds.getNorthEast();
+  var sw = this.getTilePoint_(bl, zoom, swPadding);
+
+  var ne = this.getTilePoint_(tr, zoom, nePadding);
+  var gw = this.gridWidth_[zoom];
+
+  // Crossing the prime meridian requires correction of bounds.
+  if (tr.lng() < bl.lng() || ne.x < sw.x) {
+    sw.x -= gw;
+  }
+  if (ne.x - sw.x  + 1 >= gw) {
+    // Computed grid bounds are larger than the world; truncate.
+    sw.x = 0;
+    ne.x = gw - 1;
+  }
+
+  var gridBounds = new GridBounds([sw, ne]);
+  gridBounds.z = zoom;
+
+  return gridBounds;
+};
+
+
+/**
+ * Gets the grid-space bounds for the current map viewport.
+ *
+ * @return {Bounds} The bounds in grid space.
+ */
+MarkerManager.prototype.getMapGridBounds_ = function () {
+  return this.getGridBounds_(this.map_.getBounds(), this.mapZoom_, this.swPadding_, this.nePadding_);
+};
+
+
+/**
+ * Event listener for map:movend.
+ * NOTE: Use a timeout so that the user is not blocked
+ * from moving the map.
+ *
+ * Removed this because a a lack of a scopy override/callback function on events. 
+ */
+MarkerManager.prototype.onMapMoveEnd_ = function () {
+  this.objectSetTimeout_(this, this.updateMarkers_, 0);
+};
+
+
+/**
+ * Call a function or evaluate an expression after a specified number of
+ * milliseconds.
+ *
+ * Equivalent to the standard window.setTimeout function, but the given
+ * function executes as a method of this instance. So the function passed to
+ * objectSetTimeout can contain references to this.
+ *    objectSetTimeout(this, function () { alert(this.x) }, 1000);
+ *
+ * @param {Object} object  The target object.
+ * @param {Function} command  The command to run.
+ * @param {Number} milliseconds  The delay.
+ * @return {Boolean}  Success.
+ */
+MarkerManager.prototype.objectSetTimeout_ = function (object, command, milliseconds) {
+  return window.setTimeout(function () {
+    command.call(object);
+  }, milliseconds);
+};
+
+
+/**
+ * Is this layer visible?
+ *
+ * Returns visibility setting
+ *
+ * @return {Boolean} Visible
+ */
+MarkerManager.prototype.visible = function () {
+  return this.show_ ? true : false;
+};
+
+
+/**
+ * Returns true if the manager is hidden.
+ * Otherwise returns false.
+ * @return {Boolean} Hidden
+ */
+MarkerManager.prototype.isHidden = function () {
+  return !this.show_;
+};
+
+
+/**
+ * Shows the manager if it's currently hidden.
+ */
+MarkerManager.prototype.show = function () {
+  this.show_ = true;
+  this.refresh();
+};
+
+
+/**
+ * Hides the manager if it's currently visible
+ */
+MarkerManager.prototype.hide = function () {
+  this.show_ = false;
+  this.refresh();
+};
+
+
+/**
+ * Toggles the visibility of the manager.
+ */
+MarkerManager.prototype.toggle = function () {
+  this.show_ = !this.show_;
+  this.refresh();
+};
+
+
+/**
+ * Refresh forces the marker-manager into a good state.
+ * <ol>
+ *   <li>If never before initialized, shows all the markers.</li>
+ *   <li>If previously initialized, removes and re-adds all markers.</li>
+ * </ol>
+ */
+MarkerManager.prototype.refresh = function () {
+  if (this.shownMarkers_ > 0) {
+    this.processAll_(this.shownBounds_, this.removeOverlay_);
+  }
+  // An extra check on this.show_ to increase performance (no need to processAll_)
+  if (this.show_) {
+    this.processAll_(this.shownBounds_, this.addOverlay_);
+  }
+  this.notifyListeners_();
+};
+
+
+/**
+ * After the viewport may have changed, add or remove markers as needed.
+ */
+MarkerManager.prototype.updateMarkers_ = function () {
+  this.mapZoom_ = this.map_.getZoom();
+  var newBounds = this.getMapGridBounds_();
+    
+  // If the move does not include new grid sections,
+  // we have no work to do:
+  if (newBounds.equals(this.shownBounds_) && newBounds.z === this.shownBounds_.z) {
+    return;
+  }
+
+  if (newBounds.z !== this.shownBounds_.z) {
+    this.processAll_(this.shownBounds_, this.removeOverlay_);
+    if (this.show_) { // performance
+      this.processAll_(newBounds, this.addOverlay_);
+    }
+  } else {
+    // Remove markers:
+    this.rectangleDiff_(this.shownBounds_, newBounds, this.removeCellMarkers_);
+
+    // Add markers:
+    if (this.show_) { // performance
+      this.rectangleDiff_(newBounds, this.shownBounds_, this.addCellMarkers_);
+    }
+  }
+  this.shownBounds_ = newBounds;
+
+  this.notifyListeners_();
+};
+
+
+/**
+ * Notify listeners when the state of what is displayed changes.
+ */
+MarkerManager.prototype.notifyListeners_ = function () {
+  google.maps.event.trigger(this, 'changed', this.shownBounds_, this.shownMarkers_);
+};
+
+
+/**
+ * Process all markers in the bounds provided, using a callback.
+ *
+ * @param {Bounds} bounds The bounds in grid space.
+ * @param {Function} callback The function to call for each marker.
+ */
+MarkerManager.prototype.processAll_ = function (bounds, callback) {
+  for (var x = bounds.minX; x <= bounds.maxX; x++) {
+    for (var y = bounds.minY; y <= bounds.maxY; y++) {
+      this.processCellMarkers_(x, y,  bounds.z, callback);
+    }
+  }
+};
+
+
+/**
+ * Process all markers in the grid cell, using a callback.
+ *
+ * @param {Number} x The x coordinate of the cell.
+ * @param {Number} y The y coordinate of the cell.
+ * @param {Number} z The z coordinate of the cell.
+ * @param {Function} callback The function to call for each marker.
+ */
+MarkerManager.prototype.processCellMarkers_ = function (x, y, z, callback) {
+  var cell = this.getGridCellNoCreate_(x, y, z);
+  if (cell) {
+    for (var i = cell.length - 1; i >= 0; i--) {
+      callback(cell[i]);
+    }
+  }
+};
+
+
+/**
+ * Remove all markers in a grid cell.
+ *
+ * @param {Number} x The x coordinate of the cell.
+ * @param {Number} y The y coordinate of the cell.
+ * @param {Number} z The z coordinate of the cell.
+ */
+MarkerManager.prototype.removeCellMarkers_ = function (x, y, z) {
+  this.processCellMarkers_(x, y, z, this.removeOverlay_);
+};
+
+
+/**
+ * Add all markers in a grid cell.
+ *
+ * @param {Number} x The x coordinate of the cell.
+ * @param {Number} y The y coordinate of the cell.
+ * @param {Number} z The z coordinate of the cell.
+ */
+MarkerManager.prototype.addCellMarkers_ = function (x, y, z) {
+  this.processCellMarkers_(x, y, z, this.addOverlay_);
+};
+
+
+/**
+ * Use the rectangleDiffCoords_ function to process all grid cells
+ * that are in bounds1 but not bounds2, using a callback, and using
+ * the current MarkerManager object as the instance.
+ *
+ * Pass the z parameter to the callback in addition to x and y.
+ *
+ * @param {Bounds} bounds1 The bounds of all points we may process.
+ * @param {Bounds} bounds2 The bounds of points to exclude.
+ * @param {Function} callback The callback function to call
+ *                   for each grid coordinate (x, y, z).
+ */
+MarkerManager.prototype.rectangleDiff_ = function (bounds1, bounds2, callback) {
+  var me = this;
+  me.rectangleDiffCoords_(bounds1, bounds2, function (x, y) {
+    callback.apply(me, [x, y, bounds1.z]);
+  });
+};
+
+
+/**
+ * Calls the function for all points in bounds1, not in bounds2
+ *
+ * @param {Bounds} bounds1 The bounds of all points we may process.
+ * @param {Bounds} bounds2 The bounds of points to exclude.
+ * @param {Function} callback The callback function to call
+ *                   for each grid coordinate.
+ */
+MarkerManager.prototype.rectangleDiffCoords_ = function (bounds1, bounds2, callback) {
+  var minX1 = bounds1.minX;
+  var minY1 = bounds1.minY;
+  var maxX1 = bounds1.maxX;
+  var maxY1 = bounds1.maxY;
+  var minX2 = bounds2.minX;
+  var minY2 = bounds2.minY;
+  var maxX2 = bounds2.maxX;
+  var maxY2 = bounds2.maxY;
+
+  var x, y;
+  for (x = minX1; x <= maxX1; x++) {  // All x in R1
+    // All above:
+    for (y = minY1; y <= maxY1 && y < minY2; y++) {  // y in R1 above R2
+      callback(x, y);
+    }
+    // All below:
+    for (y = Math.max(maxY2 + 1, minY1);  // y in R1 below R2
+         y <= maxY1; y++) {
+      callback(x, y);
+    }
+  }
+
+  for (y = Math.max(minY1, minY2);
+       y <= Math.min(maxY1, maxY2); y++) {  // All y in R2 and in R1
+    // Strictly left:
+    for (x = Math.min(maxX1 + 1, minX2) - 1;
+         x >= minX1; x--) {  // x in R1 left of R2
+      callback(x, y);
+    }
+    // Strictly right:
+    for (x = Math.max(minX1, maxX2 + 1);  // x in R1 right of R2
+         x <= maxX1; x++) {
+      callback(x, y);
+    }
+  }
+};
+
+
+/**
+ * Removes value from array. O(N).
+ *
+ * @param {Array} array  The array to modify.
+ * @param {any} value  The value to remove.
+ * @param {Boolean} opt_notype  Flag to disable type checking in equality.
+ * @return {Number}  The number of instances of value that were removed.
+ */
+MarkerManager.prototype.removeFromArray_ = function (array, value, opt_notype) {
+  var shift = 0;
+  for (var i = 0; i < array.length; ++i) {
+    if (array[i] === value || (opt_notype && array[i] === value)) {
+      array.splice(i--, 1);
+      shift++;
+    }
+  }
+  return shift;
+};
+
+
+
+
+
+
+
+/**
+*   Projection overlay helper. Helps in calculating
+*   that markers get into the right grid.
+*   @constructor
+*   @param {Map} map The map to manage.
+**/
+function ProjectionHelperOverlay(map) {
+  
+  this.setMap(map);
+
+  var TILEFACTOR = 8;
+  var TILESIDE = 1 << TILEFACTOR;
+  var RADIUS = 7;
+
+  this._map = map;
+  this._zoom = -1;
+  this._X0 =
+  this._Y0 =
+  this._X1 =
+  this._Y1 = -1;
+
+  
+}
+ProjectionHelperOverlay.prototype = new google.maps.OverlayView();
+
+/**
+ *  Helper function to convert Lng to X
+ *  @private
+ *  @param {float} lng
+ **/
+ProjectionHelperOverlay.prototype.LngToX_ = function (lng) {
+  return (1 + lng / 180);
+};
+
+/**
+ *  Helper function to convert Lat to Y
+ *  @private
+ *  @param {float} lat
+ **/
+ProjectionHelperOverlay.prototype.LatToY_ = function (lat) {
+  var sinofphi = Math.sin(lat * Math.PI / 180);
+  return (1 - 0.5 / Math.PI * Math.log((1 + sinofphi) / (1 - sinofphi)));
+};
+
+/**
+*   Old school LatLngToPixel
+*   @param {LatLng} latlng google.maps.LatLng object
+*   @param {Number} zoom Zoom level
+*   @return {position} {x: pixelPositionX, y: pixelPositionY}
+**/
+ProjectionHelperOverlay.prototype.LatLngToPixel = function (latlng, zoom) {
+  var map = this._map;
+  var div = this.getProjection().fromLatLngToDivPixel(latlng);
+  var abs = {x: ~~(0.5 + this.LngToX_(latlng.lng()) * (2 << (zoom + 6))), y: ~~(0.5 + this.LatToY_(latlng.lat()) * (2 << (zoom + 6)))};
+  return abs;
+};
+
+
+/**
+ * Draw function only triggers a ready event for
+ * MarkerManager to know projection can proceed to
+ * initialize.
+ */
+ProjectionHelperOverlay.prototype.draw = function () {
+  if (!this.ready) {
+    this.ready = true;
+    google.maps.event.trigger(this, 'ready');
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/markermanager_packed.js	Mon Mar 10 01:42:59 2014 +0200
@@ -0,0 +1,1 @@
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('9 m(c,b){4 a=3;a.18=c;a.Q=c.1G();a.1e=u K(c);q.p.B.X(a.1e,\'16\',9(){a.2m=3.1F();a.1C(c,b)})}m.l.1C=9(d,b){4 f=3;b=b||{};f.13=m.1s;4 h=d.1p;4 i=1;t(4 c 2H h){n(h.2A(c)&&h.1l(c)&&h.1l(c).1h===\'1I\'){4 g=d.1p.1l(c).1h;n(g>i){i=g}}}f.G=b.1h||19;f.1O=b.2b;f.v=b.1B||C;4 e;n(27 b.1w===\'1I\'){e=b.1w}14{e=m.1u}f.1t=u q.p.D(-e,e);f.1r=u q.p.D(e,-e);f.26=e;f.L={};f.T={};f.T[f.G]={};f.J={};f.J[f.G]=0;q.p.B.X(d,\'1Z\',9(){f.12()});q.p.B.X(d,\'1X\',9(){f.12()});q.p.B.X(d,\'1V\',9(){f.12()});f.I=9(a){a.1k(2F);f.W--};f.O=9(a){n(f.v){a.1k(f.18);f.W++}};f.1f();f.W=0;f.r=f.1j();q.p.B.1g(f,\'2s\')};m.1s=2q;m.1u=2o;m.1H=2l;m.l.1f=9(){4 a=m.1H;t(4 b=0;b<=3.G;++b){3.T[b]={};3.J[b]=0;3.L[b]=s.2i(a/3.13);a<<=1}};m.l.2g=9(){3.P(3.r,3.I);3.1f()};m.l.w=9(a,c,b){4 d=3.1e.1D(a,c);4 e=u q.p.2c(s.1Q((d.x+b.29)/3.13),s.1Q((d.y+b.28)/3.13));o e};m.l.1d=9(i,d,j){4 f=3;4 e=i.V();i.1z=d;n(3.1O){q.p.B.X(i,\'1y\',9(a,b,c){f.1x(a,b,c)})}4 h=3.w(e,j,u q.p.D(0,0,0,0));t(4 g=j;g>=d;g--){4 k=3.1c(h.x,h.y,g);k.1v(i);h.x=h.x>>1;h.y=h.y>>1}};m.l.N=9(d){4 b=3.r.E<=d.y&&d.y<=3.r.F;4 e=3.r.A;4 c=e<=d.x&&d.x<=3.r.H;n(!c&&e<0){4 a=3.L[3.r.z];c=e+a<=d.x&&d.x<=a-1}o b&&c};m.l.1x=9(f,b,g){4 c=3.G;4 a=U;4 d=3.w(b,c,u q.p.D(0,0,0,0));4 e=3.w(g,c,u q.p.D(0,0,0,0));1q(c>=0&&(d.x!==e.x||d.y!==e.y)){4 h=3.Y(d.x,d.y,c);n(h){n(3.1b(h,f)){3.1c(e.x,e.y,c).1v(f)}}n(c===3.Q){n(3.N(d)){n(!3.N(e)){3.I(f);a=C}}14{n(3.N(e)){3.O(f);a=C}}}d.x=d.x>>1;d.y=d.y>>1;e.x=e.x>>1;e.y=e.y>>1;--c}n(a){3.M()}};m.l.25=9(d){4 b=3.G;4 a=U;4 e=d.V();4 c=3.w(e,b,u q.p.D(0,0,0,0));1q(b>=0){4 f=3.Y(c.x,c.y,b);n(f){3.1b(f,d)}n(b===3.Q){n(3.N(c)){3.I(d);a=C}}c.x=c.x>>1;c.y=c.y>>1;--b}n(a){3.M()}3.J[d.1z]--};m.l.24=9(b,a,c){4 d=3.1a(c);t(4 i=b.Z-1;i>=0;i--){3.1d(b[i],a,d)}3.J[a]+=b.Z};m.l.1a=9(a){o a||3.G};m.l.23=9(a){4 b=0;t(4 z=0;z<=a;z++){b+=3.J[z]}o b};m.l.22=9(c,e,d){4 b=u q.p.21(c,e);4 f=3.w(b,d,u q.p.D(0,0,0,0));4 g=u q.p.20({1Y:b});4 a=3.Y(f.x,f.y,d);n(a!==1n){t(4 i=0;i<a.Z;i++){n(c===a[i].V().1m()&&e===a[i].V().10()){g=a[i]}}}o g};m.l.1W=9(d,a,b){4 e=3.1a(b);3.1d(d,a,e);4 c=3.w(d.V(),3.Q,u q.p.D(0,0,0,0));n(3.N(c)&&a<=3.r.z&&3.r.z<=e){3.O(d);3.M()}3.J[a]++};9 11(a){3.A=s.R(a[0].x,a[1].x);3.H=s.S(a[0].x,a[1].x);3.E=s.R(a[0].y,a[1].y);3.F=s.S(a[0].y,a[1].y)}11.l.1o=9(a){n(3.H===a.H&&3.F===a.F&&3.A===a.A&&3.E===a.E){o C}14{o U}};11.l.1U=9(a){4 b=3;o(b.A<=a.x&&b.H>=a.x&&b.E<=a.y&&b.F>=a.y)};m.l.1c=9(x,y,z){4 b=3.T[z];n(x<0){x+=3.L[z]}4 c=b[x];n(!c){c=b[x]=[];o(c[y]=[])}4 a=c[y];n(!a){o(c[y]=[])}o a};m.l.Y=9(x,y,z){4 a=3.T[z];n(x<0){x+=3.L[z]}4 b=a[x];o b?b[y]:1n};m.l.1S=9(j,b,c,e){b=s.R(b,3.G);4 i=j.2G();4 f=j.2E();4 d=3.w(i,b,c);4 g=3.w(f,b,e);4 a=3.L[b];n(f.10()<i.10()||g.x<d.x){d.x-=a}n(g.x-d.x+1>=a){d.x=0;g.x=a-1}4 h=u 11([d,g]);h.z=b;o h};m.l.1j=9(){o 3.1S(3.18.2D(),3.Q,3.1t,3.1r)};m.l.12=9(){3.1R(3,3.1A,0)};m.l.1R=9(b,a,c){o 2C.2B(9(){a.2z(b)},c)};m.l.2y=9(){o 3.v?C:U};m.l.2x=9(){o!3.v};m.l.1B=9(){3.v=C;3.17()};m.l.2w=9(){3.v=U;3.17()};m.l.2u=9(){3.v=!3.v;3.17()};m.l.17=9(){n(3.W>0){3.P(3.r,3.I)}n(3.v){3.P(3.r,3.O)}3.M()};m.l.1A=9(){3.Q=3.18.1G();4 a=3.1j();n(a.1o(3.r)&&a.z===3.r.z){o}n(a.z!==3.r.z){3.P(3.r,3.I);n(3.v){3.P(a,3.O)}}14{3.1i(3.r,a,3.1N);n(3.v){3.1i(a,3.r,3.1L)}}3.r=a;3.M()};m.l.M=9(){q.p.B.1g(3,\'1y\',3.r,3.W)};m.l.P=9(b,a){t(4 x=b.A;x<=b.H;x++){t(4 y=b.E;y<=b.F;y++){3.15(x,y,b.z,a)}}};m.l.15=9(x,y,z,a){4 b=3.Y(x,y,z);n(b){t(4 i=b.Z-1;i>=0;i--){a(b[i])}}};m.l.1N=9(x,y,z){3.15(x,y,z,3.I)};m.l.1L=9(x,y,z){3.15(x,y,z,3.O)};m.l.1i=9(c,d,a){4 b=3;b.1K(c,d,9(x,y){a.2p(b,[x,y,c.z])})};m.l.1K=9(j,k,b){4 f=j.A;4 a=j.E;4 d=j.H;4 h=j.F;4 g=k.A;4 c=k.E;4 e=k.H;4 i=k.F;4 x,y;t(x=f;x<=d;x++){t(y=a;y<=h&&y<c;y++){b(x,y)}t(y=s.S(i+1,a);y<=h;y++){b(x,y)}}t(y=s.S(a,c);y<=s.R(h,i);y++){t(x=s.R(d+1,g)-1;x>=f;x--){b(x,y)}t(x=s.S(f,e+1);x<=d;x++){b(x,y)}}};m.l.1b=9(a,c,b){4 d=0;t(4 i=0;i<a.Z;++i){n(a[i]===c||(b&&a[i]===c)){a.2n(i--,1);d++}}o d};9 K(b){3.1k(b);4 d=8;4 c=1<<d;4 a=7;3.1J=b;3.2k=-1;3.2r=3.2j=3.2t=3.2h=-1}K.l=u q.p.2v();K.l.1M=9(a){o(1+a/1T)};K.l.1P=9(b){4 a=s.2f(b*s.1E/1T);o(1-0.5/s.1E*s.2e((1+a)/(1-a)))};K.l.1D=9(a,d){4 c=3.1J;4 b=3.1F().2d(a);4 e={x:~~(0.5+3.1M(a.10())*(2<<(d+6))),y:~~(0.5+3.1P(a.1m())*(2<<(d+6)))};o e};K.l.2a=9(){n(!3.16){3.16=C;q.p.B.1g(3,\'16\')}};',62,168,'|||this|var|||||function||||||||||||prototype|MarkerManager|if|return|maps|google|shownBounds_|Math|for|new|show_|getTilePoint_||||minX|event|true|Size|minY|maxY|maxZoom_|maxX|removeOverlay_|numMarkers_|ProjectionHelperOverlay|gridWidth_|notifyListeners_|isGridPointVisible_|addOverlay_|processAll_|mapZoom_|min|max|grid_|false|getPosition|shownMarkers_|addListener|getGridCellNoCreate_|length|lng|GridBounds|onMapMoveEnd_|tileSize_|else|processCellMarkers_|ready|refresh|map_||getOptMaxZoom_|removeFromArray_|getGridCellCreate_|addMarkerBatch_|projectionHelper_|resetManager_|trigger|maxZoom|rectangleDiff_|getMapGridBounds_|setMap|get|lat|undefined|equals|mapTypes|while|nePadding_|DEFAULT_TILE_SIZE_|swPadding_|DEFAULT_BORDER_PADDING_|push|borderPadding|onMarkerMoved_|changed|MarkerManager_minZoom|updateMarkers_|show|initialize|LatLngToPixel|PI|getProjection|getZoom|MERCATOR_ZOOM_LEVEL_ZERO_RANGE|number|_map|rectangleDiffCoords_|addCellMarkers_|LngToX_|removeCellMarkers_|trackMarkers_|LatToY_|floor|objectSetTimeout_|getGridBounds_|180|containsPoint|zoom_changed|addMarker|idle|position|dragend|Marker|LatLng|getMarker|getMarkerCount|addMarkers|removeMarker|borderPadding_|typeof|height|width|draw|trackMarkers|Point|fromLatLngToDivPixel|log|sin|clearMarkers|_Y1|ceil|_Y0|_zoom|256|projection_|splice|100|apply|1024|_X0|loaded|_X1|toggle|OverlayView|hide|isHidden|visible|call|hasOwnProperty|setTimeout|window|getBounds|getNorthEast|null|getSouthWest|in'.split('|'),0,{}))
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/util.js	Mon Mar 10 01:42:59 2014 +0200
@@ -0,0 +1,52 @@
+/**
+* Returns an XMLHttp instance to use for asynchronous
+* downloading. This method will never throw an exception, but will
+* return NULL if the browser does not support XmlHttp for any reason.
+* @return {XMLHttpRequest|Null}
+*/
+function XCreateXmlHTTPRequest() {
+ try {
+   if (typeof ActiveXObject != 'undefined') {
+     return new ActiveXObject('Microsoft.XMLHTTP');
+   } else if (window["XMLHttpRequest"]) {
+     return new XMLHttpRequest();
+   }
+ } catch (e) {
+   changeStatus(e);
+ }
+ return null;
+};
+
+/**
+* This functions wraps XMLHttpRequest open/send function.
+* It lets you specify a URL and will call the callback if
+* it gets a status code of 200.
+* @param {String} url The URL to retrieve
+* @param {Function} callback The function to call once retrieved.
+*/
+function XDownloadUrl(url, callback) {
+ var status = -1;
+ var request = XCreateXmlHTTPRequest();
+ if (!request)
+   return false;
+
+ request.onreadystatechange = function() {
+   if (request.readyState == 4) {
+     try {
+       status = request.status;
+     } catch (e) {
+       // Usually indicates request timed out in FF.
+     }
+     if (status == 200) {
+       callback(request.responseText, request.status);
+       request.onreadystatechange = function() {};
+     }
+   }
+ }
+ request.open('GET', url, true);
+ try {
+   request.send(null);
+ } catch (e) {
+   changeStatus(e);
+ }
+};