0% found this document useful (0 votes)
3K views

ClusterMarker (0ld)

This document describes a marker manager for the Google Maps API that allows clustering of markers. It defines functions for adding and refreshing markers, including clustering markers that are close together. When markers are clustered, a new cluster marker is created and properties are assigned to relate it to the clustered markers.

Uploaded by

akshaybis1005
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3K views

ClusterMarker (0ld)

This document describes a marker manager for the Google Maps API that allows clustering of markers. It defines functions for adding and refreshing markers, including clustering markers that are close together. When markers are clustered, a new cluster marker is created and properties are assigned to relate it to the clustered markers.

Uploaded by

akshaybis1005
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 11

/*

A marker manager for the Google Maps API


http://googlemapsapi.martinpearman.co.uk/clustermarker
Copyright Martin Pearman 2009

This program is free software: you can redistribute it and/or modify it


under the terms of the GNU General Public License as published by the Free Softw
are Foundation, either version 3 of the License, or (at your option) any later v
ersion.
This program is distributed in the hope that it will be useful, but WITH
OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNES
S FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function ClusterMarker($map, $options){
var $this=this;
$this.map=$map;
$this.markers=[];
$this.clusterMarkers=[];
var $options={clusteringEnabled:true, borderPadding:100};
if(typeof($options)!=='undefined'){
// process options
$this.clusteringEnabled=$options.clusteringEnabled?$options.clus
teringEnabled:true;
$this.borderPadding=$options.borderPadding?$options.borderPaddin
g/100:0.5; // decide a default border padding from 1% to 100%
$this.iconPadding=$options.iconPadding?$options.iconPadding:0;
if($options.markers){
if($options.areOverlaid){
$areOverlaid=$options.areOverlaid;
} else {
$areOverlaid=true;
}
$this.addMarkers($options.markers, $areOverlaid);
}
}
GEvent.bind($map, 'moveend', this, $this.moveEnd);
GEvent.bind($map, 'zoomend', this, $this.zoomEnd);
GEvent.bind($map, 'maptypechanged', $this, $this.mapTypeChanged);
}
ClusterMarker.prototype.addMarkers=function($markers, $areOverlaid){ //
auto inc markers array from last time markers were added
var $this=this, $indexOffset=$this.markers.length, $length=$markers.leng
th;
if(typeof($areOverlaid)==='undefined'){
var $areOverlaid=true; // default is expect markers to be
already added to the map
}
while($length--){
$markers[$length]._ClusterMarker_={index:$length+$indexOffset, i
sActive:true, doNotCluster:false, anchorPoint:[], intersectTable:[], isOverlaid:
$areOverlaid};
}
$this.markers=$this.markers.concat($markers);
};
ClusterMarker.prototype.refresh=function(){
var $this=this;
// helper functions for cluster marker add/remove
function compareArrays($array1, $array2){
if($array1.length!==$array2.length){
return false;
}
var i, $length=$array1.length<$array2.length?$array1.length:$arr
ay2.length;
for (i=0; i<$length; i++){
if($array1[i]!==$array2[i]){
return false;
}
}
return true;
}
function createClusterMarker($indexes){
var $icon=new GIcon(G_DEFAULT_ICON, '/greenmarker/top/red.png');
var $clusterBounds=new GLatLngBounds(), $length=$indexes.length,
i, $markers=$this.markers;
while($length--){
$clusterBounds.extend($markers[$indexes[$length]].getLat
Lng());
}
// anchor cluster marker to lat/lng of marker nearest to cl
uster center
// either an error in my code or distanceFrom() is not accu
rate enough to be useable
/* $length=$indexes.length;
var $distance, $minDistance, $index, $nearestIndex, $clusterCent
er=$clusterBounds.getCenter();
while($length--){
$index=$indexes[$length];
$distance=$markers[$index].getLatLng().distanceFrom($clu
sterCenter);
if($length===$indexes.length-1 || $distance<$minDistance
){
$minDistance=$distance;
$nearestIndex=$index;
}
}
var $clusterMarker=new GMarker($markers[$nearestIndex].getLatLng
(), {icon:$icon, title:$indexes.length+' markers in this cluster'}); */
// old behaviour to center cluster marker at cluster center
// var $clusterMarker=new GMarker($clusterBounds.getCenter(
), {icon:$icon, title:$indexes.length+' markers in this cluster'});
// use (1st) clustered marker anchor as cluster marker anch
or
var $clusterMarker=new GMarker($markers[$indexes[0]].getLatLng()
, {icon:$icon, title:$indexes.length+' markers in this cluster'});

GEvent.addListener($clusterMarker, 'click', function(){


var $options={};
$this.clusterMarkerClick($clusterMarker, $options);
});
$clusterMarker._markerIndexes=$indexes; // would be more us
eful as an array of markers
$clusterMarker._clusterBounds=$clusterBounds; // save as
property or discard?
$length=$indexes.length;
while($length--){
$markers[$indexes[$length]]._clusterMarker=$clusterMarke
r;
}
return $clusterMarker;
}
// filter active markers
var $map=$this.map, $zoom=$map.getZoom(), $bounds=$map.getBounds(), $mar
kers=$this.markers, $length=$markers.length, $marker, $isActive;
// variable border padding as a % of screen size
var $mapSize=$map.getSize(), $borderPadding=1,/*$this.borderPadding,*/ $
borderPaddingX=$mapSize.width*$borderPadding, $borderPaddingY=$mapSize.height*$b
orderPadding;
// to do: compare new method to previous version
var $projection=$map.getCurrentMapType().getProjection();
var $mapSwPoint=$projection.fromLatLngToPixel($bounds.getSouthWest(), $z
oom);
var $mapNePoint=$projection.fromLatLngToPixel($bounds.getNorthEast(), $z
oom);
var $swX=$mapSwPoint.x-$borderPaddingX, $swY=$mapSwPoint.y+$borderPaddin
gY, $neX=$mapNePoint.x+$borderPaddingX, $neY=$mapNePoint.y-$borderPaddingY;
var $activeSwLatLng=$projection.fromPixelToLatLng(new GPoint($swX, $swY)
, $zoom); // is unbounded option required here?
var $activeNeLatLng=$projection.fromPixelToLatLng(new GPoint($neX, $neY)
, $zoom);
$bounds.extend($activeSwLatLng); // could create new GLatLng
Bounds() instead of extending existing $bounds
$bounds.extend($activeNeLatLng);
while($length--){
$marker=$markers[$length];
$isActive=false;
if(!$marker.isHidden() && $bounds.containsLatLng($marker.getLatL
ng())){
$isActive=true;
}
$marker._ClusterMarker_.isActive=$isActive; // not requ
ired - or keep so active markers can be quickly enumerated?
$marker._makeVisible=$isActive;
}
// filter clustered markers
var $newClusterIndexes=[];
if($this.clusteringEnabled){
var i=$markers.length, j, $indexes, $cancelCluster;
while(i--){
if($markers[i]._makeVisible){
$indexes=[i];
j=i;
while(j--){
if($markers[j]._makeVisible && $this.mar
kerIconsIntersect($markers[i], $markers[j])){
$indexes.push(j);
}
}
if($indexes.length>1){
$cancelCluster=false;
j=$indexes.length;
while(j--){
if($markers[$indexes[j]]._Cluste
rMarker_.doNotCluster){ // instead of marker doNotCluster property have an
array of markers which should not be clustered - more functional for more than o
ne marker
$cancelCluster=true;
} else {
$markers[$indexes[j]]._m
akeVisible=false;
}
}
// array $newClusterIndexes is arra
y of array of indexes of clustered markers;
if(!$cancelCluster){
$newClusterIndexes.push($indexes
);
}
}
}
}
}
// update map
// add or remove cluster markers
var $clusterMarkers=$this.clusterMarkers, $clusterMarkersLength=$cluster
Markers.length, $clusterMarker, $newClusterMarkers=[], k;
var $newClusterIndexesLength=$newClusterIndexes.length, $clusterIndex;
// remove cluster markers who markerIndexes no longer exist and fla
g the relates index array not to be created
for(i=0; i<$clusterMarkersLength; i++){
$clusterMarker=$clusterMarkers[i];
$clusterIndex=$clusterMarker._markerIndexes;
for(j=0; j<$newClusterIndexesLength; j++){
if(compareArrays($clusterIndex, $newClusterIndexes[j])){
// cluster marker markerIndexes still exist
so do not remove cluster marker
$newClusterMarkers.push($clusterMarker);
$newClusterIndexes[j]=false;
break; // break out of j loop
}
}
if($newClusterIndexes[j]!==false){
$indexes=$clusterMarker._markerIndexes;
$length=$indexes.length;
while($length--){
delete $this.markers[$indexes[$length]]._cluster
Marker;
}
$map.removeOverlay($clusterMarker);
}
}
// loop thru $newClusterIndexes creating cluster markers for any in
dex which is not FALSE
while($newClusterIndexesLength--){
$indexes=$newClusterIndexes[$newClusterIndexesLength];
if($indexes!==false){
$clusterMarker=createClusterMarker($newClusterIndexes[$n
ewClusterIndexesLength]);
$newClusterMarkers.push($clusterMarker);
$map.addOverlay($clusterMarker);
}
}
$this.clusterMarkers=$newClusterMarkers; // quicker to use a
rray splice?
// add or remove active markers
$length=$markers.length;
while($length--){
$marker=$markers[$length];
if($marker._makeVisible && !$marker._ClusterMarker_.isOverlaid){
$map.addOverlay($marker);
$marker._ClusterMarker_.isOverlaid=true;
} else if(!$marker._makeVisible && $marker._ClusterMarker_.isOve
rlaid){
$map.removeOverlay($marker);
$marker._ClusterMarker_.isOverlaid=false;
}
}
};
ClusterMarker.prototype.getMarkerAnchorPoint=function($marker, $zoom){
if(typeof($marker._ClusterMarker_.anchorPoint[$zoom])!=='undefined'){
return $marker._ClusterMarker_.anchorPoint[$zoom];
} else {
var $projection=this.map.getCurrentMapType().getProjection();
var $anchorPoint=$projection.fromLatLngToPixel($marker.getLatLng
(), $zoom);
$marker._ClusterMarker_.anchorPoint[$zoom]=$anchorPoint;
return $anchorPoint;
}
};
ClusterMarker.prototype.clusterMarkerClick=function($clusterMarker, $options){
// problems possible with inaccurate value returned by .getMaximumR
esolution()
// see link in bookmarks toolbar
// maybe zoomIn() or setZoom() and check if getZoom() has increased
to establish if map can zoom in any further
// but that might cause performance issues..?
// http://code.google.com/apis/maps/documentation/reference.html#GM
apType
// getMaxZoomAtLatLng()
var $this=this, $map=$this.map, $indexes=$clusterMarker._markerIndexes;
// what should a click on a cluster marker do?
// 1: old behaviour to fit map to clustered markers
// $map.setCenter($clusterMarker._clusterBounds.getCenter(), $map.g
etBoundsZoomLevel($clusterMarker._clusterBounds));
// 2: center map on cluster group center and zoom in a level
// $map.setCenter($clusterMarker._clusterBounds.getCenter());
// $map.zoomIn();
// 3: display infowindow of links to all markers in cluster and use
the doNotCluster property of any selected marker to uncluster it
// this method will become the default cluster marker click for eve
ry map type's maximum zoom level
// (ie unclusterable markers)
// update this method to use uncluster if map not fully zoomed in a
nd donotcluster if map fully zoomed in
function sortByMarkerTitle(a, b){
var title1=a.getTitle(), title2=b.getTitle();
if(title1<title2){
return -1;
} else if (title1>title2){
return 1;
} else {
return 0;
}
}
function unclusterAndClick($marker){
return function(){
$map.setZoom($this.getMinUnclusterLevel($marker));
$map.setCenter($marker.getLatLng());
GEvent.trigger($marker, 'click');
}
}
function doNotClusterAndClick($marker){
return function(){
$marker._ClusterMarker_.doNotCluster=true;
$this.refresh();
GEvent.trigger($marker, 'click');
var i=GEvent.addListener($this.map, 'infowindowclose', f
unction(){
$marker._ClusterMarker_.doNotCluster=false;
$this.refresh();
GEvent.removeListener(i);
});
}
}
var i, $length=$indexes.length, $maxContent=document.createElement('div'
), $minContent=document.createElement('div'), $link, $marker, $markers=[];
i=$length;
while(i--){
$markers.push($this.markers[$indexes[i]]);
}
$markers.sort(sortByMarkerTitle);
for(i=0; i<$length; i++){
$marker=$markers[i];
$link=document.createElement('a');
$link.href='javascript:return void()';
// this does not handle cluster markers at less than max zo
om that persist to max zoom - their click action is wrong
// so link click must detect if map fully zoomed in and the
n execute appropriate function
if($map.getZoom()<$map.getCurrentMapType().getMaximumResolution(
)){
$link.onclick=unclusterAndClick($marker);
} else {
$link.onclick=doNotClusterAndClick($marker);
}

$link.appendChild(document.createTextNode($marker.getTitle().rep
lace(/ /g, '_')));
$maxContent.appendChild($link);
if(i<$length-1){
$maxContent.appendChild(document.createTextNode(' | '));
}
}
$minContent.appendChild(document.createElement('br'));
$link=document.createElement('a');
$link.href='javascript:return void()';
$link.onclick=function(){
$this.map.getInfoWindow().maximize();
};
$link.appendChild(document.createTextNode('Show links'));
$minContent.appendChild($link);
$minContent.appendChild(document.createTextNode(' | '));
// if map fully zoomed in then no need for zoom in links and might
as well show links by default
$link=document.createElement('a');
$link.href='javascript:return void()';
$link.onclick=function(){
$map.setCenter($clusterMarker._clusterBounds.getCenter(), $map.g
etBoundsZoomLevel($clusterMarker._clusterBounds));
};
$link.appendChild(document.createTextNode('Fit map to cluster'));
$minContent.appendChild($link);
$minContent.appendChild(document.createTextNode(' | '));
$link=document.createElement('a');
$link.href='javascript:return void()';
$link.onclick=function(){
// $map.panTo($clusterMarker._clusterBounds.getCenter());
$map.zoomIn();
};
$link.appendChild(document.createTextNode('Zoom in'));
$minContent.appendChild($link);
$clusterMarker.openInfoWindow($minContent, {maxContent:$maxContent, maxT
itle:"Markers within this cluster"});
// debug option: just display a simple infowindow
// $clusterMarker.openInfoWindowHtml($indexes.length+' markers in t
his cluster');
};
// make these three event handlers into anon functions in constructor?
ClusterMarker.prototype.zoomEnd=function(){
this._cancelMoveEnd=true;
this.refresh();
};
ClusterMarker.prototype.moveEnd=function(){
if(this._cancelMoveEnd){
this._cancelMoveEnd=false;
} else {
this.refresh();
}
};
ClusterMarker.prototype.mapTypeChanged=function(){
// refresh can be assigned directly to listener if no other functio
ns are required
this.refresh();
};

ClusterMarker.prototype.markerIconsIntersect=function($marker1, $marker2, $zoom)


{ // optional zoom parameter so triggerClick can be implemented
var $this=this, $map=$this.map;
function getIconPointBounds($marker){
var $icon=$marker.getIcon();
var $iconSize=$icon.iconSize;
var $iconAnchorPoint=$icon.iconAnchor;
var $markerAnchorPoint=$this.getMarkerAnchorPoint($marker, $zoom
);
var $iconPadding=$this.iconPadding;
var $swIconAnchorPoint=new GPoint($markerAnchorPoint.x-$iconAnch
orPoint.x-$iconPadding, $markerAnchorPoint.y-$iconAnchorPoint.y+$iconSize.height
+$iconPadding);
var $neIconAnchorPoint=new GPoint($markerAnchorPoint.x-$iconAnch
orPoint.x+$iconSize.width+$iconPadding, $markerAnchorPoint.y-$iconAnchorPoint.y-
$iconPadding);
return {sw:$swIconAnchorPoint, ne:$neIconAnchorPoint};
}
if(typeof($zoom)==='undefined'){
$zoom=$map.getZoom();
}
if(typeof($marker1._ClusterMarker_.intersectTable[$zoom])!=='undefined'
&& typeof($marker1._ClusterMarker_.intersectTable[$zoom][$marker2._ClusterMarker
_.index])!=='undefined'){
return $marker1._ClusterMarker_.intersectTable[$zoom][$marker2._
ClusterMarker_.index];
}
/* if($marker2._ClusterMarker_.intersectTable[$zoom] && $marker2._C
lusterMarker_.intersectTable[$zoom][$marker1._ClusterMarker_.index]){
return $marker2._ClusterMarker_.intersectTable[$zoom][$marker1._
ClusterMarker_.index];
} */
if(typeof($marker1)==='undefined' || typeof($marker2)==='undefined'){
GLog.write('Undefined markers');
}
var $bounds1=getIconPointBounds($marker1), $bounds2=getIconPointBounds($
marker2);
var $intersects=!($bounds2.sw.x>$bounds1.ne.x || $bounds2.ne.x<$bounds1.
sw.x || $bounds2.ne.y>$bounds1.sw.y || $bounds2.sw.y<$bounds1.ne.y);
if(typeof($marker1._ClusterMarker_.intersectTable[$zoom])==='undefined')
{
$marker1._ClusterMarker_.intersectTable[$zoom]=[];
}
$marker1._ClusterMarker_.intersectTable[$zoom][$marker2._ClusterMarker_.
index]=$intersects;
/* if(!$marker2._ClusterMarker_.intersectTable[$zoom]){
$marker2._ClusterMarker_.intersectTable[$zoom]=[];
}
$marker2._ClusterMarker_.intersectTable[$zoom][$marker1._ClusterMarker_.
index]=$intersects; */
return $intersects;
};
ClusterMarker.prototype.removeMarkers=function($markers){
if(!$markers){ // <<< looks bugggy
for(var i=0; i<this.markers.length; i++){
this.map.removeOverlay(this.markers[i]); //
marker properties to delete
}
this.markers=[];
for(i=0; i<this.clusterMarkers.length; i++){
this.map.removeOverlay(this.clusterMarkers[i]); //
event listener property to implement
}
this.clusterMarkers=[];
}
};

ClusterMarker.prototype.fitMapToMarkers=function($markers, $maxZoom){
var $this=this, $bounds=new GLatLngBounds(), $refresh=false;
if(typeof($markers)==='undefined' || $markers===null){
var $markers=this.markers;
}
var $length=$markers.length;
while($length--){
if(!$markers[$length].isHidden()){
$bounds.extend($markers[$length].getLatLng());
$refresh=true;
}
}
if($refresh){
var $zoom=$this.map.getBoundsZoomLevel($bounds);
if(typeof($maxZoom)!=='undefined'){
$zoom=$zoom>$maxZoom?$maxZoom:$zoom;
}
$this.map.setCenter($bounds.getCenter(), $zoom);
}
};

ClusterMarker.prototype.getMinUnclusterLevel=function($marker){
var $this=this, $map=$this.map, $maxZoomLevel=$map.getCurrentMapType().g
etMaximumResolution(), $isClustered, $markers=$this.markers, $length=$markers.le
ngth, $indexes=[], i, $zoomLevel;
while($length--){
if($marker!==$markers[$length]){
$indexes.push($markers[$length]._ClusterMarker_.index);
// will need updating when remove single (or more) markers is implemented t
o avoid undefined elements in this.markers array
}
}
if($marker._clusterMarker){
$zoomLevel=$map.getZoom()+1;
} else {
$zoomLevel=0;
}
$length=$indexes.length;
while($zoomLevel<=$maxZoomLevel){
$isClustered=false;
i=$length;
while(!$isClustered && i--){
if($this.markerIconsIntersect($marker, $markers[$indexes
[i]], $zoomLevel)){ // add support for hidden markers?
$isClustered=true;
}
}
if(!$isClustered){
break;
}
$zoomLevel++;
}
return $zoomLevel; // should return $zoomLevel-1 ??
/*
if($isUnclustered){
return $zoomLevel; // -1 ?
} else {
// marker is unclusterable with the current map type
return false;
}
*/
};

ClusterMarker.prototype.setDoNotCluster=function($marker, $state){
$marker._ClusterMarker_.doNotCluster=$state;
this.refresh();
};
ClusterMarker.prototype.newMethod=function(){
};

You might also like