PageRenderTime 29ms CodeModel.GetById 13ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/src/com/google/maps/extras/markerclusterer/MarkerClusterer.as

http://gmaps-utility-library-flash.googlecode.com/
ActionScript | 395 lines | 213 code | 51 blank | 131 comment | 40 complexity | 5a9c1b94afab84cdb0db5cf57a7883dc MD5 | raw file
  1/**
  2 * @name MarkerClusterer for Flash
  3 * @version 1.0
  4 * @author Xiaoxi Wu
  5 * @copyright (c) 2009 Xiaoxi Wu
  6 * @fileoverview
  7 * Ported from Javascript to Actionscript 3 by Sean Toru
  8 * Ported for use in Flex (removal of fl. libraries) by Ian Watkins
  9 * Reflectored for both Flash and Flex, 
 10 * and maintained by Juguang XIAO (juguang@gmail.com)
 11 * 
 12 * This actionscript library creates and manages per-zoom-level 
 13 * clusters for large amounts of markers (hundreds or thousands).
 14 * This library was inspired by the <a href="http://www.maptimize.com">
 15 * Maptimize</a> hosted clustering solution.
 16 * <br /><br/>
 17 * <b>How it works</b>:<br/>
 18 * The <code>MarkerClusterer</code> will group markers into clusters according to
 19 * their distance from a cluster's center. When a marker is added,
 20 * the marker cluster will find a position in all the clusters, and 
 21 * if it fails to find one, it will create a new cluster with the marker.
 22 * The number of markers in a cluster will be displayed
 23 * on the cluster marker. When the map viewport changes,
 24 * <code>MarkerClusterer</code> will destroy the clusters in the viewport 
 25 * and regroup them into new clusters.
 26 *
 27 */
 28
 29
 30/*
 31 * Licensed under the Apache License, Version 2.0 (the "License");
 32 * you may not use this file except in compliance with the License.
 33 * You may obtain a copy of the License at
 34 *
 35 *     http://www.apache.org/licenses/LICENSE-2.0
 36 *
 37 * Unless required by applicable law or agreed to in writing, software
 38 * distributed under the License is distributed on an "AS IS" BASIS,
 39 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 40 * See the License for the specific language governing permissions and
 41 * limitations under the License.
 42 */
 43
 44package com.google.maps.extras.markerclusterer
 45{
 46import com.google.maps.LatLng;
 47import com.google.maps.LatLngBounds;
 48import com.google.maps.interfaces.IMap;
 49import com.google.maps.interfaces.IPane;
 50
 51import flash.events.Event;
 52import flash.geom.Point;
 53import flash.geom.Rectangle;
 54
 55/**
 56 * This class is the entry to MarkerClusterer package, and the only class you as a developer should touch.
 57 * 
 58 * 
 59 * 
 60 */ 
 61public class MarkerClusterer
 62{
 63	private var _markers		: Array;
 64	private  var clusters_ 		: Array;
 65	private  var map_ 			: IMap;
 66
 67	private  var leftMarkers_ 	: Array;
 68	private var _pane:IPane;
 69	public var options:MarkerClustererOptions;
 70	/**
 71	 * 
 72	 * @param pane.
 73	 * 
 74	 * @param markers An array of UnitMarker. 
 75	 * 
 76	 * @param options of MarkerClustererOptions
 77	 * 
 78	 */ 
 79	public function MarkerClusterer (pane: IPane, markers : Array, opts : MarkerClustererOptions = null)
 80	{
 81		_markers		= new Array();
 82		clusters_ 		= new Array();
 83		map_ 			= pane.map;
 84		leftMarkers_ 	= new Array();
 85		this._pane = pane; ///  map_.getPaneManager().createPane();
 86		
 87		if(opts == null)		opts = new MarkerClustererOptions;
 88		this.options = opts;
 89
 90		
 91		if(options.styles.length == 0){
 92			var styles:Array		= new Array();
 93			for (var i:int = 1; i <= 5; ++i)
 94			{
 95				styles.push({'url': "assets/images/m" + i + ".png"});
 96			}
 97			options.styles = styles;
 98		}
 99
100		if (markers != null){
101			addMarkers(markers);
102		}
103		
104		map_.addEventListener("mapevent_moveend", mapMoved);
105	}
106	
107	private function mapMoved (event : Event) :void
108	{
109		resetViewport();
110	}
111	
112	private function addLeftMarkers () : void
113	{
114		var leftMarkers : Array = [];
115		
116		if (leftMarkers_.length < 1) {
117			return;
118		}
119		
120		for (var i:int = 0; i < leftMarkers_.length; ++i) {
121			addMarker(leftMarkers_[i], true, false, null, true);
122		}
123		
124		leftMarkers_ = leftMarkers;
125	}
126	
127	public function getStyles () : Array
128	{
129		return this.options.styles; // (styles_);
130	}
131	
132	public function clearMarkers () : void 
133	{
134		for each(var cluster:Cluster in listCluster()){
135			cluster.clearMarkers();
136		}	
137	/* 
138		for (var i:int = 0; i < clusters_.length; ++i)	{
139			if (clusters_[i] != undefined && clusters_[i] != null)
140			{
141				clusters_[i].clearMarkers();
142			}
143		}
144		 */
145		clusters_ 		= new Array();;
146		leftMarkers_ 	= new Array();
147	}
148	
149	private function isMarkerInViewport_(marker : UnitMarker) : Boolean 
150	{
151		return map_.getLatLngBounds().containsLatLng(marker.getLatLng());
152	}
153	
154	private function reAddMarkers_(markers : Array) : void
155	{
156	//	var len:int 		= markers.length;
157		var clusters :Array	= new Array();
158		var i:int = 0;
159	//	for (var i:int = len - 1; i >= 0; --i) {
160		for each(var marker:UnitMarker in markers){
161			addMarker(marker, true, marker.isAdded, clusters, true);
162			trace('...' + i);
163			i++;
164		}
165		
166		addLeftMarkers();
167	}
168	
169	private function addMarker (marker : UnitMarker, 
170		opt_isNodraw : Boolean,
171		isAdded : Boolean, 
172		clusters : Array, 
173		opt_isNoCheck : Boolean) : void
174	{
175		this._markers.push(marker);
176		
177		if (opt_isNoCheck != true){
178			if (!isMarkerInViewport_(marker)) {
179				leftMarkers_.push(marker);
180				return;
181			}
182		}
183		var pos	: Point= map_.fromLatLngToViewport(marker.getLatLng());
184		
185		if (clusters == null){
186			clusters = clusters_;
187		}
188		
189//		length 		= clusters.length;
190//		cluster 	= null;
191		var centrePoint	: Point;
192		var center		: LatLng;
193		for each(var cluster:Cluster in clusters){
194//		for (var i:int = length - 1; i >= 0; i--) {
195//			cluster 	= clusters[i];
196			center 		= cluster.getCenter();
197			
198			if (center == null)	
199				continue;
200		
201			centrePoint = map_.fromLatLngToViewport(center);
202			
203			// Found a cluster which contains the marker.
204			if (pos.x >= centrePoint.x - options.gridSize 
205			&& pos.x <= centrePoint.x + options.gridSize 
206			&& pos.y >= centrePoint.y - options.gridSize 
207			&& pos.y <= centrePoint.y + options.gridSize){
208				marker.isAdded = isAdded;
209				cluster.addMarker(marker);
210				
211				if (!opt_isNodraw)
212				{
213					cluster.redraw(false);
214				}
215				
216				return;
217			}
218		}
219		
220		// No cluster contain the marker, create a new cluster.
221		var newCluster:Cluster 		= new Cluster(this, _pane);
222		marker.isAdded 	= isAdded;
223		newCluster.addMarker(marker);
224		
225		if (!opt_isNodraw)
226		{
227			cluster.redraw(false);
228		}
229		
230		// Add this cluster both in clusters provided and clusters_
231		clusters.push(newCluster);
232		
233		if (clusters != clusters_)
234		{
235			clusters_.push(newCluster);
236		}
237	}
238	
239/* 	private function removeMarker (marker : UnitMarker)  : void
240	{
241		
242		for (var i:int = 0; i < clusters_.length; ++i)
243		{
244			if (clusters_[i].remove(marker))
245			{
246				clusters_[i].redraw_();
247				return;
248			}
249		}
250	} */
251	
252	private function redraw_ () : void
253	{
254		// @20100212
255		
256//		this._pane.clear();
257		for each(var cluster:Cluster in listClusterInViewport_()){
258			cluster.redraw(true);
259		}
260	}
261	
262	private function listCluster():Array{
263		return this.clusters_;
264	}
265	private function listClusterInViewport_ () : Array
266	{
267		var clusters 	: Array = [];
268
269		var curBounds:LatLngBounds 	= map_.getLatLngBounds();
270		var nw:Point = map_.fromLatLngToViewport(curBounds.getNorthWest());
271		var se:Point = map_.fromLatLngToViewport(curBounds.getSouthEast());
272		var rect:Rectangle = new Rectangle(nw.x, nw.y, se.x - nw.x, se.y - nw.y);				
273	
274		//for (i = 0; i < clusters_.length; i ++)
275		for each(var cluster:Cluster in this.listCluster()){
276		//	if (cluster.isInBounds(curBounds))
277		//	if((clusters_[i] as Cluster).isInRectangle(rect))
278			if(cluster.isInRectangle(rect))
279			{
280				clusters.push(cluster);
281			}
282		}
283		
284		return clusters;
285	
286	}
287	/**
288	 * This getter property is intented for Cluster use
289	 */
290	internal function get maxZoom() : Number
291	{
292		return this.options.maxZoom;
293	}
294	/**
295	 * This getter property is intented for Cluster use
296	 */
297	internal function get zoom():Number{
298		return map_.getZoom();
299	}
300	/**
301	 * This getter property is intented for Cluster use
302	 */
303	internal function get maximumResolution():Number{
304		return map_.getCurrentMapType().getMaximumResolution(); 
305	}
306	// this will be removed!!
307	// if cluster needs any info of map, this class should provide methods for it,
308	// not to provide whole map object. 
309/*  	internal function get map (): IMap
310	{
311		return map_;
312	}  */
313	/**
314	 * This getter property is intented for Cluster use
315	 */
316	internal function get gridSize () : Number
317	{
318		return this.options.gridSize;
319	}
320	/* 
321	private function getTotalMarkers () : int
322	{
323		var result 	: int = 0;
324		for each(var cluster:Cluster in clusters_){
325			result += cluster.getTotalMarkers();
326		}
327		return result;
328	} */
329	/* 
330	private function getTotalClusters () : int
331	{
332		return clusters_.length;
333	}
334	 */
335	public function resetViewport (force:Boolean=false) : void{
336		// this new method will clear all markers, and then rebuild.
337		var tmpMarkers:Array = this._markers.concat();
338		this._pane.clear();
339		
340		this.clearMarkers();
341		clusters_ = new Array;
342		this._markers = new Array;
343		this.reAddMarkers_(tmpMarkers);
344	//	this.reAddMarkers_(this._markers);
345		this.redraw_();
346	}
347	
348	public function resetViewport0 (force:Boolean=false) : void {
349		var clusters 	: Array = listClusterInViewport_();
350		var tmpMarkers 	: Array = [];
351		var removed 	: int = 0;
352
353		for each(var cluster:Cluster in clusters)
354		{
355		//	cluster = clusters[i];
356			var oldZoom		: Number = cluster.getCurrentZoom();
357			if (isNaN(oldZoom))	continue;
358			var curZoom 	: Number = map_.getZoom();
359			
360			if (curZoom != oldZoom || force)
361			{
362				
363				// If the cluster zoom level changed then destroy the cluster
364    			// and collect its markers.
365    			for each(var mk:UnitMarker in cluster.getMarkers()){
366					tmpMarkers.push(mk);
367				}
368				
369				cluster.clearMarkers();
370				removed++;
371				
372				for (var j:int = 0; j < clusters_.length; ++j){
373					if (cluster == clusters_[j]){
374						clusters_.splice(j, 1);
375					}
376				}
377			} // for each previous cluster.
378		}
379		
380		// I am not sure what is the difference 
381		// between tmpMarkers and original markers in constructor.!!
382		reAddMarkers_(tmpMarkers);
383		redraw_();
384	}
385	
386	public function addMarkers (markers : Array) : void
387	{
388		for each(var marker:UnitMarker in markers){
389			this.addMarker(marker, true, false, new Array(), true);
390		}
391	//	redraw_();
392		this.resetViewport();
393	}
394}
395}