From: Thierry Parmentelat Date: Tue, 17 Sep 2013 09:42:44 +0000 (+0200) Subject: imported latest release of markercluster from google X-Git-Tag: myslice-0.2-3~9^2~1 X-Git-Url: http://git.onelab.eu/?p=myslice.git;a=commitdiff_plain;h=55f628fd2398b231159c148e1dee8302e255640e imported latest release of markercluster from google --- diff --git a/Makefile b/Makefile index 235336b7..d011fe41 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,9 @@ THIRD-PARTY-RESOURCES += $(shell ls third-party/codemirror-3.15/lib/codemirror.c THIRD-PARTY-RESOURCES += $(shell ls third-party/codemirror-3.15/mode/sql/sql.js) # Mustache.js THIRD-PARTY-RESOURCES += $(shell ls third-party/mustache/mustache.js) - +# markerclustererplus for the googlemap plugin +THIRD-PARTY-RESOURCES += $(shell ls third-party/markerclusterer/markerclusterer.js) +THIRD-PARTY-RESOURCES += $(shell ls third-party/markerclusterer/markerclusterer_packed.js) thirdparty-js: @find $(THIRD-PARTY-RESOURCES) -name '*.js' diff --git a/plugins/googlemaps/static/js/markerclusterer.js b/plugins/googlemaps/static/js/markerclusterer.js deleted file mode 100644 index 8ac6d8fe..00000000 --- a/plugins/googlemaps/static/js/markerclusterer.js +++ /dev/null @@ -1,1137 +0,0 @@ -// ==ClosureCompiler== -// @compilation_level ADVANCED_OPTIMIZATIONS -// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js -// ==/ClosureCompiler== - -/** - * @name MarkerClusterer for Google Maps v3 - * @version version 1.0 - * @author Luke Mahe - * @fileoverview - * The library creates and manages per-zoom-level clusters for large amounts of - * markers. - *
- * This is a v3 implementation of the - * v2 MarkerClusterer. - */ - -/** - * 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. - */ - - -/** - * A Marker Clusterer that clusters markers. - * - * @param {google.maps.Map} map The Google map to attach to. - * @param {Array.} opt_markers Optional markers to add to - * the cluster. - * @param {Object} opt_options support the following options: - * 'gridSize': (number) The grid size of a cluster in pixels. - * 'maxZoom': (number) The maximum zoom level that a marker can be part of a - * cluster. - * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a - * cluster is to zoom into it. - * 'averageCenter': (boolean) Wether the center of each cluster should be - * the average of all markers in the cluster. - * 'styles': (object) An object that has style properties: - * 'url': (string) The image url. - * 'height': (number) The image height. - * 'width': (number) The image width. - * 'anchor': (Array) The anchor position of the label text. - * 'textColor': (string) The text color. - * 'textSize': (number) The text size. - * @constructor - * @extends google.maps.OverlayView - */ -function MarkerClusterer(map, opt_markers, opt_options) { - // MarkerClusterer implements google.maps.OverlayView interface. We use the - // extend function to extend MarkerClusterer with google.maps.OverlayView - // because it might not always be available when the code is defined so we - // look for it at the last possible moment. If it doesn't exist now then - // there is no point going ahead :) - this.extend(MarkerClusterer, google.maps.OverlayView); - this.map_ = map; - - /** - * @type {Array.} - * @private - */ - this.markers_ = []; - - /** - * @type {Array.} - */ - this.clusters_ = []; - - this.sizes = [53, 56, 66, 78, 90]; - - /** - * @private - */ - this.styles_ = []; - - /** - * @type {boolean} - * @private - */ - this.ready_ = false; - - var options = opt_options || {}; - - /** - * @type {number} - * @private - */ - this.gridSize_ = options['gridSize'] || 60; - - /** - * @type {?number} - * @private - */ - this.maxZoom_ = options['maxZoom'] || null; - - this.styles_ = options['styles'] || []; - - /** - * @type {string} - * @private - */ - this.imagePath_ = options['imagePath'] || - this.MARKER_CLUSTER_IMAGE_PATH_; - - /** - * @type {string} - * @private - */ - this.imageExtension_ = options['imageExtension'] || - this.MARKER_CLUSTER_IMAGE_EXTENSION_; - - /** - * @type {boolean} - * @private - */ - this.zoomOnClick_ = true; - - if (options['zoomOnClick'] != undefined) { - this.zoomOnClick_ = options['zoomOnClick']; - } - - /** - * @type {boolean} - * @private - */ - this.averageCenter_ = false; - - if (options['averageCenter'] != undefined) { - this.averageCenter_ = options['averageCenter']; - } - - this.setupStyles_(); - - this.setMap(map); - - /** - * @type {number} - * @private - */ - this.prevZoom_ = this.map_.getZoom(); - - // Add the map event listeners - var that = this; - google.maps.event.addListener(this.map_, 'zoom_changed', function() { - var maxZoom = that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom; - var zoom = that.map_.getZoom(); - if (zoom < 0 || zoom > maxZoom) { - return; - } - - if (that.prevZoom_ != zoom) { - that.prevZoom_ = that.map_.getZoom(); - that.resetViewport(); - } - }); - - google.maps.event.addListener(this.map_, 'idle', function() { - that.redraw(); - }); - - // Finally, add the markers - if (opt_markers && opt_markers.length) { - this.addMarkers(opt_markers, false); - } -} - - -/** - * The marker cluster image path. - * - * @type {string} - * @private - */ -MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = - 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/' + - 'images/m'; - - -/** - * The marker cluster image path. - * - * @type {string} - * @private - */ -MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; - - -/** - * Extends a objects prototype by anothers. - * - * @param {Object} obj1 The object to be extended. - * @param {Object} obj2 The object to extend with. - * @return {Object} The new extended object. - * @ignore - */ -MarkerClusterer.prototype.extend = function(obj1, obj2) { - return (function(object) { - for (property in object.prototype) { - this.prototype[property] = object.prototype[property]; - } - return this; - }).apply(obj1, [obj2]); -}; - - -/** - * Implementaion of the interface method. - * @ignore - */ -MarkerClusterer.prototype.onAdd = function() { - this.setReady_(true); -}; - - -/** - * Implementation of the interface. - * @ignore - */ -MarkerClusterer.prototype.draw = function() {}; - - -/** - * Sets up the styles object. - * - * @private - */ -MarkerClusterer.prototype.setupStyles_ = function() { - if (this.styles_.length) { - return; - } - - for (var i = 0, size; size = this.sizes[i]; i++) { - this.styles_.push({ - url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, - height: size, - width: size - }); - } -}; - - -/** - * Sets the styles. - * - * @param {Object} styles The style to set. - */ -MarkerClusterer.prototype.setStyles = function(styles) { - this.styles_ = styles; -}; - - -/** - * Gets the styles. - * - * @return {Object} The styles object. - */ -MarkerClusterer.prototype.getStyles = function() { - return this.styles_; -}; - - -/** - * Whether zoom on click is set. - * - * @return {boolean} True if zoomOnClick_ is set. - */ -MarkerClusterer.prototype.isZoomOnClick = function() { - return this.zoomOnClick_; -}; - -/** - * Whether average center is set. - * - * @return {boolean} True if averageCenter_ is set. - */ -MarkerClusterer.prototype.isAverageCenter = function() { - return this.averageCenter_; -}; - - -/** - * Returns the array of markers in the clusterer. - * - * @return {Array.} The markers. - */ -MarkerClusterer.prototype.getMarkers = function() { - return this.markers_; -}; - - -/** - * Returns the array of markers in the clusterer. - * - * @return {Array.} The number of markers. - */ -MarkerClusterer.prototype.getTotalMarkers = function() { - return this.markers_; -}; - - -/** - * Sets the max zoom for the clusterer. - * - * @param {number} maxZoom The max zoom level. - */ -MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { - this.maxZoom_ = maxZoom; -}; - - -/** - * Gets the max zoom for the clusterer. - * - * @return {number} The max zoom level. - */ -MarkerClusterer.prototype.getMaxZoom = function() { - return this.maxZoom_ || this.map_.mapTypes[this.map_.getMapTypeId()].maxZoom; -}; - - -/** - * The function for calculating the cluster icon image. - * - * @param {Array.} markers The markers in the clusterer. - * @param {number} numStyles The number of styles available. - * @return {Object} A object properties: 'text' (string) and 'index' (number). - * @private - */ -MarkerClusterer.prototype.calculator_ = function(markers, numStyles) { - var index = 0; - var count = markers.length; - var dv = count; - while (dv !== 0) { - dv = parseInt(dv / 10, 10); - index++; - } - - index = Math.min(index, numStyles); - return { - text: count, - index: index - }; -}; - - -/** - * Set the calculator function. - * - * @param {function(Array, number)} calculator The function to set as the - * calculator. The function should return a object properties: - * 'text' (string) and 'index' (number). - * - */ -MarkerClusterer.prototype.setCalculator = function(calculator) { - this.calculator_ = calculator; -}; - - -/** - * Get the calculator function. - * - * @return {function(Array, number)} the calculator function. - */ -MarkerClusterer.prototype.getCalculator = function() { - return this.calculator_; -}; - - -/** - * Add an array of markers to the clusterer. - * - * @param {Array.} markers The markers to add. - * @param {boolean} opt_nodraw Whether to redraw the clusters. - */ -MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) { - for (var i = 0, marker; marker = markers[i]; i++) { - this.pushMarkerTo_(marker); - } - if (!opt_nodraw) { - this.redraw(); - } -}; - - -/** - * Pushes a marker to the clusterer. - * - * @param {google.maps.Marker} marker The marker to add. - * @private - */ -MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { - marker.setVisible(false); - marker.setMap(null); - marker.isAdded = false; - if (marker['draggable']) { - // If the marker is draggable add a listener so we update the clusters on - // the drag end. - var that = this; - google.maps.event.addListener(marker, 'dragend', function() { - marker.isAdded = false; - that.resetViewport(); - that.redraw(); - }); - } - this.markers_.push(marker); -}; - - -/** - * Adds a marker to the clusterer and redraws if needed. - * - * @param {google.maps.Marker} marker The marker to add. - * @param {boolean} opt_nodraw Whether to redraw the clusters. - */ -MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { - this.pushMarkerTo_(marker); - if (!opt_nodraw) { - this.redraw(); - } -}; - - -/** - * Remove a marker from the cluster. - * - * @param {google.maps.Marker} marker The marker to remove. - * @return {boolean} True if the marker was removed. - */ -MarkerClusterer.prototype.removeMarker = function(marker) { - var index = -1; - if (this.markers_.indexOf) { - index = this.markers_.indexOf(marker); - } else { - for (var i = 0, m; m = this.markers_[i]; i++) { - if (m == marker) { - index = i; - continue; - } - } - } - - if (index == -1) { - // Marker is not in our list of markers. - return false; - } - - this.markers_.splice(index, 1); - marker.setVisible(false); - marker.setMap(null); - - this.resetViewport(); - this.redraw(); - return true; -}; - - -/** - * Sets the clusterer's ready state. - * - * @param {boolean} ready The state. - * @private - */ -MarkerClusterer.prototype.setReady_ = function(ready) { - if (!this.ready_) { - this.ready_ = ready; - this.createClusters_(); - } -}; - - -/** - * Returns the number of clusters in the clusterer. - * - * @return {number} The number of clusters. - */ -MarkerClusterer.prototype.getTotalClusters = function() { - return this.clusters_.length; -}; - - -/** - * Returns the google map that the clusterer is associated with. - * - * @return {google.maps.Map} The map. - */ -MarkerClusterer.prototype.getMap = function() { - return this.map_; -}; - - -/** - * Sets the google map that the clusterer is associated with. - * - * @param {google.maps.Map} map The map. - */ -MarkerClusterer.prototype.setMap = function(map) { - this.map_ = map; -}; - - -/** - * Returns the size of the grid. - * - * @return {number} The grid size. - */ -MarkerClusterer.prototype.getGridSize = function() { - return this.gridSize_; -}; - - -/** - * Returns the size of the grid. - * - * @param {number} size The grid size. - */ -MarkerClusterer.prototype.setGridSize = function(size) { - this.gridSize_ = size; -}; - - -/** - * Extends a bounds object by the grid size. - * - * @param {google.maps.LatLngBounds} bounds The bounds to extend. - * @return {google.maps.LatLngBounds} The extended bounds. - */ -MarkerClusterer.prototype.getExtendedBounds = function(bounds) { - var projection = this.getProjection(); - - // Turn the bounds into latlng. - var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), - bounds.getNorthEast().lng()); - var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), - bounds.getSouthWest().lng()); - - // Convert the points to pixels and the extend out by the grid size. - var trPix = projection.fromLatLngToDivPixel(tr); - trPix.x += this.gridSize_; - trPix.y -= this.gridSize_; - - var blPix = projection.fromLatLngToDivPixel(bl); - blPix.x -= this.gridSize_; - blPix.y += this.gridSize_; - - // Convert the pixel points back to LatLng - var ne = projection.fromDivPixelToLatLng(trPix); - var sw = projection.fromDivPixelToLatLng(blPix); - - // Extend the bounds to contain the new bounds. - bounds.extend(ne); - bounds.extend(sw); - - return bounds; -}; - - -/** - * Determins if a marker is contained in a bounds. - * - * @param {google.maps.Marker} marker The marker to check. - * @param {google.maps.LatLngBounds} bounds The bounds to check against. - * @return {boolean} True if the marker is in the bounds. - * @private - */ -MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { - return bounds.contains(marker.getPosition()); -}; - - -/** - * Clears all clusters and markers from the clusterer. - */ -MarkerClusterer.prototype.clearMarkers = function() { - this.resetViewport(); - - // Set the markers a empty array. - this.markers_ = []; -}; - - -/** - * Clears all existing clusters and recreates them. - */ -MarkerClusterer.prototype.resetViewport = function() { - // Remove all the clusters - for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { - cluster.remove(); - } - - // Reset the markers to not be added and to be invisible. - for (var i = 0, marker; marker = this.markers_[i]; i++) { - marker.isAdded = false; - marker.setMap(null); - marker.setVisible(false); - } - - this.clusters_ = []; -}; - - -/** - * Redraws the clusters. - */ -MarkerClusterer.prototype.redraw = function() { - this.createClusters_(); -}; - - -/** - * Creates the clusters. - * - * @private - */ -MarkerClusterer.prototype.createClusters_ = function() { - if (!this.ready_) { - return; - } - - // Get our current map view bounds. - // Create a new bounds object so we don't affect the map. - var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), - this.map_.getBounds().getNorthEast()); - var bounds = this.getExtendedBounds(mapBounds); - - for (var i = 0, marker; marker = this.markers_[i]; i++) { - var added = false; - if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { - for (var j = 0, cluster; cluster = this.clusters_[j]; j++) { - if (!added && cluster.getCenter() && - cluster.isMarkerInClusterBounds(marker)) { - added = true; - cluster.addMarker(marker); - break; - } - } - - if (!added) { - // Create a new cluster. - var cluster = new Cluster(this); - cluster.addMarker(marker); - this.clusters_.push(cluster); - } - } - } -}; - - -/** - * A cluster that contains markers. - * - * @param {MarkerClusterer} markerClusterer The markerclusterer that this - * cluster is associated with. - * @constructor - * @ignore - */ -function Cluster(markerClusterer) { - this.markerClusterer_ = markerClusterer; - this.map_ = markerClusterer.getMap(); - this.gridSize_ = markerClusterer.getGridSize(); - this.averageCenter_ = markerClusterer.isAverageCenter(); - this.center_ = null; - this.markers_ = []; - this.bounds_ = null; - this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), - markerClusterer.getGridSize()); -} - -/** - * Determins if a marker is already added to the cluster. - * - * @param {google.maps.Marker} marker The marker to check. - * @return {boolean} True if the marker is already added. - */ -Cluster.prototype.isMarkerAlreadyAdded = function(marker) { - if (this.markers_.indexOf) { - return this.markers_.indexOf(marker) != -1; - } else { - for (var i = 0, m; m = this.markers_[i]; i++) { - if (m == marker) { - return true; - } - } - } - return false; -}; - - -/** - * Add a marker the cluster. - * - * @param {google.maps.Marker} marker The marker to add. - * @return {boolean} True if the marker was added. - */ -Cluster.prototype.addMarker = function(marker) { - if (this.isMarkerAlreadyAdded(marker)) { - return false; - } - - if (!this.center_) { - this.center_ = marker.getPosition(); - this.calculateBounds_(); - } else { - if (this.averageCenter_) { - var lat = (this.center_.lat() + marker.getPosition().lat()) / 2; - var lng = (this.center_.lng() + marker.getPosition().lng()) / 2; - this.center_ = new google.maps.LatLng(lat, lng); - this.calculateBounds_(); - } - } - - - if (this.markers_.length == 0) { - // Only 1 marker in this cluster so show the marker. - marker.setMap(this.map_); - marker.setVisible(true); - } else if (this.markers_.length == 1) { - // Hide the 1 marker that was showing. - this.markers_[0].setMap(null); - this.markers_[0].setVisible(false); - } - - marker.isAdded = true; - this.markers_.push(marker); - - this.updateIcon(); - return true; -}; - - -/** - * Returns the marker clusterer that the cluster is associated with. - * - * @return {MarkerClusterer} The associated marker clusterer. - */ -Cluster.prototype.getMarkerClusterer = function() { - return this.markerClusterer_; -}; - - -/** - * Returns the bounds of the cluster. - * - * @return {google.maps.LatLngBounds} the cluster bounds. - */ -Cluster.prototype.getBounds = function() { - this.calculateBounds_(); - return this.bounds_; -}; - - -/** - * Removes the cluster - */ -Cluster.prototype.remove = function() { - this.clusterIcon_.remove(); - this.markers_.length = 0; - delete this.markers_; -}; - - -/** - * Returns the center of the cluster. - * - * @return {number} The cluster center. - */ -Cluster.prototype.getSize = function() { - return this.markers_.length; -}; - - -/** - * Returns the center of the cluster. - * - * @return {Array.} The cluster center. - */ -Cluster.prototype.getMarkers = function() { - return this.markers_; -}; - - -/** - * Returns the center of the cluster. - * - * @return {google.maps.LatLng} The cluster center. - */ -Cluster.prototype.getCenter = function() { - return this.center_; -}; - - -/** - * Calculated the bounds of the cluster with the grid. - * - * @private - */ -Cluster.prototype.calculateBounds_ = function() { - var bounds = new google.maps.LatLngBounds(this.center_, this.center_); - this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); -}; - - -/** - * Determines if a marker lies in the clusters bounds. - * - * @param {google.maps.Marker} marker The marker to check. - * @return {boolean} True if the marker lies in the bounds. - */ -Cluster.prototype.isMarkerInClusterBounds = function(marker) { - return this.bounds_.contains(marker.getPosition()); -}; - - -/** - * Returns the map that the cluster is associated with. - * - * @return {google.maps.Map} The map. - */ -Cluster.prototype.getMap = function() { - return this.map_; -}; - - -/** - * Updates the cluster icon - */ -Cluster.prototype.updateIcon = function() { - var zoom = this.map_.getZoom(); - var mz = this.markerClusterer_.getMaxZoom(); - - if (zoom > mz) { - // The zoom is greater than our max zoom so show all the markers in cluster. - for (var i = 0, marker; marker = this.markers_[i]; i++) { - marker.setMap(this.map_); - marker.setVisible(true); - } - return; - } - - if (this.markers_.length < 2) { - // We have 0 or 1 markers so hide the icon. - this.clusterIcon_.hide(); - return; - } - - var numStyles = this.markerClusterer_.getStyles().length; - var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); - this.clusterIcon_.setCenter(this.center_); - this.clusterIcon_.setSums(sums); - this.clusterIcon_.show(); -}; - - -/** - * A cluster icon - * - * @param {Cluster} cluster The cluster to be associated with. - * @param {Object} styles An object that has style properties: - * 'url': (string) The image url. - * 'height': (number) The image height. - * 'width': (number) The image width. - * 'anchor': (Array) The anchor position of the label text. - * 'textColor': (string) The text color. - * 'textSize': (number) The text size. - * @param {number} opt_padding Optional padding to apply to the cluster icon. - * @constructor - * @extends google.maps.OverlayView - * @ignore - */ -function ClusterIcon(cluster, styles, opt_padding) { - cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); - - this.styles_ = styles; - this.padding_ = opt_padding || 0; - this.cluster_ = cluster; - this.center_ = null; - this.map_ = cluster.getMap(); - this.div_ = null; - this.sums_ = null; - this.visible_ = false; - - this.setMap(this.map_); -} - - -/** - * Triggers the clusterclick event and zoom's if the option is set. - */ -ClusterIcon.prototype.triggerClusterClick = function() { - var markerClusterer = this.cluster_.getMarkerClusterer(); - - // Trigger the clusterclick event. - google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_); - - if (markerClusterer.isZoomOnClick()) { - // Center the map on this cluster. - this.map_.panTo(this.cluster_.getCenter()); - - // Zoom into the cluster. - this.map_.fitBounds(this.cluster_.getBounds()); - } -}; - - -/** - * Adding the cluster icon to the dom. - * @ignore - */ -ClusterIcon.prototype.onAdd = function() { - this.div_ = document.createElement('DIV'); - if (this.visible_) { - var pos = this.getPosFromLatLng_(this.center_); - this.div_.style.cssText = this.createCss(pos); - this.div_.innerHTML = this.sums_.text; - } - - var panes = this.getPanes(); - panes.overlayImage.appendChild(this.div_); - - var that = this; - google.maps.event.addDomListener(this.div_, 'click', function() { - that.triggerClusterClick(); - }); -}; - - -/** - * Returns the position to place the div dending on the latlng. - * - * @param {google.maps.LatLng} latlng The position in latlng. - * @return {google.maps.Point} The position in pixels. - * @private - */ -ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { - var pos = this.getProjection().fromLatLngToDivPixel(latlng); - pos.x -= parseInt(this.width_ / 2, 10); - pos.y -= parseInt(this.height_ / 2, 10); - return pos; -}; - - -/** - * Draw the icon. - * @ignore - */ -ClusterIcon.prototype.draw = function() { - if (this.visible_) { - var pos = this.getPosFromLatLng_(this.center_); - this.div_.style.top = pos.y + 'px'; - this.div_.style.left = pos.x + 'px'; - } -}; - - -/** - * Hide the icon. - */ -ClusterIcon.prototype.hide = function() { - if (this.div_) { - this.div_.style.display = 'none'; - } - this.visible_ = false; -}; - - -/** - * Position and show the icon. - */ -ClusterIcon.prototype.show = function() { - if (this.div_) { - var pos = this.getPosFromLatLng_(this.center_); - this.div_.style.cssText = this.createCss(pos); - this.div_.style.display = ''; - } - this.visible_ = true; -}; - - -/** - * Remove the icon from the map - */ -ClusterIcon.prototype.remove = function() { - this.setMap(null); -}; - - -/** - * Implementation of the onRemove interface. - * @ignore - */ -ClusterIcon.prototype.onRemove = function() { - if (this.div_ && this.div_.parentNode) { - this.hide(); - this.div_.parentNode.removeChild(this.div_); - this.div_ = null; - } -}; - - -/** - * Set the sums of the icon. - * - * @param {Object} sums The sums containing: - * 'text': (string) The text to display in the icon. - * 'index': (number) The style index of the icon. - */ -ClusterIcon.prototype.setSums = function(sums) { - this.sums_ = sums; - this.text_ = sums.text; - this.index_ = sums.index; - if (this.div_) { - this.div_.innerHTML = sums.text; - } - - this.useStyle(); -}; - - -/** - * Sets the icon to the the styles. - */ -ClusterIcon.prototype.useStyle = function() { - var index = Math.max(0, this.sums_.index - 1); - index = Math.min(this.styles_.length - 1, index); - var style = this.styles_[index]; - this.url_ = style['url']; - this.height_ = style['height']; - this.width_ = style['width']; - this.textColor_ = style['textColor']; - this.anchor = style['anchor']; - this.textSize_ = style['textSize']; -}; - - -/** - * Sets the center of the icon. - * - * @param {google.maps.LatLng} center The latlng to set as the center. - */ -ClusterIcon.prototype.setCenter = function(center) { - this.center_ = center; -}; - - -/** - * Create the css text based on the position of the icon. - * - * @param {google.maps.Point} pos The position. - * @return {string} The css style text. - */ -ClusterIcon.prototype.createCss = function(pos) { - var style = []; - if (document.all) { - style.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' + - 'sizingMethod=scale,src="' + this.url_ + '");'); - } else { - style.push('background:url(' + this.url_ + ');'); - } - - if (typeof this.anchor_ === 'object') { - if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && - this.anchor_[0] < this.height_) { - style.push('height:' + (this.height_ - this.anchor_[0]) + - 'px; padding-top:' + this.anchor_[0] + 'px;'); - } else { - style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + - 'px;'); - } - if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && - this.anchor_[1] < this.width_) { - style.push('width:' + (this.width_ - this.anchor_[1]) + - 'px; padding-left:' + this.anchor_[1] + 'px;'); - } else { - style.push('width:' + this.width_ + 'px; text-align:center;'); - } - } else { - style.push('height:' + this.height_ + 'px; line-height:' + - this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); - } - - var txtColor = this.textColor_ ? this.textColor_ : 'black'; - var txtSize = this.textSize_ ? this.textSize_ : 11; - - style.push('cursor:pointer; top:' + pos.y + 'px; left:' + - pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + - txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); - return style.join(''); -}; - - -// Export Symbols for Closure -// If you are not going to compile with closure then you can remove the -// code below. -window['MarkerClusterer'] = MarkerClusterer; -MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; -MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; -MarkerClusterer.prototype['clearMarkers'] = - MarkerClusterer.prototype.clearMarkers; -MarkerClusterer.prototype['getCalculator'] = - MarkerClusterer.prototype.getCalculator; -MarkerClusterer.prototype['getGridSize'] = - MarkerClusterer.prototype.getGridSize; -MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; -MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; -MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; -MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; -MarkerClusterer.prototype['getTotalClusters'] = - MarkerClusterer.prototype.getTotalClusters; -MarkerClusterer.prototype['getTotalMarkers'] = - MarkerClusterer.prototype.getTotalMarkers; -MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; -MarkerClusterer.prototype['removeMarker'] = - MarkerClusterer.prototype.removeMarker; -MarkerClusterer.prototype['resetViewport'] = - MarkerClusterer.prototype.resetViewport; -MarkerClusterer.prototype['setCalculator'] = - MarkerClusterer.prototype.setCalculator; -MarkerClusterer.prototype['setGridSize'] = - MarkerClusterer.prototype.setGridSize; -MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; -MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; - -Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; -Cluster.prototype['getSize'] = Cluster.prototype.getSize; -Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; - -ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; -ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; -ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove; diff --git a/plugins/googlemaps/static/js/markerclusterer_compiled.js b/plugins/googlemaps/static/js/markerclusterer_compiled.js deleted file mode 100644 index ad74d1bb..00000000 --- a/plugins/googlemaps/static/js/markerclusterer_compiled.js +++ /dev/null @@ -1 +0,0 @@ -function d(a){return function(b){this[a]=b}}function e(a){return function(){return this[a]}}var h; function i(a,b,c){this.extend(i,google.maps.OverlayView);this.b=a;this.a=[];this.l=[];this.Y=[53,56,66,78,90];this.h=[];this.z=false;c=c||{};this.f=c.gridSize||60;this.U=c.maxZoom||null;this.h=c.styles||[];this.T=c.imagePath||this.M;this.S=c.imageExtension||this.L;this.K=true;if(c.zoomOnClick!=undefined)this.K=c.zoomOnClick;this.p=false;if(c.averageCenter!=undefined)this.p=c.averageCenter;j(this);this.setMap(a);this.G=this.b.getZoom();var f=this;google.maps.event.addListener(this.b,"zoom_changed", function(){var g=f.b.mapTypes[f.b.getMapTypeId()].maxZoom,l=f.b.getZoom();if(!(l<0||l>g))if(f.G!=l){f.G=f.b.getZoom();f.m()}});google.maps.event.addListener(this.b,"idle",function(){f.j()});b&&b.length&&this.B(b,false)}h=i.prototype;h.M="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";h.L="png";h.extend=function(a,b){return function(c){for(property in c.prototype)this.prototype[property]=c.prototype[property];return this}.apply(a,[b])}; h.onAdd=function(){if(!this.z){this.z=true;m(this)}};h.R=function(){};h.draw=function(){};function j(a){if(!a.h.length)for(var b=0,c;c=a.Y[b];b++)a.h.push({url:a.T+(b+1)+"."+a.S,height:c,width:c})}h.w=e("h");h.v=e("a");h.Q=e("a");h.F=function(){return this.U||this.b.mapTypes[this.b.getMapTypeId()].maxZoom};h.C=function(a,b){for(var c=0,f=a.length,g=f;g!==0;){g=parseInt(g/10,10);c++}c=Math.min(c,b);return{text:f,index:c}};h.W=d("C");h.D=e("C"); h.B=function(a,b){for(var c=0,f;f=a[c];c++)n(this,f);b||this.j()};function n(a,b){b.setVisible(false);b.setMap(null);b.r=false;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.r=false;a.m();a.j()});a.a.push(b)}h.o=function(a,b){n(this,a);b||this.j()};h.V=function(a){var b=-1;if(this.a.indexOf)b=this.a.indexOf(a);else for(var c=0,f;f=this.a[c];c++)if(f==a)b=c;if(b==-1)return false;this.a.splice(b,1);a.setVisible(false);a.setMap(null);this.m();this.j();return true};h.P=function(){return this.l.length}; h.getMap=e("b");h.setMap=d("b");h.u=e("f");h.X=d("f");function o(a,b){var c=a.getProjection(),f=new google.maps.LatLng(b.getNorthEast().lat(),b.getNorthEast().lng()),g=new google.maps.LatLng(b.getSouthWest().lat(),b.getSouthWest().lng());f=c.fromLatLngToDivPixel(f);f.x+=a.f;f.y-=a.f;g=c.fromLatLngToDivPixel(g);g.x-=a.f;g.y+=a.f;f=c.fromDivPixelToLatLng(f);c=c.fromDivPixelToLatLng(g);b.extend(f);b.extend(c);return b}h.N=function(){this.m();this.a=[]}; h.m=function(){for(var a=0,b;b=this.l[a];a++)b.remove();for(a=0;b=this.a[a];a++){b.r=false;b.setMap(null);b.setVisible(false)}this.l=[]};h.j=function(){m(this)};function m(a){if(a.z)for(var b=o(a,new google.maps.LatLngBounds(a.b.getBounds().getSouthWest(),a.b.getBounds().getNorthEast())),c=0,f;f=a.a[c];c++){var g=false;if(!f.r&&b.contains(f.getPosition())){for(var l=0,k;k=a.l[l];l++)if(!g&&k.getCenter()&&k.t.contains(f.getPosition())){g=true;k.o(f);break}if(!g){k=new p(a);k.o(f);a.l.push(k)}}}} function p(a){this.i=a;this.b=a.getMap();this.f=a.u();this.p=a.p;this.d=null;this.a=[];this.t=null;this.k=new q(this,a.w(),a.u())}h=p.prototype; h.o=function(a){var b;a:if(this.a.indexOf)b=this.a.indexOf(a)!=-1;else{b=0;for(var c;c=this.a[b];b++)if(c==a){b=true;break a}b=false}if(b)return false;if(this.d){if(this.p){b=(this.d.lat()+a.getPosition().lat())/2;c=(this.d.lng()+a.getPosition().lng())/2;this.d=new google.maps.LatLng(b,c);r(this)}}else{this.d=a.getPosition();r(this)}if(this.a.length==0){a.setMap(this.b);a.setVisible(true)}else if(this.a.length==1){this.a[0].setMap(null);this.a[0].setVisible(false)}a.r=true;this.a.push(a);if(this.b.getZoom()> this.i.F())for(a=0;b=this.a[a];a++){b.setMap(this.b);b.setVisible(true)}else if(this.a.length<2)s(this.k);else{a=this.i.w().length;b=this.i.D()(this.a,a);this.k.setCenter(this.d);a=this.k;a.A=b;a.aa=b.text;a.Z=b.index;if(a.c)a.c.innerHTML=b.text;b=Math.max(0,a.A.index-1);b=Math.min(a.h.length-1,b);b=a.h[b];a.J=b.url;a.g=b.height;a.n=b.width;a.H=b.textColor;a.anchor=b.anchor;a.I=b.textSize;this.k.show()}return true};h.getBounds=function(){r(this);return this.t}; h.remove=function(){this.k.remove();this.a.length=0;delete this.a};h.O=function(){return this.a.length};h.v=e("a");h.getCenter=e("d");function r(a){a.t=o(a.i,new google.maps.LatLngBounds(a.d,a.d))}h.getMap=e("b");function q(a,b,c){a.i.extend(q,google.maps.OverlayView);this.h=b;this.$=c||0;this.q=a;this.d=null;this.b=a.getMap();this.A=this.c=null;this.s=false;this.setMap(this.b)}h=q.prototype; h.onAdd=function(){this.c=document.createElement("DIV");if(this.s){this.c.style.cssText=t(this,u(this,this.d));this.c.innerHTML=this.A.text}this.getPanes().overlayImage.appendChild(this.c);var a=this;google.maps.event.addDomListener(this.c,"click",function(){var b=a.q.i;google.maps.event.trigger(b,"clusterclick",a.q);if(b.K){a.b.panTo(a.q.getCenter());a.b.fitBounds(a.q.getBounds())}})}; function u(a,b){var c=a.getProjection().fromLatLngToDivPixel(b);c.x-=parseInt(a.n/2,10);c.y-=parseInt(a.g/2,10);return c}h.draw=function(){if(this.s){var a=u(this,this.d);this.c.style.top=a.y+"px";this.c.style.left=a.x+"px"}};function s(a){if(a.c)a.c.style.display="none";a.s=false}h.show=function(){if(this.c){this.c.style.cssText=t(this,u(this,this.d));this.c.style.display=""}this.s=true};h.remove=function(){this.setMap(null)}; h.onRemove=function(){if(this.c&&this.c.parentNode){s(this);this.c.parentNode.removeChild(this.c);this.c=null}};h.setCenter=d("d"); function t(a,b){var c=[];document.all?c.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'+a.J+'");'):c.push("background:url("+a.J+");");if(typeof a.e==="object"){typeof a.e[0]==="number"&&a.e[0]>0&&a.e[0]0&&a.e[1]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}('6 5(c,d,e){3.Z(5,u.v.23);3.l=c;3.k=[];3.N=[];3.24=[2H,2I,2J,2K,2L];3.F=[];3.1d=r;7 f=e||{};3.G=f[\'2M\']||2N;3.1s=f[\'1t\']||w;3.F=f[\'2O\']||[];3.25=f[\'2P\']||3.26;3.27=f[\'2Q\']||3.28;3.1u=C;8(f[\'29\']!=2a){3.1u=f[\'29\']}3.12=r;8(f[\'2b\']!=2a){3.12=f[\'2b\']}3.2c();3.D(c);3.1v=3.l.1e();7 g=3;u.v.13.1w(3.l,\'2R\',6(){7 a=g.l.2d[g.l.2e()].1t;7 b=g.l.1e();8(b<0||b>a){9}8(g.1v!=b){g.1v=g.l.1e();g.K()}});u.v.13.1w(3.l,\'1f\',6(){g.H()});8(d&&d.A){3.1g(d,r)}}5.4.26=\'2S://u-v-2T-2U-2V.2W.2X/2Y/2Z/30/\'+\'31/m\';5.4.28=\'32\';5.4.Z=6(b,c){9(6(a){E(1x 33 a.4){3.4[1x]=a.4[1x]}9 3}).34(b,[c])};5.4.O=6(){3.2f(C)};5.4.1f=6(){};5.4.P=6(){};5.4.2c=6(){8(3.F.A){9}E(7 i=0,14;14=3.24[i];i++){3.F.z({1y:3.25+(i+1)+\'.\'+3.27,L:14,15:14})}};5.4.35=6(a){3.F=a};5.4.16=6(){9 3.F};5.4.2g=6(){9 3.1u};5.4.2h=6(){9 3.12};5.4.Q=6(){9 3.k};5.4.1z=6(){9 3.k};5.4.36=6(a){3.1s=a};5.4.1h=6(){9 3.1s||3.l.2d[3.l.2e()].1t};5.4.1A=6(a,b){7 c=0;7 d=a.A;7 e=d;37(e!==0){e=1B(e/10,10);c++}c=1C.2i(c,b);9{R:d,1D:c}};5.4.1E=6(a){3.1A=a};5.4.1i=6(){9 3.1A};5.4.1g=6(a,b){E(7 i=0,q;q=a[i];i++){3.1F(q)}8(!b){3.H()}};5.4.1F=6(a){a.S(r);a.D(w);a.17=r;8(a[\'38\']){7 b=3;u.v.13.1w(a,\'39\',6(){a.17=r;b.K();b.H()})}3.k.z(a)};5.4.T=6(a,b){3.1F(a);8(!b){3.H()}};5.4.1G=6(a){7 b=-1;8(3.k.1j){b=3.k.1j(a)}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){b=i;3a}}}8(b==-1){9 r}3.k.3b(b,1);a.S(r);a.D(w);3.K();3.H();9 C};5.4.2f=6(a){8(!3.1d){3.1d=a;3.1H()}};5.4.1I=6(){9 3.N.A};5.4.U=6(){9 3.l};5.4.D=6(a){3.l=a};5.4.18=6(){9 3.G};5.4.1J=6(a){3.G=a};5.4.1K=6(a){7 b=3.2j();7 c=M u.v.1L(a.1M().1k(),a.1M().1l());7 d=M u.v.1L(a.1N().1k(),a.1N().1l());7 e=b.1O(c);e.x+=3.G;e.y-=3.G;7 f=b.1O(d);f.x-=3.G;f.y+=3.G;7 g=b.2k(e);7 h=b.2k(f);a.Z(g);a.Z(h);9 a};5.4.2l=6(a,b){9 b.2m(a.19())};5.4.1P=6(){3.K();3.k=[]};5.4.K=6(){E(7 i=0,1Q;1Q=3.N[i];i++){1Q.1m()}E(7 i=0,q;q=3.k[i];i++){q.17=r;q.D(w);q.S(r)}3.N=[]};5.4.H=6(){3.1H()};5.4.1H=6(){8(!3.1d){9}7 a=M u.v.2n(3.l.1n().1N(),3.l.1n().1M());7 b=3.1K(a);E(7 i=0,q;q=3.k[i];i++){7 c=r;8(!q.17&&3.2l(q,b)){E(7 j=0,d;d=3.N[j];j++){8(!c&&d.1a()&&d.2o(q)){c=C;d.T(q);3c}}8(!c){7 d=M o(3);d.T(q);3.N.z(d)}}}};6 o(a){3.V=a;3.l=a.U();3.G=a.18();3.12=a.2h();3.s=w;3.k=[];3.1o=w;3.W=M n(3,a.16(),a.18())}o.4.2p=6(a){8(3.k.1j){9 3.k.1j(a)!=-1}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){9 C}}}9 r};o.4.T=6(a){8(3.2p(a)){9 r}8(!3.s){3.s=a.19();3.1p()}I{8(3.12){7 b=(3.s.1k()+a.19().1k())/2;7 c=(3.s.1l()+a.19().1l())/2;3.s=M u.v.1L(b,c);3.1p()}}8(3.k.A==0){a.D(3.l);a.S(C)}I 8(3.k.A==1){3.k[0].D(w);3.k[0].S(r)}a.17=C;3.k.z(a);3.2q();9 C};o.4.1R=6(){9 3.V};o.4.1n=6(){3.1p();9 3.1o};o.4.1m=6(){3.W.1m();3.k.A=0;3d 3.k};o.4.1S=6(){9 3.k.A};o.4.Q=6(){9 3.k};o.4.1a=6(){9 3.s};o.4.1p=6(){7 a=M u.v.2n(3.s,3.s);3.1o=3.V.1K(a)};o.4.2o=6(a){9 3.1o.2m(a.19())};o.4.U=6(){9 3.l};o.4.2q=6(){7 a=3.l.1e();7 b=3.V.1h();8(a>b){E(7 i=0,q;q=3.k[i];i++){q.D(3.l);q.S(C)}9}8(3.k.A<2){3.W.1T();9}7 c=3.V.16().A;7 d=3.V.1i()(3.k,c);3.W.2r(3.s);3.W.2s(d);3.W.2t()};6 n(a,b,c){a.1R().Z(n,u.v.23);3.F=b;3.3e=c||0;3.1b=a;3.s=w;3.l=a.U();3.p=w;3.1q=w;3.1c=r;3.D(3.l)}n.4.2u=6(){7 a=3.1b.1R();u.v.13.3f(a,\'3g\',3.1b);8(a.2g()){3.l.3h(3.1b.1a());3.l.3i(3.1b.1n())}};n.4.O=6(){3.p=2v.3j(\'3k\');8(3.1c){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.2x=3.1q.R}7 b=3.3l();b.3m.3n(3.p);7 c=3;u.v.13.3o(3.p,\'3p\',6(){c.2u()})};n.4.1r=6(a){7 b=3.2j().1O(a);b.x-=1B(3.Y/2,10);b.y-=1B(3.J/2,10);9 b};n.4.P=6(){8(3.1c){7 a=3.1r(3.s);3.p.X.1V=a.y+\'t\';3.p.X.1W=a.x+\'t\'}};n.4.1T=6(){8(3.p){3.p.X.2y=\'3q\'}3.1c=r};n.4.2t=6(){8(3.p){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.X.2y=\'\'}3.1c=C};n.4.1m=6(){3.D(w)};n.4.1X=6(){8(3.p&&3.p.2z){3.1T();3.p.2z.3r(3.p);3.p=w}};n.4.2s=6(a){3.1q=a;3.3s=a.R;3.3t=a.1D;8(3.p){3.p.2x=a.R}3.2A()};n.4.2A=6(){7 a=1C.3u(0,3.1q.1D-1);a=1C.2i(3.F.A-1,a);7 b=3.F[a];3.1Y=b[\'1y\'];3.J=b[\'L\'];3.Y=b[\'15\'];3.1Z=b[\'3v\'];3.2B=b[\'2B\'];3.20=b[\'3w\']};n.4.2r=6(a){3.s=a};n.4.1U=6(a){7 b=[];8(2v.3x){b.z(\'3y:3z:3A.3B.3C(\'+\'3D=3E,3F="\'+3.1Y+\'");\')}I{b.z(\'3G:1y(\'+3.1Y+\');\')}8(21 3.B===\'3H\'){8(21 3.B[0]===\'2C\'&&3.B[0]>0&&3.B[0]<3.J){b.z(\'L:\'+(3.J-3.B[0])+\'t; 2D-1V:\'+3.B[0]+\'t;\')}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t;\')}8(21 3.B[1]===\'2C\'&&3.B[1]>0&&3.B[1]<3.Y){b.z(\'15:\'+(3.Y-3.B[1])+\'t; 2D-1W:\'+3.B[1]+\'t;\')}I{b.z(\'15:\'+3.Y+\'t; R-2F:2G;\')}}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t; 15:\'+3.Y+\'t; R-2F:2G;\')}7 c=3.1Z?3.1Z:\'3I\';7 d=3.20?3.20:11;b.z(\'3J:3K; 1V:\'+a.y+\'t; 1W:\'+a.x+\'t; 3L:\'+c+\'; 3M:3N; 22-14:\'+d+\'t; 22-3O:3P,3Q-3R; 22-3S:3T\');9 b.3U(\'\')};3V[\'5\']=5;5.4[\'T\']=5.4.T;5.4[\'1g\']=5.4.1g;5.4[\'1P\']=5.4.1P;5.4[\'1i\']=5.4.1i;5.4[\'18\']=5.4.18;5.4[\'U\']=5.4.U;5.4[\'Q\']=5.4.Q;5.4[\'1h\']=5.4.1h;5.4[\'16\']=5.4.16;5.4[\'1I\']=5.4.1I;5.4[\'1z\']=5.4.1z;5.4[\'H\']=5.4.H;5.4[\'1G\']=5.4.1G;5.4[\'K\']=5.4.K;5.4[\'1E\']=5.4.1E;5.4[\'1J\']=5.4.1J;5.4[\'O\']=5.4.O;5.4[\'P\']=5.4.P;5.4[\'1f\']=5.4.1f;o.4[\'1a\']=o.4.1a;o.4[\'1S\']=o.4.1S;o.4[\'Q\']=o.4.Q;n.4[\'O\']=n.4.O;n.4[\'P\']=n.4.P;n.4[\'1X\']=n.4.1X;',62,244,'|||this|prototype|MarkerClusterer|function|var|if|return|||||||||||markers_|map_||ClusterIcon|Cluster|div_|marker|false|center_|px|google|maps|null|||push|length|anchor_|true|setMap|for|styles_|gridSize_|redraw|else|height_|resetViewport|height|new|clusters_|onAdd|draw|getMarkers|text|setVisible|addMarker|getMap|markerClusterer_|clusterIcon_|style|width_|extend|||averageCenter_|event|size|width|getStyles|isAdded|getGridSize|getPosition|getCenter|cluster_|visible_|ready_|getZoom|idle|addMarkers|getMaxZoom|getCalculator|indexOf|lat|lng|remove|getBounds|bounds_|calculateBounds_|sums_|getPosFromLatLng_|maxZoom_|maxZoom|zoomOnClick_|prevZoom_|addListener|property|url|getTotalMarkers|calculator_|parseInt|Math|index|setCalculator|pushMarkerTo_|removeMarker|createClusters_|getTotalClusters|setGridSize|getExtendedBounds|LatLng|getNorthEast|getSouthWest|fromLatLngToDivPixel|clearMarkers|cluster|getMarkerClusterer|getSize|hide|createCss|top|left|onRemove|url_|textColor_|textSize_|typeof|font|OverlayView|sizes|imagePath_|MARKER_CLUSTER_IMAGE_PATH_|imageExtension_|MARKER_CLUSTER_IMAGE_EXTENSION_|zoomOnClick|undefined|averageCenter|setupStyles_|mapTypes|getMapTypeId|setReady_|isZoomOnClick|isAverageCenter|min|getProjection|fromDivPixelToLatLng|isMarkerInBounds_|contains|LatLngBounds|isMarkerInClusterBounds|isMarkerAlreadyAdded|updateIcon|setCenter|setSums|show|triggerClusterClick|document|cssText|innerHTML|display|parentNode|useStyle|anchor|number|padding|line|align|center|53|56|66|78|90|gridSize|60|styles|imagePath|imageExtension|zoom_changed|http|utility|library|v3|googlecode|com|svn|trunk|markerclusterer|images|png|in|apply|setStyles|setMaxZoom|while|draggable|dragend|continue|splice|break|delete|padding_|trigger|clusterclick|panTo|fitBounds|createElement|DIV|getPanes|overlayImage|appendChild|addDomListener|click|none|removeChild|text_|index_|max|textColor|textSize|all|filter|progid|DXImageTransform|Microsoft|AlphaImageLoader|sizingMethod|scale|src|background|object|black|cursor|pointer|color|position|absolute|family|Arial|sans|serif|weight|bold|join|window'.split('|'),0,{})) diff --git a/third-party/markerclusterer/markerclusterer-2.0.16.js b/third-party/markerclusterer/markerclusterer-2.0.16.js new file mode 100644 index 00000000..cb89261e --- /dev/null +++ b/third-party/markerclusterer/markerclusterer-2.0.16.js @@ -0,0 +1,1644 @@ +/*jslint browser: true, confusion: true, sloppy: true, vars: true, nomen: false, plusplus: false, indent: 2 */ +/*global window,google */ + +/** + * @name MarkerClustererPlus for Google Maps V3 + * @version 2.0.16 [October 18, 2012] + * @author Gary Little + * @fileoverview + * The library creates and manages per-zoom-level clusters for large amounts of markers. + *

+ * This is an enhanced V3 implementation of the + * V2 MarkerClusterer by Xiaoxi Wu. It is based on the + * V3 MarkerClusterer port by Luke Mahe. MarkerClustererPlus was created by Gary Little. + *

+ * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It + * adds support for the ignoreHidden, title, printable, + * batchSizeIE, and calculator properties as well as support for + * four more events. It also allows greater control over the styling of the text that appears + * on the cluster marker. The documentation has been significantly improved and the overall + * code has been simplified and polished. Very large numbers of markers can now be managed + * without causing Javascript timeout errors on Internet Explorer. Note that the name of the + * clusterclick event has been deprecated. The new name is click, + * so please change your application code now. + */ + +/** + * 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 ClusterIconStyle + * @class This class represents the object for values in the styles array passed + * to the {@link MarkerClusterer} constructor. The element in this array that is used to + * style the cluster icon is determined by calling the calculator function. + * + * @property {string} url The URL of the cluster icon image file. Required. + * @property {number} height The height (in pixels) of the cluster icon. Required. + * @property {number} width The width (in pixels) of the cluster icon. Required. + * @property {Array} [anchor] The anchor position (in pixels) of the label text to be shown on + * the cluster icon, relative to the top left corner of the icon. + * The format is [yoffset, xoffset]. The yoffset must be positive + * and less than height and the xoffset must be positive and less + * than width. The default is to anchor the label text so that it is centered + * on the icon. + * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the + * spot on the cluster icon that is to be aligned with the cluster position. The format is + * [yoffset, xoffset] where yoffset increases as you go down and + * xoffset increases to the right. The default anchor position is the center of the + * cluster icon. + * @property {string} [textColor="black"] The color of the label text shown on the + * cluster icon. + * @property {number} [textSize=11] The size (in pixels) of the label text shown on the + * cluster icon. + * @property {number} [textDecoration="none"] The value of the CSS text-decoration + * property for the label text shown on the cluster icon. + * @property {number} [fontWeight="bold"] The value of the CSS font-weight + * property for the label text shown on the cluster icon. + * @property {number} [fontStyle="normal"] The value of the CSS font-style + * property for the label text shown on the cluster icon. + * @property {number} [fontFamily="Arial,sans-serif"] The value of the CSS font-family + * property for the label text shown on the cluster icon. + * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image + * within the image defined by url. The format is "xpos ypos" + * (the same format as for the CSS background-position property). You must set + * this property appropriately when the image defined by url represents a sprite + * containing multiple images. + */ +/** + * @name ClusterIconInfo + * @class This class is an object containing general information about a cluster icon. This is + * the object that a calculator function returns. + * + * @property {string} text The text of the label to be shown on the cluster icon. + * @property {number} index The index plus 1 of the element in the styles + * array to be used to style the cluster icon. + * @property {string} title The tooltip to display when the mouse moves over the cluster icon. + * If this value is undefined or "", title is set to the + * value of the title property passed to the MarkerClusterer. + */ +/** + * A cluster icon. + * + * @constructor + * @extends google.maps.OverlayView + * @param {Cluster} cluster The cluster with which the icon is to be associated. + * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons + * to use for various cluster sizes. + * @private + */ +function ClusterIcon(cluster, styles) { + cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); + + this.cluster_ = cluster; + this.className_ = cluster.getMarkerClusterer().getClusterClass(); + this.styles_ = styles; + this.center_ = null; + this.div_ = null; + this.sums_ = null; + this.visible_ = false; + + this.setMap(cluster.getMap()); // Note: this causes onAdd to be called +} + + +/** + * Adds the icon to the DOM. + */ +ClusterIcon.prototype.onAdd = function () { + var cClusterIcon = this; + var cMouseDownInCluster; + var cDraggingMapByCluster; + + this.div_ = document.createElement("div"); + this.div_.className = this.className_; + if (this.visible_) { + this.show(); + } + + this.getPanes().overlayMouseTarget.appendChild(this.div_); + + // Fix for Issue 157 + this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () { + cDraggingMapByCluster = cMouseDownInCluster; + }); + + google.maps.event.addDomListener(this.div_, "mousedown", function () { + cMouseDownInCluster = true; + cDraggingMapByCluster = false; + }); + + google.maps.event.addDomListener(this.div_, "click", function (e) { + cMouseDownInCluster = false; + if (!cDraggingMapByCluster) { + var theBounds; + var mz; + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when a cluster marker is clicked. + * @name MarkerClusterer#click + * @param {Cluster} c The cluster that was clicked. + * @event + */ + google.maps.event.trigger(mc, "click", cClusterIcon.cluster_); + google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name + + // The default click handler follows. Disable it by setting + // the zoomOnClick property to false. + if (mc.getZoomOnClick()) { + // Zoom into the cluster. + mz = mc.getMaxZoom(); + theBounds = cClusterIcon.cluster_.getBounds(); + mc.getMap().fitBounds(theBounds); + // There is a fix for Issue 170 here: + setTimeout(function () { + mc.getMap().fitBounds(theBounds); + // Don't zoom beyond the max zoom level + if (mz !== null && (mc.getMap().getZoom() > mz)) { + mc.getMap().setZoom(mz + 1); + } + }, 100); + } + + // Prevent event propagation to the map: + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + } + }); + + google.maps.event.addDomListener(this.div_, "mouseover", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves over a cluster marker. + * @name MarkerClusterer#mouseover + * @param {Cluster} c The cluster that the mouse moved over. + * @event + */ + google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_); + }); + + google.maps.event.addDomListener(this.div_, "mouseout", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves out of a cluster marker. + * @name MarkerClusterer#mouseout + * @param {Cluster} c The cluster that the mouse moved out of. + * @event + */ + google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_); + }); +}; + + +/** + * Removes the icon from the DOM. + */ +ClusterIcon.prototype.onRemove = function () { + if (this.div_ && this.div_.parentNode) { + this.hide(); + google.maps.event.removeListener(this.boundsChangedListener_); + google.maps.event.clearInstanceListeners(this.div_); + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + + +/** + * Draws the icon. + */ +ClusterIcon.prototype.draw = function () { + if (this.visible_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.top = pos.y + "px"; + this.div_.style.left = pos.x + "px"; + } +}; + + +/** + * Hides the icon. + */ +ClusterIcon.prototype.hide = function () { + if (this.div_) { + this.div_.style.display = "none"; + } + this.visible_ = false; +}; + + +/** + * Positions and shows the icon. + */ +ClusterIcon.prototype.show = function () { + if (this.div_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.cssText = this.createCss(pos); + if (this.cluster_.printable_) { + // (Would like to use "width: inherit;" below, but doesn't work with MSIE) + this.div_.innerHTML = "

" + this.sums_.text + "
"; + } else { + this.div_.innerHTML = this.sums_.text; + } + if (typeof this.sums_.title === "undefined" || this.sums_.title === "") { + this.div_.title = this.cluster_.getMarkerClusterer().getTitle(); + } else { + this.div_.title = this.sums_.title; + } + this.div_.style.display = ""; + } + this.visible_ = true; +}; + + +/** + * Sets the icon styles to the appropriate element in the styles array. + * + * @param {ClusterIconInfo} sums The icon label text and styles index. + */ +ClusterIcon.prototype.useStyle = function (sums) { + this.sums_ = sums; + var index = Math.max(0, sums.index - 1); + index = Math.min(this.styles_.length - 1, index); + var style = this.styles_[index]; + this.url_ = style.url; + this.height_ = style.height; + this.width_ = style.width; + this.anchor_ = style.anchor; + this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)]; + this.textColor_ = style.textColor || "black"; + this.textSize_ = style.textSize || 11; + this.textDecoration_ = style.textDecoration || "none"; + this.fontWeight_ = style.fontWeight || "bold"; + this.fontStyle_ = style.fontStyle || "normal"; + this.fontFamily_ = style.fontFamily || "Arial,sans-serif"; + this.backgroundPosition_ = style.backgroundPosition || "0 0"; +}; + + +/** + * Sets the position at which to center the icon. + * + * @param {google.maps.LatLng} center The latlng to set as the center. + */ +ClusterIcon.prototype.setCenter = function (center) { + this.center_ = center; +}; + + +/** + * Creates the cssText style parameter based on the position of the icon. + * + * @param {google.maps.Point} pos The position of the icon. + * @return {string} The CSS style text. + */ +ClusterIcon.prototype.createCss = function (pos) { + var style = []; + if (!this.cluster_.printable_) { + style.push('background-image:url(' + this.url_ + ');'); + style.push('background-position:' + this.backgroundPosition_ + ';'); + } + + if (typeof this.anchor_ === 'object') { + if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && + this.anchor_[0] < this.height_) { + style.push('height:' + (this.height_ - this.anchor_[0]) + + 'px; padding-top:' + this.anchor_[0] + 'px;'); + } else { + style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + + 'px;'); + } + if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && + this.anchor_[1] < this.width_) { + style.push('width:' + (this.width_ - this.anchor_[1]) + + 'px; padding-left:' + this.anchor_[1] + 'px;'); + } else { + style.push('width:' + this.width_ + 'px; text-align:center;'); + } + } else { + style.push('height:' + this.height_ + 'px; line-height:' + + this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); + } + + style.push('cursor:pointer; top:' + pos.y + 'px; left:' + + pos.x + 'px; color:' + this.textColor_ + '; position:absolute; font-size:' + + this.textSize_ + 'px; font-family:' + this.fontFamily_ + '; font-weight:' + + this.fontWeight_ + '; font-style:' + this.fontStyle_ + '; text-decoration:' + + this.textDecoration_ + ';'); + + return style.join(""); +}; + + +/** + * Returns the position at which to place the DIV depending on the latlng. + * + * @param {google.maps.LatLng} latlng The position in latlng. + * @return {google.maps.Point} The position in pixels. + */ +ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) { + var pos = this.getProjection().fromLatLngToDivPixel(latlng); + pos.x -= this.anchorIcon_[1]; + pos.y -= this.anchorIcon_[0]; + return pos; +}; + + +/** + * Creates a single cluster that manages a group of proximate markers. + * Used internally, do not call this constructor directly. + * @constructor + * @param {MarkerClusterer} mc The MarkerClusterer object with which this + * cluster is associated. + */ +function Cluster(mc) { + this.markerClusterer_ = mc; + this.map_ = mc.getMap(); + this.gridSize_ = mc.getGridSize(); + this.minClusterSize_ = mc.getMinimumClusterSize(); + this.averageCenter_ = mc.getAverageCenter(); + this.printable_ = mc.getPrintable(); + this.markers_ = []; + this.center_ = null; + this.bounds_ = null; + this.clusterIcon_ = new ClusterIcon(this, mc.getStyles()); +} + + +/** + * Returns the number of markers managed by the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {number} The number of markers in the cluster. + */ +Cluster.prototype.getSize = function () { + return this.markers_.length; +}; + + +/** + * Returns the array of markers managed by the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {Array} The array of markers in the cluster. + */ +Cluster.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the center of the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {google.maps.LatLng} The center of the cluster. + */ +Cluster.prototype.getCenter = function () { + return this.center_; +}; + + +/** + * Returns the map with which the cluster is associated. + * + * @return {google.maps.Map} The map. + * @ignore + */ +Cluster.prototype.getMap = function () { + return this.map_; +}; + + +/** + * Returns the MarkerClusterer object with which the cluster is associated. + * + * @return {MarkerClusterer} The associated marker clusterer. + * @ignore + */ +Cluster.prototype.getMarkerClusterer = function () { + return this.markerClusterer_; +}; + + +/** + * Returns the bounds of the cluster. + * + * @return {google.maps.LatLngBounds} the cluster bounds. + * @ignore + */ +Cluster.prototype.getBounds = function () { + var i; + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + var markers = this.getMarkers(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + return bounds; +}; + + +/** + * Removes the cluster from the map. + * + * @ignore + */ +Cluster.prototype.remove = function () { + this.clusterIcon_.setMap(null); + this.markers_ = []; + delete this.markers_; +}; + + +/** + * Adds a marker to the cluster. + * + * @param {google.maps.Marker} marker The marker to be added. + * @return {boolean} True if the marker was added. + * @ignore + */ +Cluster.prototype.addMarker = function (marker) { + var i; + var mCount; + var mz; + + if (this.isMarkerAlreadyAdded_(marker)) { + return false; + } + + if (!this.center_) { + this.center_ = marker.getPosition(); + this.calculateBounds_(); + } else { + if (this.averageCenter_) { + var l = this.markers_.length + 1; + var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; + var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; + this.center_ = new google.maps.LatLng(lat, lng); + this.calculateBounds_(); + } + } + + marker.isAdded = true; + this.markers_.push(marker); + + mCount = this.markers_.length; + mz = this.markerClusterer_.getMaxZoom(); + if (mz !== null && this.map_.getZoom() > mz) { + // Zoomed in past max zoom, so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount < this.minClusterSize_) { + // Min cluster size not reached so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount === this.minClusterSize_) { + // Hide the markers that were showing. + for (i = 0; i < mCount; i++) { + this.markers_[i].setMap(null); + } + } else { + marker.setMap(null); + } + + this.updateIcon_(); + return true; +}; + + +/** + * Determines if a marker lies within the cluster's bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker lies in the bounds. + * @ignore + */ +Cluster.prototype.isMarkerInClusterBounds = function (marker) { + return this.bounds_.contains(marker.getPosition()); +}; + + +/** + * Calculates the extended bounds of the cluster with the grid. + */ +Cluster.prototype.calculateBounds_ = function () { + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); +}; + + +/** + * Updates the cluster icon. + */ +Cluster.prototype.updateIcon_ = function () { + var mCount = this.markers_.length; + var mz = this.markerClusterer_.getMaxZoom(); + + if (mz !== null && this.map_.getZoom() > mz) { + this.clusterIcon_.hide(); + return; + } + + if (mCount < this.minClusterSize_) { + // Min cluster size not yet reached. + this.clusterIcon_.hide(); + return; + } + + var numStyles = this.markerClusterer_.getStyles().length; + var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); + this.clusterIcon_.setCenter(this.center_); + this.clusterIcon_.useStyle(sums); + this.clusterIcon_.show(); +}; + + +/** + * Determines if a marker has already been added to the cluster. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker has already been added. + */ +Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) { + var i; + if (this.markers_.indexOf) { + return this.markers_.indexOf(marker) !== -1; + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + return true; + } + } + } + return false; +}; + + +/** + * @name MarkerClustererOptions + * @class This class represents the optional parameter passed to + * the {@link MarkerClusterer} constructor. + * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square. + * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or + * null if clustering is to be enabled at all zoom levels. + * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is + * clicked. You may want to set this to false if you have installed a handler + * for the click event and it deals with zooming on its own. + * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be + * the average position of all markers in the cluster. If set to false, the + * cluster marker is positioned at the location of the first marker added to the cluster. + * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster + * before the markers are hidden and a cluster marker appears. + * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You + * may want to set this to true to ensure that hidden markers are not included + * in the marker count that appears on a cluster marker (this count is the value of the + * text property of the result returned by the default calculator). + * If set to true and you change the visibility of a marker being clustered, be + * sure to also call MarkerClusterer.repaint(). + * @property {boolean} [printable=false] Whether to make the cluster icons printable. Do not + * set to true if the url fields in the styles array + * refer to image sprite files. + * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster + * marker. (Alternatively, you can use a custom calculator function to specify a + * different tooltip for each cluster marker.) + * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine + * the text to be displayed on a cluster marker and the index indicating which style to use + * for the cluster marker. The input parameters for the function are (1) the array of markers + * represented by a cluster marker and (2) the number of cluster icon styles. It returns a + * {@link ClusterIconInfo} object. The default calculator returns a + * text property which is the number of markers in the cluster and an + * index property which is one higher than the lowest integer such that + * 10^i exceeds the number of markers in the cluster, or the size of the styles + * array, whichever is less. The styles array element used has an index of + * index minus 1. For example, the default calculator returns a + * text value of "125" and an index of 3 + * for a cluster icon representing 125 markers so the element used in the styles + * array is 2. A calculator may also return a title + * property that contains the text of the tooltip to be used for the cluster marker. If + * title is not defined, the tooltip is set to the value of the title + * property for the MarkerClusterer. + * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles + * for the cluster markers. Use this class to define CSS styles that are not set up by the code + * that processes the styles array. + * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles + * of the cluster markers to be used. The element to be used to style a given cluster marker + * is determined by the function defined by the calculator property. + * The default is an array of {@link ClusterIconStyle} elements whose properties are derived + * from the values for imagePath, imageExtension, and + * imageSizes. + * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the + * number of markers to be processed in a single batch when using a browser other than + * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead). + * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is + * being used, markers are processed in several batches with a small delay inserted between + * each batch in an attempt to avoid Javascript timeout errors. Set this property to the + * number of markers to be processed in a single batch; select as high a number as you can + * without causing a timeout error in the browser. This number might need to be as low as 100 + * if 15,000 markers are being managed, for example. + * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH] + * The full URL of the root name of the group of image files to use for cluster icons. + * The complete file name is of the form imagePathn.imageExtension + * where n is the image file number (1, 2, etc.). + * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION] + * The extension name for the cluster icon image files (e.g., "png" or + * "jpg"). + * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES] + * An array of numbers containing the widths of the group of + * imagePathn.imageExtension image files. + * (The images are assumed to be square.) + */ +/** + * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}. + * @constructor + * @extends google.maps.OverlayView + * @param {google.maps.Map} map The Google map to attach to. + * @param {Array.} [opt_markers] The markers to be added to the cluster. + * @param {MarkerClustererOptions} [opt_options] The optional parameters. + */ +function MarkerClusterer(map, opt_markers, opt_options) { + // MarkerClusterer implements google.maps.OverlayView interface. We use the + // extend function to extend MarkerClusterer with google.maps.OverlayView + // because it might not always be available when the code is defined so we + // look for it at the last possible moment. If it doesn't exist now then + // there is no point going ahead :) + this.extend(MarkerClusterer, google.maps.OverlayView); + + opt_markers = opt_markers || []; + opt_options = opt_options || {}; + + this.markers_ = []; + this.clusters_ = []; + this.listeners_ = []; + this.activeMap_ = null; + this.ready_ = false; + + this.gridSize_ = opt_options.gridSize || 60; + this.minClusterSize_ = opt_options.minimumClusterSize || 2; + this.maxZoom_ = opt_options.maxZoom || null; + this.styles_ = opt_options.styles || []; + this.title_ = opt_options.title || ""; + this.zoomOnClick_ = true; + if (opt_options.zoomOnClick !== undefined) { + this.zoomOnClick_ = opt_options.zoomOnClick; + } + this.averageCenter_ = false; + if (opt_options.averageCenter !== undefined) { + this.averageCenter_ = opt_options.averageCenter; + } + this.ignoreHidden_ = false; + if (opt_options.ignoreHidden !== undefined) { + this.ignoreHidden_ = opt_options.ignoreHidden; + } + this.printable_ = false; + if (opt_options.printable !== undefined) { + this.printable_ = opt_options.printable; + } + this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH; + this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION; + this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES; + this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR; + this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE; + this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE; + this.clusterClass_ = opt_options.clusterClass || "cluster"; + + if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) { + // Try to avoid IE timeout when processing a huge number of markers: + this.batchSize_ = this.batchSizeIE_; + } + + this.setupStyles_(); + + this.addMarkers(opt_markers, true); + this.setMap(map); // Note: this causes onAdd to be called +} + + +/** + * Implementation of the onAdd interface method. + * @ignore + */ +MarkerClusterer.prototype.onAdd = function () { + var cMarkerClusterer = this; + + this.activeMap_ = this.getMap(); + this.ready_ = true; + + this.repaint(); + + // Add the map event listeners + this.listeners_ = [ + google.maps.event.addListener(this.getMap(), "zoom_changed", function () { + cMarkerClusterer.resetViewport_(false); + // Workaround for this Google bug: when map is at level 0 and "-" of + // zoom slider is clicked, a "zoom_changed" event is fired even though + // the map doesn't zoom out any further. In this situation, no "idle" + // event is triggered so the cluster markers that have been removed + // do not get redrawn. Same goes for a zoom in at maxZoom. + if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) { + google.maps.event.trigger(this, "idle"); + } + }), + google.maps.event.addListener(this.getMap(), "idle", function () { + cMarkerClusterer.redraw_(); + }) + ]; +}; + + +/** + * Implementation of the onRemove interface method. + * Removes map event listeners and all cluster icons from the DOM. + * All managed markers are also put back on the map. + * @ignore + */ +MarkerClusterer.prototype.onRemove = function () { + var i; + + // Put all the managed markers back on the map: + for (i = 0; i < this.markers_.length; i++) { + if (this.markers_[i].getMap() !== this.activeMap_) { + this.markers_[i].setMap(this.activeMap_); + } + } + + // Remove all clusters: + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Remove map event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } + this.listeners_ = []; + + this.activeMap_ = null; + this.ready_ = false; +}; + + +/** + * Implementation of the draw interface method. + * @ignore + */ +MarkerClusterer.prototype.draw = function () {}; + + +/** + * Sets up the styles object. + */ +MarkerClusterer.prototype.setupStyles_ = function () { + var i, size; + if (this.styles_.length > 0) { + return; + } + + for (i = 0; i < this.imageSizes_.length; i++) { + size = this.imageSizes_[i]; + this.styles_.push({ + url: this.imagePath_ + (i + 1) + "." + this.imageExtension_, + height: size, + width: size + }); + } +}; + + +/** + * Fits the map to the bounds of the markers managed by the clusterer. + */ +MarkerClusterer.prototype.fitMapToMarkers = function () { + var i; + var markers = this.getMarkers(); + var bounds = new google.maps.LatLngBounds(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + + this.getMap().fitBounds(bounds); +}; + + +/** + * Returns the value of the gridSize property. + * + * @return {number} The grid size. + */ +MarkerClusterer.prototype.getGridSize = function () { + return this.gridSize_; +}; + + +/** + * Sets the value of the gridSize property. + * + * @param {number} gridSize The grid size. + */ +MarkerClusterer.prototype.setGridSize = function (gridSize) { + this.gridSize_ = gridSize; +}; + + +/** + * Returns the value of the minimumClusterSize property. + * + * @return {number} The minimum cluster size. + */ +MarkerClusterer.prototype.getMinimumClusterSize = function () { + return this.minClusterSize_; +}; + +/** + * Sets the value of the minimumClusterSize property. + * + * @param {number} minimumClusterSize The minimum cluster size. + */ +MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) { + this.minClusterSize_ = minimumClusterSize; +}; + + +/** + * Returns the value of the maxZoom property. + * + * @return {number} The maximum zoom level. + */ +MarkerClusterer.prototype.getMaxZoom = function () { + return this.maxZoom_; +}; + + +/** + * Sets the value of the maxZoom property. + * + * @param {number} maxZoom The maximum zoom level. + */ +MarkerClusterer.prototype.setMaxZoom = function (maxZoom) { + this.maxZoom_ = maxZoom; +}; + + +/** + * Returns the value of the styles property. + * + * @return {Array} The array of styles defining the cluster markers to be used. + */ +MarkerClusterer.prototype.getStyles = function () { + return this.styles_; +}; + + +/** + * Sets the value of the styles property. + * + * @param {Array.} styles The array of styles to use. + */ +MarkerClusterer.prototype.setStyles = function (styles) { + this.styles_ = styles; +}; + + +/** + * Returns the value of the title property. + * + * @return {string} The content of the title text. + */ +MarkerClusterer.prototype.getTitle = function () { + return this.title_; +}; + + +/** + * Sets the value of the title property. + * + * @param {string} title The value of the title property. + */ +MarkerClusterer.prototype.setTitle = function (title) { + this.title_ = title; +}; + + +/** + * Returns the value of the zoomOnClick property. + * + * @return {boolean} True if zoomOnClick property is set. + */ +MarkerClusterer.prototype.getZoomOnClick = function () { + return this.zoomOnClick_; +}; + + +/** + * Sets the value of the zoomOnClick property. + * + * @param {boolean} zoomOnClick The value of the zoomOnClick property. + */ +MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) { + this.zoomOnClick_ = zoomOnClick; +}; + + +/** + * Returns the value of the averageCenter property. + * + * @return {boolean} True if averageCenter property is set. + */ +MarkerClusterer.prototype.getAverageCenter = function () { + return this.averageCenter_; +}; + + +/** + * Sets the value of the averageCenter property. + * + * @param {boolean} averageCenter The value of the averageCenter property. + */ +MarkerClusterer.prototype.setAverageCenter = function (averageCenter) { + this.averageCenter_ = averageCenter; +}; + + +/** + * Returns the value of the ignoreHidden property. + * + * @return {boolean} True if ignoreHidden property is set. + */ +MarkerClusterer.prototype.getIgnoreHidden = function () { + return this.ignoreHidden_; +}; + + +/** + * Sets the value of the ignoreHidden property. + * + * @param {boolean} ignoreHidden The value of the ignoreHidden property. + */ +MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) { + this.ignoreHidden_ = ignoreHidden; +}; + + +/** + * Returns the value of the imageExtension property. + * + * @return {string} The value of the imageExtension property. + */ +MarkerClusterer.prototype.getImageExtension = function () { + return this.imageExtension_; +}; + + +/** + * Sets the value of the imageExtension property. + * + * @param {string} imageExtension The value of the imageExtension property. + */ +MarkerClusterer.prototype.setImageExtension = function (imageExtension) { + this.imageExtension_ = imageExtension; +}; + + +/** + * Returns the value of the imagePath property. + * + * @return {string} The value of the imagePath property. + */ +MarkerClusterer.prototype.getImagePath = function () { + return this.imagePath_; +}; + + +/** + * Sets the value of the imagePath property. + * + * @param {string} imagePath The value of the imagePath property. + */ +MarkerClusterer.prototype.setImagePath = function (imagePath) { + this.imagePath_ = imagePath; +}; + + +/** + * Returns the value of the imageSizes property. + * + * @return {Array} The value of the imageSizes property. + */ +MarkerClusterer.prototype.getImageSizes = function () { + return this.imageSizes_; +}; + + +/** + * Sets the value of the imageSizes property. + * + * @param {Array} imageSizes The value of the imageSizes property. + */ +MarkerClusterer.prototype.setImageSizes = function (imageSizes) { + this.imageSizes_ = imageSizes; +}; + + +/** + * Returns the value of the calculator property. + * + * @return {function} the value of the calculator property. + */ +MarkerClusterer.prototype.getCalculator = function () { + return this.calculator_; +}; + + +/** + * Sets the value of the calculator property. + * + * @param {function(Array., number)} calculator The value + * of the calculator property. + */ +MarkerClusterer.prototype.setCalculator = function (calculator) { + this.calculator_ = calculator; +}; + + +/** + * Returns the value of the printable property. + * + * @return {boolean} the value of the printable property. + */ +MarkerClusterer.prototype.getPrintable = function () { + return this.printable_; +}; + + +/** + * Sets the value of the printable property. + * + * @param {boolean} printable The value of the printable property. + */ +MarkerClusterer.prototype.setPrintable = function (printable) { + this.printable_ = printable; +}; + + +/** + * Returns the value of the batchSizeIE property. + * + * @return {number} the value of the batchSizeIE property. + */ +MarkerClusterer.prototype.getBatchSizeIE = function () { + return this.batchSizeIE_; +}; + + +/** + * Sets the value of the batchSizeIE property. + * + * @param {number} batchSizeIE The value of the batchSizeIE property. + */ +MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) { + this.batchSizeIE_ = batchSizeIE; +}; + + +/** + * Returns the value of the clusterClass property. + * + * @return {string} the value of the clusterClass property. + */ +MarkerClusterer.prototype.getClusterClass = function () { + return this.clusterClass_; +}; + + +/** + * Sets the value of the clusterClass property. + * + * @param {string} clusterClass The value of the clusterClass property. + */ +MarkerClusterer.prototype.setClusterClass = function (clusterClass) { + this.clusterClass_ = clusterClass; +}; + + +/** + * Returns the array of markers managed by the clusterer. + * + * @return {Array} The array of markers managed by the clusterer. + */ +MarkerClusterer.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the number of markers managed by the clusterer. + * + * @return {number} The number of markers. + */ +MarkerClusterer.prototype.getTotalMarkers = function () { + return this.markers_.length; +}; + + +/** + * Returns the current array of clusters formed by the clusterer. + * + * @return {Array} The array of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getClusters = function () { + return this.clusters_; +}; + + +/** + * Returns the number of clusters formed by the clusterer. + * + * @return {number} The number of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getTotalClusters = function () { + return this.clusters_.length; +}; + + +/** + * Adds a marker to the clusterer. The clusters are redrawn unless + * opt_nodraw is set to true. + * + * @param {google.maps.Marker} marker The marker to add. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + */ +MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) { + this.pushMarkerTo_(marker); + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Adds an array of markers to the clusterer. The clusters are redrawn unless + * opt_nodraw is set to true. + * + * @param {Array.} markers The markers to add. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + */ +MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) { + var i; + for (i = 0; i < markers.length; i++) { + this.pushMarkerTo_(markers[i]); + } + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Pushes a marker to the clusterer. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.pushMarkerTo_ = function (marker) { + // If the marker is draggable add a listener so we can update the clusters on the dragend: + if (marker.getDraggable()) { + var cMarkerClusterer = this; + google.maps.event.addListener(marker, "dragend", function () { + if (cMarkerClusterer.ready_) { + this.isAdded = false; + cMarkerClusterer.repaint(); + } + }); + } + marker.isAdded = false; + this.markers_.push(marker); +}; + + +/** + * Removes a marker from the cluster. The clusters are redrawn unless + * opt_nodraw is set to true. Returns true if the + * marker was removed from the clusterer. + * + * @param {google.maps.Marker} marker The marker to remove. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + * @return {boolean} True if the marker was removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) { + var removed = this.removeMarker_(marker); + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes an array of markers from the cluster. The clusters are redrawn unless + * opt_nodraw is set to true. Returns true if markers + * were removed from the clusterer. + * + * @param {Array.} markers The markers to remove. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + * @return {boolean} True if markers were removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) { + var i, r; + var removed = false; + + for (i = 0; i < markers.length; i++) { + r = this.removeMarker_(markers[i]); + removed = removed || r; + } + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes a marker and returns true if removed, false if not. + * + * @param {google.maps.Marker} marker The marker to remove + * @return {boolean} Whether the marker was removed or not + */ +MarkerClusterer.prototype.removeMarker_ = function (marker) { + var i; + var index = -1; + if (this.markers_.indexOf) { + index = this.markers_.indexOf(marker); + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + index = i; + break; + } + } + } + + if (index === -1) { + // Marker is not in our list of markers, so do nothing: + return false; + } + + marker.setMap(null); + this.markers_.splice(index, 1); // Remove the marker from the list of managed markers + return true; +}; + + +/** + * Removes all clusters and markers from the map and also removes all markers + * managed by the clusterer. + */ +MarkerClusterer.prototype.clearMarkers = function () { + this.resetViewport_(true); + this.markers_ = []; +}; + + +/** + * Recalculates and redraws all the marker clusters from scratch. + * Call this after changing any properties. + */ +MarkerClusterer.prototype.repaint = function () { + var oldClusters = this.clusters_.slice(); + this.clusters_ = []; + this.resetViewport_(false); + this.redraw_(); + + // Remove the old clusters. + // Do it in a timeout to prevent blinking effect. + setTimeout(function () { + var i; + for (i = 0; i < oldClusters.length; i++) { + oldClusters[i].remove(); + } + }, 0); +}; + + +/** + * Returns the current bounds extended by the grid size. + * + * @param {google.maps.LatLngBounds} bounds The bounds to extend. + * @return {google.maps.LatLngBounds} The extended bounds. + * @ignore + */ +MarkerClusterer.prototype.getExtendedBounds = function (bounds) { + var projection = this.getProjection(); + + // Turn the bounds into latlng. + var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), + bounds.getNorthEast().lng()); + var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), + bounds.getSouthWest().lng()); + + // Convert the points to pixels and the extend out by the grid size. + var trPix = projection.fromLatLngToDivPixel(tr); + trPix.x += this.gridSize_; + trPix.y -= this.gridSize_; + + var blPix = projection.fromLatLngToDivPixel(bl); + blPix.x -= this.gridSize_; + blPix.y += this.gridSize_; + + // Convert the pixel points back to LatLng + var ne = projection.fromDivPixelToLatLng(trPix); + var sw = projection.fromDivPixelToLatLng(blPix); + + // Extend the bounds to contain the new bounds. + bounds.extend(ne); + bounds.extend(sw); + + return bounds; +}; + + +/** + * Redraws all the clusters. + */ +MarkerClusterer.prototype.redraw_ = function () { + this.createClusters_(0); +}; + + +/** + * Removes all clusters from the map. The markers are also removed from the map + * if opt_hide is set to true. + * + * @param {boolean} [opt_hide] Set to true to also remove the markers + * from the map. + */ +MarkerClusterer.prototype.resetViewport_ = function (opt_hide) { + var i, marker; + // Remove all the clusters + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Reset the markers to not be added and to be removed from the map. + for (i = 0; i < this.markers_.length; i++) { + marker = this.markers_[i]; + marker.isAdded = false; + if (opt_hide) { + marker.setMap(null); + } + } +}; + + +/** + * Calculates the distance between two latlng locations in km. + * + * @param {google.maps.LatLng} p1 The first lat lng point. + * @param {google.maps.LatLng} p2 The second lat lng point. + * @return {number} The distance between the two points in km. + * @see http://www.movable-type.co.uk/scripts/latlong.html +*/ +MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) { + var R = 6371; // Radius of the Earth in km + var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; + var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + return d; +}; + + +/** + * Determines if a marker is contained in a bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @param {google.maps.LatLngBounds} bounds The bounds to check against. + * @return {boolean} True if the marker is in the bounds. + */ +MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) { + return bounds.contains(marker.getPosition()); +}; + + +/** + * Adds a marker to a cluster, or creates a new cluster. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.addToClosestCluster_ = function (marker) { + var i, d, cluster, center; + var distance = 40000; // Some large number + var clusterToAddTo = null; + for (i = 0; i < this.clusters_.length; i++) { + cluster = this.clusters_[i]; + center = cluster.getCenter(); + if (center) { + d = this.distanceBetweenPoints_(center, marker.getPosition()); + if (d < distance) { + distance = d; + clusterToAddTo = cluster; + } + } + } + + if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { + clusterToAddTo.addMarker(marker); + } else { + cluster = new Cluster(this); + cluster.addMarker(marker); + this.clusters_.push(cluster); + } +}; + + +/** + * Creates the clusters. This is done in batches to avoid timeout errors + * in some browsers when there is a huge number of markers. + * + * @param {number} iFirst The index of the first marker in the batch of + * markers to be added to clusters. + */ +MarkerClusterer.prototype.createClusters_ = function (iFirst) { + var i, marker; + var mapBounds; + var cMarkerClusterer = this; + if (!this.ready_) { + return; + } + + // Cancel previous batch processing if we're working on the first batch: + if (iFirst === 0) { + /** + * This event is fired when the MarkerClusterer begins + * clustering markers. + * @name MarkerClusterer#clusteringbegin + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringbegin", this); + + if (typeof this.timerRefStatic !== "undefined") { + clearTimeout(this.timerRefStatic); + delete this.timerRefStatic; + } + } + + // Get our current map view bounds. + // Create a new bounds object so we don't affect the map. + // + // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug: + if (this.getMap().getZoom() > 3) { + mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(), + this.getMap().getBounds().getNorthEast()); + } else { + mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625)); + } + var bounds = this.getExtendedBounds(mapBounds); + + var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length); + + for (i = iFirst; i < iLast; i++) { + marker = this.markers_[i]; + if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { + if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) { + this.addToClosestCluster_(marker); + } + } + } + + if (iLast < this.markers_.length) { + this.timerRefStatic = setTimeout(function () { + cMarkerClusterer.createClusters_(iLast); + }, 0); + } else { + delete this.timerRefStatic; + + /** + * This event is fired when the MarkerClusterer stops + * clustering markers. + * @name MarkerClusterer#clusteringend + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringend", this); + } +}; + + +/** + * Extends an object's prototype by another's. + * + * @param {Object} obj1 The object to be extended. + * @param {Object} obj2 The object to extend with. + * @return {Object} The new extended object. + * @ignore + */ +MarkerClusterer.prototype.extend = function (obj1, obj2) { + return (function (object) { + var property; + for (property in object.prototype) { + this.prototype[property] = object.prototype[property]; + } + return this; + }).apply(obj1, [obj2]); +}; + + +/** + * The default function for determining the label text and style + * for a cluster icon. + * + * @param {Array.} markers The array of markers represented by the cluster. + * @param {number} numStyles The number of marker styles available. + * @return {ClusterIconInfo} The information resource for the cluster. + * @constant + * @ignore + */ +MarkerClusterer.CALCULATOR = function (markers, numStyles) { + var index = 0; + var title = ""; + var count = markers.length.toString(); + + var dv = count; + while (dv !== 0) { + dv = parseInt(dv / 10, 10); + index++; + } + + index = Math.min(index, numStyles); + return { + text: count, + index: index, + title: title + }; +}; + + +/** + * The number of markers to process in one batch. + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE = 2000; + + +/** + * The number of markers to process in one batch (IE only). + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE_IE = 500; + + +/** + * The default root name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m"; + + +/** + * The default extension name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_EXTENSION = "png"; + + +/** + * The default array of sizes for the marker cluster images. + * + * @type {Array.} + * @constant + */ +MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90]; diff --git a/third-party/markerclusterer/markerclusterer-2.0.16.js.url b/third-party/markerclusterer/markerclusterer-2.0.16.js.url new file mode 100644 index 00000000..8c78d204 --- /dev/null +++ b/third-party/markerclusterer/markerclusterer-2.0.16.js.url @@ -0,0 +1 @@ +http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.16/src/markerclusterer.js diff --git a/third-party/markerclusterer/markerclusterer-2.0.16_packed.js b/third-party/markerclusterer/markerclusterer-2.0.16_packed.js new file mode 100644 index 00000000..4a01ec6e --- /dev/null +++ b/third-party/markerclusterer/markerclusterer-2.0.16_packed.js @@ -0,0 +1 @@ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?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}('5 H(b,a){b.17().Z(H,o.n.31);4.I=b;4.2E=b.17().2w();4.P=a;4.A=t;4.q=t;4.W=t;4.1s=s;4.L(b.v())}H.6.2D=5(){7 d=4;7 g;7 f;4.q=3E.3y("24");4.q.5n=4.2E;9(4.1s){4.2a()}4.4H().4E.4z(4.q);4.2X=o.n.u.1E(4.v(),"4b",5(){f=g});o.n.u.1D(4.q,"41",5(){g=K;f=s});o.n.u.1D(4.q,"2M",5(e){g=s;9(!f){7 c;7 b;7 a=d.I.17();o.n.u.14(a,"2M",d.I);o.n.u.14(a,"3Y",d.I);9(a.2B()){b=a.1K();c=d.I.1y();a.v().1O(c);1U(5(){a.v().1O(c);9(b!==t&&(a.v().1c()>b)){a.v().3I(b+1)}},3D)}e.3A=K;9(e.2h){e.2h()}}});o.n.u.1D(4.q,"2f",5(){7 a=d.I.17();o.n.u.14(a,"2f",d.I)});o.n.u.1D(4.q,"3m",5(){7 a=d.I.17();o.n.u.14(a,"3m",d.I)})};H.6.2Q=5(){9(4.q&&4.q.3j){4.1M();o.n.u.3f(4.2X);o.n.u.4Z(4.q);4.q.3j.4S(4.q);4.q=t}};H.6.39=5(){9(4.1s){7 a=4.28(4.A);4.q.13.1J=a.y+"B";4.q.13.1F=a.x+"B"}};H.6.1M=5(){9(4.q){4.q.13.3b="2V"}4.1s=s};H.6.2a=5(){9(4.q){7 a=4.28(4.A);4.q.13.4l=4.2T(a);9(4.I.18){4.q.2R="<49 42=\'"+4.22+"\'><24 13=\'20: 2P; 1J: 2N; 1F: 2N; 1a: "+4.X+"B;\'>"+4.W.19+""}C{4.q.2R=4.W.19}9(1q 4.W.15==="1g"||4.W.15===""){4.q.15=4.I.17().2H()}C{4.q.15=4.W.15}4.q.13.3b=""}4.1s=K};H.6.2F=5(a){4.W=a;7 b=z.3X(0,a.2A-1);b=z.2e(4.P.p-1,b);7 c=4.P[b];4.22=c.1W;4.Q=c.V;4.X=c.1a;4.J=c.3R;4.1T=c.3M||[1P(4.Q/2,10),1P(4.X/2,10)];4.2k=c.3F||"3C";4.2x=c.3z||11;4.2n=c.3w||"2V";4.2C=c.3u||"3t";4.2U=c.3p||"3n";4.3k=c.5j||"5f,5c-59";4.3e=c.4X||"0 0"};H.6.3d=5(a){4.A=a};H.6.2T=5(b){7 a=[];9(!4.I.18){a.F(\'3a-4N:1W(\'+4.22+\');\');a.F(\'3a-20:\'+4.3e+\';\')}9(1q 4.J===\'4L\'){9(1q 4.J[0]===\'36\'&&4.J[0]>0&&4.J[0]<4.Q){a.F(\'V:\'+(4.Q-4.J[0])+\'B; 35-1J:\'+4.J[0]+\'B;\')}C{a.F(\'V:\'+4.Q+\'B; 34-V:\'+4.Q+\'B;\')}9(1q 4.J[1]===\'36\'&&4.J[1]>0&&4.J[1]<4.X){a.F(\'1a:\'+(4.X-4.J[1])+\'B; 35-1F:\'+4.J[1]+\'B;\')}C{a.F(\'1a:\'+4.X+\'B; 19-32:1e;\')}}C{a.F(\'V:\'+4.Q+\'B; 34-V:\'+4.Q+\'B; 1a:\'+4.X+\'B; 19-32:1e;\')}a.F(\'4D:4B; 1J:\'+b.y+\'B; 1F:\'+b.x+\'B; 4A:\'+4.2k+\'; 20:2P; 1A-1l:\'+4.2x+\'B; 1A-4x:\'+4.3k+\'; 1A-4v:\'+4.2C+\'; 1A-13:\'+4.2U+\'; 19-4u:\'+4.2n+\';\');j a.4r("")};H.6.28=5(b){7 a=4.3l().2d(b);a.x-=4.1T[1];a.y-=4.1T[0];j a};5 E(a){4.16=a;4.U=a.v();4.S=a.2S();4.12=a.3i();4.1d=a.3g();4.18=a.3h();4.k=[];4.A=t;4.23=t;4.Y=G H(4,a.21())}E.6.40=5(){j 4.k.p};E.6.1C=5(){j 4.k};E.6.2O=5(){j 4.A};E.6.v=5(){j 4.U};E.6.17=5(){j 4.16};E.6.1y=5(){7 i;7 b=G o.n.1r(4.A,4.A);7 a=4.1C();w(i=0;ib){9(e.v()!==4.U){e.L(4.U)}}C 9(c<4.12){9(e.v()!==4.U){e.L(4.U)}}C 9(c===4.12){w(i=0;ia){4.Y.1M();j}9(c<4.12){4.Y.1M();j}7 b=4.16.21().p;7 d=4.16.2G()(4.k,b);4.Y.3d(4.A);4.Y.2F(d);4.Y.2a()};E.6.2L=5(a){7 i;9(4.k.1o){j 4.k.1o(a)!==-1}C{w(i=0;i<4.k.p;i++){9(a===4.k[i]){j K}}}j s};5 8(a,c,b){4.Z(8,o.n.31);c=c||[];b=b||{};4.k=[];4.D=[];4.1n=[];4.1h=t;4.1j=s;4.S=b.3W||3V;4.12=b.3U||2;4.2c=b.2y||t;4.P=b.3T||[];4.1V=b.15||"";4.1L=K;9(b.2v!==1g){4.1L=b.2v}4.1d=s;9(b.2u!==1g){4.1d=b.2u}4.1b=s;9(b.2t!==1g){4.1b=b.2t}4.18=s;9(b.2s!==1g){4.18=b.2s}4.1z=b.3S||8.2r;4.1v=b.3Q||8.2p;4.1i=b.3L||8.2i;4.1S=b.3J||8.2m;4.1R=b.3H||8.2l;4.1x=b.3G||8.2j;4.1Q=b.3K||"N";9(3B.3N.3O().1o("3P")!==-1){4.1R=4.1x}4.2q();4.2o(c,K);4.L(a)}8.6.2D=5(){7 a=4;4.1h=4.v();4.1j=K;4.1k();4.1n=[o.n.u.1E(4.v(),"3x",5(){a.1w(s);9(4.1c()===(4.2g("3v")||0)||4.1c()===4.2g("2y")){o.n.u.14(4,"2z")}}),o.n.u.1E(4.v(),"2z",5(){a.1m()})]};8.6.2Q=5(){7 i;w(i=0;i<4.k.p;i++){9(4.k[i].v()!==4.1h){4.k[i].L(4.1h)}}w(i=0;i<4.D.p;i++){4.D[i].1B()}4.D=[];w(i=0;i<4.1n.p;i++){o.n.u.3f(4.1n[i])}4.1n=[];4.1h=t;4.1j=s};8.6.39=5(){};8.6.2q=5(){7 i,1l;9(4.P.p>0){j}w(i=0;i<4.1i.p;i++){1l=4.1i[i];4.P.F({1W:4.1z+(i+1)+"."+4.1v,V:1l,1a:1l})}};8.6.3Z=5(){7 i;7 a=4.1C();7 b=G o.n.1r();w(i=0;i3){d=G o.n.1r(4.v().1y().26(),4.v().1y().25())}C{d=G o.n.1r(G o.n.1u(38.4t,-2W.4s),G o.n.1u(-38.4q,2W.4W))}7 a=4.1X(d);7 b=z.2e(e+4.1R,4.k.p);w(i=e;i