| /** |
| * @name MarkerManager v3 |
| * @version 1.0 |
| * @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 (typeof map.mapTypes.get(sType) === 'object' && typeof map.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, '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].getLatLng().lat() && lng === cellArray[i].getLatLng().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; |
| |
| |
| } |
| if (typeof(google) != 'undefined' && google.maps) { // make sure it exists -- amalo |
| 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'); |
| } |
| }; |