Вы находитесь на странице: 1из 10

/*

ClusterMarker Version 2
A marker manager for the Google Maps API
http://googlemapsapi.martinpearman.co.uk/clustermarker
Copyright Martin Pearman 2009
Last updated 13th December 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){
var $this=this;
$this.map=$map;
$this.markers=[];
$this.clusterMarkers=[];
$this.clusteringEnabled=true;
// using the plain green icon for cluster marker icons
// you can change this icon here in ClusterMarker.js
// or in map.php you could set:
// cluster.clusterMarkerIcon=some new icon
$this.clusterMarkerIcon=new GIcon(G_DEFAULT_ICON, 'http://maps.gstatic.c
om/intl/en_ALL/mapfiles/marker_green.png');
GEvent.bind($map, 'moveend', this, $this.moveEnd);
GEvent.bind($map, 'zoomend', this, $this.zoomEnd);
GEvent.bind($map, 'maptypechanged', $this, $this.mapTypeChanged);
}
// add markers and initialise some custome properties for the markers
// this method adds markers but does not delete/remove any existing markers
ClusterMarker.prototype.addMarkers=function($markers, $areOverlaid){
var $this=this, $indexOffset=$this.markers.length, $length=$markers.leng
th, $marker;
if(typeof($areOverlaid)==='undefined'){
$areOverlaid=false;
}
while($length--){
$marker=$markers[$length];
$marker._ClusterMarker_={id:$length+$indexOffset, isActive:true,
doNotCluster:false, anchorPoint:[], intersectTable:[], isOverlaid:$areOverlaid}
;
}
$this.markers=$this.markers.concat($markers);
};
// the refresh() method is the method that does all the updating
ClusterMarker.prototype.refresh=function(){
var $this=this;
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 $clusterBounds=new GLatLngBounds(), $length=$indexes.length,
$markers=$this.markers;
while($length--){
$clusterBounds.extend($markers[$indexes[$length]].getLat
Lng());
}
var $clusterMarker=new GMarker($markers[$indexes[0]].getLatLng()
, {icon:$this.clusterMarkerIcon, title:$indexes.length+' markers in this cluster
'});
GEvent.addListener($clusterMarker, 'click', function(){
$this.clusterMarkerClickHandler($clusterMarker);
});
$clusterMarker._markerIndexes=$indexes;
$clusterMarker._clusterBounds=$clusterBounds;
$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;
var $mapSize=$map.getSize(), $borderPaddingX=$mapSize.width, $borderPadd
ingY=$mapSize.height;
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);
var $activeNeLatLng=$projection.fromPixelToLatLng(new GPoint($neX, $neY)
, $zoom);
$bounds.extend($activeSwLatLng);
$bounds.extend($activeNeLatLng);
// if a marker is within the map bounds plus the border padding the
n it's isActive and makeVisible properties are set to true
// the makeVisible property is not part of the _ClusterMarker_ prop
erty
// (this is an early version 2 of ClusterMarker and i haven't decid
ed upon the exact format to use for each marker's custom properties)
while($length--){
$marker=$markers[$length];
$isActive=false;
if(!$marker.isHidden() && $bounds.containsLatLng($marker.getLatL
ng())){
$isActive=true;
}
$marker._ClusterMarker_.isActive=$isActive;
$marker._makeVisible=$isActive;
}
// filter clustered markers
var $newClusterIndexes=[];
if($this.clusteringEnabled){
var i=$markers.length, j, $indexes, $cancelCluster;
// loop thru all markers, if a marker is visible and it's i
con intersects the icon of the marker it is being compared to then push it to th
e $indexes array
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 the marker icon doesn't intersect any
other marker icons then $index will have a single element
// so if $index.length is greater than 1 th
en a cluster of 2 or more markers with intersecting markers has been found
if($indexes.length>1){
$cancelCluster=false;
j=$indexes.length;
// loop thru the cluster of markers
whose icons intersect
// if any marker in that cluster ha
s it's doNotCluster property set to true then we do not want to create a cluster
marker
// instead the marker whose doNotCl
uster property is true will be displayed and any other markers that are in this
cluster are not displayed
while(j--){
if($markers[$indexes[j]]._Cluste
rMarker_.doNotCluster){
$cancelCluster=true;
} else {
$markers[$indexes[j]]._m
akeVisible=false;
}
}
// $indexes is an array of markers
whose icons intersect
// $newClusterIndexes is an array o
f arrays
if(!$cancelCluster){
$newClusterIndexes.push($indexes
);
}
}
}
}
}
// update map
// add or remove cluster markers
var $clusterMarkers=$this.clusterMarkers, $clusterMarkersLength=$cluster
Markers.length, $clusterMarker, $newClusterMarkers=[], i, j, $indexes;
var $newClusterIndexesLength=$newClusterIndexes.length, $clusterIndex;
// remove cluster markers whose markerIndexes no longer exist and f
lag the related 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])){
// no need to create a cluster marker if a
cluster marker already exists and that cluster marker contains the same markers
that we want to create a cluster marker for
$newClusterMarkers.push($clusterMarker);
// to flag this array of markers as belongi
ng to a cluster marker that already exists change it's value from the array to f
alse
$newClusterIndexes[j]=false;
break; // break out of j loop
}
}
if($newClusterIndexes[j]!==false){
// this cluster marker is not going to be reused/re
cycled - it is going to be removed from the map
// so delete the _clusterMarker property of each ma
rker within that cluster
$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;
// add or remove active markers
$length=$markers.length;
while($length--){
$marker=$markers[$length];
if($marker._makeVisible && !$marker._ClusterMarker_.isOverlaid){
// if a marker's makeVisible property is true and i
t's isOverlaid property is false then add it to the map
$map.addOverlay($marker);
$marker._ClusterMarker_.isOverlaid=true;
} else if(!$marker._makeVisible && $marker._ClusterMarker_.isOve
rlaid){
// if a marker's makeVisible property is false and
it's isOverlaid property is true then remove it from the map
$map.removeOverlay($marker);
$marker._ClusterMarker_.isOverlaid=false;
}
}
// trigger a custom event 'refreshed' each time ClusterMarker's ref
resh() method has finished executing
// this is commented out - it is intended to be used when a dynamic
sidebar is attached to the map
// the sidebar would listen for the 'refreshed' event and then upda
te itself to reflect the state of the map markers
// GEvent.trigger($this, 'refreshed');
};
// this method returns pixel coordinates of a marker's lat/lng position on
the map
// if this calculation has already been done then it's value will have been
cached and the cached value is returned
// otherwise the value is calculated and cached before being returned
ClusterMarker.prototype.getMarkerAnchorPoint=function($marker, $zoom){
if(typeof($marker._ClusterMarker_.anchorPoint[$zoom])!=='undefined'){
return $marker._ClusterMarker_.anchorPoint[$zoom];
} else {
var $anchorPoint=this.map.getCurrentMapType().getProjection().fr
omLatLngToPixel($marker.getLatLng(), $zoom);
$marker._ClusterMarker_.anchorPoint[$zoom]=$anchorPoint;
return $anchorPoint;
}
};
// this method handles any clicks on a cluster marker
ClusterMarker.prototype.clusterMarkerClickHandler=function($clusterMarker){
var $this=this, $map=$this.map, $indexes=$clusterMarker._markerIndexes;
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;
}
}
// this function handles clicks on links to markers in a cluster ma
rker infowindow
function $clusterMarkerInfoWindowClickHandler($marker){
return function(){
GEvent.trigger($marker, 'click');
};
}
var i, $length=$indexes.length, $maxContent=document.createElement('div'
), $minContent=document.createElement('div'), $link, $img, $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:void(0)';
$link.onclick=$clusterMarkerInfoWindowClickHandler($marker);
$link.appendChild(document.createTextNode($marker.getTitle()));
$maxContent.appendChild($link);
$img=document.createElement('img');
$img.src=$marker._tpath;
$img.width=50;
$img.height=50;
$maxContent.appendChild($img);
if(i<$length-1){
$maxContent.appendChild(document.createElement('br'));
}
}
$minContent.appendChild(document.createElement('br'));
$link=document.createElement('a');
$link.href='javascript:void(0)';
$link.onclick=function(){
$this.map.getInfoWindow().maximize();
};
$link.appendChild(document.createTextNode('Show links'));
$minContent.appendChild($link);
// if map fully zoomed in then no need for zoom in link - in fact t
he 'Show links' link could be omitted and all links displayed when the infowindo
w first opens
// (that is if there are not too many links - otherwise the infowin
dow will likely overflow)
if($map.getZoom()<$map.getCurrentMapType().getMaximumResolution()){
$minContent.appendChild(document.createTextNode(' | '));
/* $link=document.createElement('a');
$link.href='javascript:void(0)';
$link.onclick=function(){
$map.setCenter($clusterMarker._clusterBounds.getCenter()
, $map.getBoundsZoomLevel($clusterMarker._clusterBounds));
};
$link.appendChild(document.createTextNode('Fit map to cluster'))
;
$minContent.appendChild($link);
$minContent.appendChild(document.createTextNode(' | '));
*/
$link=document.createElement('a');
$link.href='javascript:void(0)';
$link.onclick=function(){
$map.zoomIn();
};
$link.appendChild(document.createTextNode('Zoom in'));
$minContent.appendChild($link);
}
$clusterMarker.openInfoWindow($minContent, {maxContent:$maxContent, maxT
itle:"Markers in this cluster"});
};
// zoomEnd, moveEnd and mapTypeChanged are all GMap2 events that ClusterMar
ker needs to listen for and update itself when any of these events occurs
ClusterMarker.prototype.zoomEnd=function(){
this._cancelMoveEnd=true;
this.refresh();
};
// a map zoom will trigger both a zoomEnd and moveEnd event/
// no need to execute ClusterMarker's refresh() event twice so the cancelMoveEnd
property is used as a flag to avoid executing refresh() twice
ClusterMarker.prototype.moveEnd=function(){
if(this._cancelMoveEnd){
this._cancelMoveEnd=false;
} else {
this.refresh();
}
};
ClusterMarker.prototype.mapTypeChanged=function(){
this.refresh();
};
// here we build a pair of pixel coordinates for each marker's icon
// the pair representing the marker's icon's south-west and north-east poin
ts on the map
// we can then calculate whether or not two marker icons intersect
// all calculated values are cached so that they do not need to be re-cacul
ated if required again
ClusterMarker.prototype.markerIconsIntersect=function($marker1, $marker2, $zoom)
{
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 $swIconAnchorPoint=new GPoint($markerAnchorPoint.x-$iconAnch
orPoint.x, $markerAnchorPoint.y-$iconAnchorPoint.y+$iconSize.height);
var $neIconAnchorPoint=new GPoint($markerAnchorPoint.x-$iconAnch
orPoint.x+$iconSize.width, $markerAnchorPoint.y-$iconAnchorPoint.y);
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
_.id])!=='undefined'){
return $marker1._ClusterMarker_.intersectTable[$zoom][$marker2._
ClusterMarker_.id];
}
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_.
id]=$intersects;
return $intersects;
};
// this method is not complete - if you require to remove markers from Clus
terMarker then i shall finish this method for you
// custom marker properties ought to be deleted and cluster marker event li
steners removed
ClusterMarker.prototype.removeMarkers=function($markers){
if(!$markers){
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=[];
}
};
// this method will zoom and pan the map to show all markers in the $marker
s array that is passed to it
// if no array of markers is passed to this method then the map will zoom a
nd pan to fit all markers added to ClusterMarker
// if a value is passed for $maxZoom then the map will not be zoomed in any
more than this zoom level
ClusterMarker.prototype.fitMapToMarkers=function($markers, $maxZoom){
var $this=this, $bounds=new GLatLngBounds(), $refresh=false;
if(typeof($markers)==='undefined' || $markers===null){
$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);
}
};
// this method will return the minimum zoom level at which a marker is not
clustered
// if the marker is currently part of a cluster then start searching for th
e minimum unclustered zoom level from the current map zoom level plus one
// (no need to search for the zoom level at lower zoom levels as we know th
e marker is clustered at this zoom level)
// otherwise search for the minimum unclustered zoom level from map zoom le
vel zero
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_.id);
}
}
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)){
$isClustered=true;
}
}
if(!$isClustered){
break;
}
$zoomLevel++;
}
return $zoomLevel;
};
// set a marker's doNotCluster property to true or false
// if the doNotCluster property is set to true then listen for the GMap2 'i
nfowindowclose' event so we can then set doNotCluster to false
ClusterMarker.prototype.setDoNotCluster=function($marker, $state){
if($state && !$marker._ClusterMarker_.doNotCluster){
var $this=this;
$marker._ClusterMarker_.doNotCluster=true;
$marker._ClusterMarker_.infowindowcloseListener=GEvent.addListen
er($this.map, 'infowindowclose', function(){
$marker._ClusterMarker_.doNotCluster=false;
$this.refresh();
GEvent.removeListener($marker._ClusterMarker_.infowindow
closeListener);
});
} else if (!$state && $marker._ClusterMarker_.doNotCluster){
$marker._ClusterMarker_.doNotCluster=false;
$this.refresh();
GEvent.removeListener($marker._ClusterMarker_.infowindowcloseLis
tener);
}
};

Вам также может понравиться