Mercurial > hg > batmud > gmap2
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); + } +};