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
//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.
Option name | Type | Description |
---|---|---|
map | google.maps.Map | The Google map to attach to. |
opt_markers | Array.<google.maps.Marker> | Optional markers to add to the cluster. |
opt_options | Object | support the following options: 'gridSize': (number) The grid size of a cluster in pixels. |
A Marker Clusterer that clusters markers.
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;
this.clusters_ = [];
this.sizes = [53, 56, 66, 78, 90];
Fit the map to the bounds of the markers in the clusterer.
MarkerClusterer.prototype.fitMapToMarkers = function () {
var markers = this.getMarkers();
var bounds = new google.maps.LatLngBounds();
for (var i = 0, marker; marker = markers[i]; i++) {
bounds.extend(marker.getPosition());
}
this.map_.fitBounds(bounds);
};
Option name | Type | Description |
---|---|---|
styles | Object | The style to set. |
Sets the styles.
MarkerClusterer.prototype.setStyles = function (styles) {
this.styles_ = styles;
};
Gets the styles.
MarkerClusterer.prototype.getStyles = function () {
return this.styles_;
};
Whether zoom on click is set.
MarkerClusterer.prototype.isZoomOnClick = function () {
return this.zoomOnClick_;
};
Whether average center is set.
MarkerClusterer.prototype.isAverageCenter = function () {
return this.averageCenter_;
};
Returns the array of markers in the clusterer.
MarkerClusterer.prototype.getMarkers = function () {
return this.markers_;
};
Returns the number of markers in the clusterer
MarkerClusterer.prototype.getTotalMarkers = function () {
return this.markers_.length;
};
Option name | Type | Description |
---|---|---|
maxZoom | number | The max zoom level. |
Sets the max zoom for the clusterer.
MarkerClusterer.prototype.setMaxZoom = function (maxZoom) {
this.maxZoom_ = maxZoom;
};
Gets the max zoom for the clusterer.
MarkerClusterer.prototype.getMaxZoom = function () {
return this.maxZoom_;
};
Option name | Type | Description |
---|---|---|
calculator | function(Array, number) | The function to set as the calculator. The function should return a object properties: |
Set the calculator function.
MarkerClusterer.prototype.setCalculator = function (calculator) {
this.calculator_ = calculator;
};
Get the calculator function.
MarkerClusterer.prototype.getCalculator = function () {
return this.calculator_;
};
Option name | Type | Description |
---|---|---|
markers | Array.<google.maps.Marker> | The markers to add. |
opt_nodraw | boolean | Whether to redraw the clusters. |
Add an array of markers to the clusterer.
MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) {
for (var i = 0, marker; marker = markers[i]; i++) {
this.pushMarkerTo_(marker);
}
if (!opt_nodraw) {
this.redraw();
}
};
Option name | Type | Description |
---|---|---|
marker | google.maps.Marker | The marker to add. |
opt_nodraw | boolean | Whether to redraw the clusters. |
Adds a marker to the clusterer and redraws if needed.
MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) {
this.pushMarkerTo_(marker);
if (!opt_nodraw) {
this.redraw();
}
};
Option name | Type | Description |
---|---|---|
marker | google.maps.Marker | The marker to remove. |
opt_nodraw | boolean | Optional boolean to force no redraw. |
return | boolean | True if the marker was removed. |
Remove a marker from the cluster.
MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) {
var removed = this.removeMarker_(marker);
if (!opt_nodraw && removed) {
this.resetViewport();
this.redraw();
return true;
} else {
return false;
}
};
Option name | Type | Description |
---|---|---|
markers | Array.<google.maps.Marker> | The markers to remove. |
opt_nodraw | boolean | Optional boolean to force no redraw. |
Removes an array of markers from the cluster.
MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) {
var removed = false;
for (var i = 0, marker; marker = markers[i]; i++) {
var r = this.removeMarker_(marker);
removed = removed || r;
}
if (!opt_nodraw && removed) {
this.resetViewport();
this.redraw();
return true;
}
};
Returns the number of clusters in the clusterer.
MarkerClusterer.prototype.getTotalClusters = function () {
return this.clusters_.length;
};
Returns the google map that the clusterer is associated with.
MarkerClusterer.prototype.getMap = function () {
return this.map_;
};
Option name | Type | Description |
---|---|---|
map | google.maps.Map | The map. |
Sets the google map that the clusterer is associated with.
MarkerClusterer.prototype.setMap = function (map) {
this.map_ = map;
};
Returns the size of the grid.
MarkerClusterer.prototype.getGridSize = function () {
return this.gridSize_;
};
Option name | Type | Description |
---|---|---|
size | number | The grid size. |
Sets the size of the grid.
MarkerClusterer.prototype.setGridSize = function (size) {
this.gridSize_ = size;
};
Returns the min cluster size.
MarkerClusterer.prototype.getMinClusterSize = function () {
return this.minClusterSize_;
};
Option name | Type | Description |
---|---|---|
size | number | The grid size. |
Sets the min cluster size.
MarkerClusterer.prototype.setMinClusterSize = function (size) {
this.minClusterSize_ = size;
};
Option name | Type | Description |
---|---|---|
bounds | google.maps.LatLngBounds | The bounds to extend. |
return | google.maps.LatLngBounds | The extended bounds. |
Extends a bounds object by the grid size.
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;
};
Clears all clusters and markers from the clusterer.
MarkerClusterer.prototype.clearMarkers = function () {
this.resetViewport(true);
// Set the markers a empty array.
this.markers_ = [];
};
Option name | Type | Description |
---|---|---|
opt_hide | boolean | To also hide the marker. |
Clears all existing clusters and recreates them.
MarkerClusterer.prototype.resetViewport = function (opt_hide) {
// 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;
if (opt_hide) {
marker.setClustered(true);
}
}
this.clusters_ = [];
};
MarkerClusterer.prototype.repaint = function () {
var oldClusters = this.clusters_.slice();
this.clusters_.length = 0;
this.resetViewport();
this.redraw();
// Remove the old clusters.
// Do it in a timeout so the other clusters have been drawn first.
window.setTimeout(function () {
for (var i = 0, cluster; cluster = oldClusters[i]; i++) {
cluster.remove();
}
}, 0);
};
Redraws the clusters.
MarkerClusterer.prototype.redraw = function () {
this.createClusters_();
};
Option name | Type | Description |
---|---|---|
marker | google.maps.Marker | The marker to check. |
return | boolean | True if the marker is already added. |
Determins if a marker is already added to the cluster.
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;
};
Option name | Type | Description |
---|---|---|
marker | google.maps.Marker | The marker to add. |
return | boolean | True if the marker was added. |
Add a marker the cluster.
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 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);
var len = this.markers_.length;
if (len < this.minClusterSize_) {
// Min cluster size not reached so show the marker.
marker.setClustered(false);
}
if (len == this.minClusterSize_) {
// Hide the markers that were showing.
for (var i = 0; i < len; i++) {
this.markers_[i].setClustered(true);
}
}
if (len >= this.minClusterSize_) {
marker.setClustered(true);
}
this.updateIcon();
return true;
};
Returns the marker clusterer that the cluster is associated with.
Cluster.prototype.getMarkerClusterer = function () {
return this.markerClusterer_;
};
Returns the bounds of the cluster.
Cluster.prototype.getBounds = function () {
var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
var markers = this.getMarkers();
for (var i = 0, marker; marker = markers[i]; i++) {
bounds.extend(marker.getPosition());
}
return bounds;
};
Removes the cluster
Cluster.prototype.remove = function () {
this.clusterIcon_.remove();
this.markers_.length = 0;
delete this.markers_;
};
Returns the center of the cluster.
Cluster.prototype.getSize = function () {
return this.markers_.length;
};
Returns the center of the cluster.
Cluster.prototype.getMarkers = function () {
return this.markers_;
};
Returns the center of the cluster.
Cluster.prototype.getCenter = function () {
return this.center_;
};
Option name | Type | Description |
---|---|---|
marker | google.maps.Marker | The marker to check. |
return | boolean | True if the marker lies in the bounds. |
Determines if a marker lies in the clusters bounds.
Cluster.prototype.isMarkerInClusterBounds = function (marker) {
return this.bounds_.contains(marker.getPosition());
};
Returns the map that the cluster is associated with.
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 (mz && 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.setClustered(false);
}
return;
}
if (this.markers_.length < 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_.setSums(sums);
this.clusterIcon_.show();
};
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()) {
// Zoom into the cluster.
this.map_.fitBounds(this.cluster_.getBounds());
}
};
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);
};
Option name | Type | Description |
---|---|---|
sums | Object | The sums containing: 'text': (string) The text to display in the icon. |
Set the sums 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'];
this.backgroundPosition_ = style['backgroundPosition'];
};
Option name | Type | Description |
---|---|---|
center | google.maps.LatLng | The latlng to set as the center. |
Sets the center of the icon.
ClusterIcon.prototype.setCenter = function (center) {
this.center_ = center;
};
Option name | Type | Description |
---|---|---|
pos | google.maps.Point | The position. |
return | string | The css style text. |
Create the css text based on the position of the icon.
ClusterIcon.prototype.createCss = function (pos) {
var style = [];
//style.push('background-image:url(' + this.url_ + ');');
//var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0';
//style.push('background-position:' + 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;');
}
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['fitMapToMarkers'] =
MarkerClusterer.prototype.fitMapToMarkers;
MarkerClusterer.prototype['getCalculator'] =
MarkerClusterer.prototype.getCalculator;
MarkerClusterer.prototype['getGridSize'] =
MarkerClusterer.prototype.getGridSize;
MarkerClusterer.prototype['getExtendedBounds'] =
MarkerClusterer.prototype.getExtendedBounds;
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['removeMarkers'] =
MarkerClusterer.prototype.removeMarkers;
MarkerClusterer.prototype['resetViewport'] =
MarkerClusterer.prototype.resetViewport;
MarkerClusterer.prototype['repaint'] =
MarkerClusterer.prototype.repaint;
MarkerClusterer.prototype['setCalculator'] =
MarkerClusterer.prototype.setCalculator;
MarkerClusterer.prototype['setGridSize'] =
MarkerClusterer.prototype.setGridSize;
MarkerClusterer.prototype['setMaxZoom'] =
MarkerClusterer.prototype.setMaxZoom;
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;