/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. * Licensed under the Apache License, Version 2.0 (the "License");
  30. * you may not use this file except in compliance with the License.
  31. * You may obtain a copy of the License at
  32. *
  33. * http://www.apache.org/licenses/LICENSE-2.0
  34. *
  35. * Unless required by applicable law or agreed to in writing, software
  36. * distributed under the License is distributed on an "AS IS" BASIS,
  37. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  38. * See the License for the specific language governing permissions and
  39. * limitations under the License.
  40. */
  41. package com.google.maps.extras.markerclusterer
  42. {
  43. import com.google.maps.LatLng;
  44. import com.google.maps.LatLngBounds;
  45. import com.google.maps.interfaces.IMap;
  46. import com.google.maps.interfaces.IPane;
  47. import flash.events.Event;
  48. import flash.geom.Point;
  49. import flash.geom.Rectangle;
  50. /**
  51. * This class is the entry to MarkerClusterer package, and the only class you as a developer should touch.
  52. *
  53. *
  54. *
  55. */
  56. public class MarkerClusterer
  57. {
  58. private var _markers : Array;
  59. private var clusters_ : Array;
  60. private var map_ : IMap;
  61. private var leftMarkers_ : Array;
  62. private var _pane:IPane;
  63. public var options:MarkerClustererOptions;
  64. /**
  65. *
  66. * @param pane.
  67. *
  68. * @param markers An array of UnitMarker.
  69. *
  70. * @param options of MarkerClustererOptions
  71. *
  72. */
  73. public function MarkerClusterer (pane: IPane, markers : Array, opts : MarkerClustererOptions = null)
  74. {
  75. _markers = new Array();
  76. clusters_ = new Array();
  77. map_ = pane.map;
  78. leftMarkers_ = new Array();
  79. this._pane = pane; /// map_.getPaneManager().createPane();
  80. if(opts == null) opts = new MarkerClustererOptions;
  81. this.options = opts;
  82. if(options.styles.length == 0){
  83. var styles:Array = new Array();
  84. for (var i:int = 1; i <= 5; ++i)
  85. {
  86. styles.push({'url': "assets/images/m" + i + ".png"});
  87. }
  88. options.styles = styles;
  89. }
  90. if (markers != null){
  91. addMarkers(markers);
  92. }
  93. map_.addEventListener("mapevent_moveend", mapMoved);
  94. }
  95. private function mapMoved (event : Event) :void
  96. {
  97. resetViewport();
  98. }
  99. private function addLeftMarkers () : void
  100. {
  101. var leftMarkers : Array = [];
  102. if (leftMarkers_.length < 1) {
  103. return;
  104. }
  105. for (var i:int = 0; i < leftMarkers_.length; ++i) {
  106. addMarker(leftMarkers_[i], true, false, null, true);
  107. }
  108. leftMarkers_ = leftMarkers;
  109. }
  110. public function getStyles () : Array
  111. {
  112. return this.options.styles; // (styles_);
  113. }
  114. public function clearMarkers () : void
  115. {
  116. for each(var cluster:Cluster in listCluster()){
  117. cluster.clearMarkers();
  118. }
  119. /*
  120. for (var i:int = 0; i < clusters_.length; ++i) {
  121. if (clusters_[i] != undefined && clusters_[i] != null)
  122. {
  123. clusters_[i].clearMarkers();
  124. }
  125. }
  126. */
  127. clusters_ = new Array();;
  128. leftMarkers_ = new Array();
  129. }
  130. private function isMarkerInViewport_(marker : UnitMarker) : Boolean
  131. {
  132. return map_.getLatLngBounds().containsLatLng(marker.getLatLng());
  133. }
  134. private function reAddMarkers_(markers : Array) : void
  135. {
  136. // var len:int = markers.length;
  137. var clusters :Array = new Array();
  138. var i:int = 0;
  139. // for (var i:int = len - 1; i >= 0; --i) {
  140. for each(var marker:UnitMarker in markers){
  141. addMarker(marker, true, marker.isAdded, clusters, true);
  142. trace('...' + i);
  143. i++;
  144. }
  145. addLeftMarkers();
  146. }
  147. private function addMarker (marker : UnitMarker,
  148. opt_isNodraw : Boolean,
  149. isAdded : Boolean,
  150. clusters : Array,
  151. opt_isNoCheck : Boolean) : void
  152. {
  153. this._markers.push(marker);
  154. if (opt_isNoCheck != true){
  155. if (!isMarkerInViewport_(marker)) {
  156. leftMarkers_.push(marker);
  157. return;
  158. }
  159. }
  160. var pos : Point= map_.fromLatLngToViewport(marker.getLatLng());
  161. if (clusters == null){
  162. clusters = clusters_;
  163. }
  164. // length = clusters.length;
  165. // cluster = null;
  166. var centrePoint : Point;
  167. var center : LatLng;
  168. for each(var cluster:Cluster in clusters){
  169. // for (var i:int = length - 1; i >= 0; i--) {
  170. // cluster = clusters[i];
  171. center = cluster.getCenter();
  172. if (center == null)
  173. continue;
  174. centrePoint = map_.fromLatLngToViewport(center);
  175. // Found a cluster which contains the marker.
  176. if (pos.x >= centrePoint.x - options.gridSize
  177. && pos.x <= centrePoint.x + options.gridSize
  178. && pos.y >= centrePoint.y - options.gridSize
  179. && pos.y <= centrePoint.y + options.gridSize){
  180. marker.isAdded = isAdded;
  181. cluster.addMarker(marker);
  182. if (!opt_isNodraw)
  183. {
  184. cluster.redraw(false);
  185. }
  186. return;
  187. }
  188. }
  189. // No cluster contain the marker, create a new cluster.
  190. var newCluster:Cluster = new Cluster(this, _pane);
  191. marker.isAdded = isAdded;
  192. newCluster.addMarker(marker);
  193. if (!opt_isNodraw)
  194. {
  195. cluster.redraw(false);
  196. }
  197. // Add this cluster both in clusters provided and clusters_
  198. clusters.push(newCluster);
  199. if (clusters != clusters_)
  200. {
  201. clusters_.push(newCluster);
  202. }
  203. }
  204. /* private function removeMarker (marker : UnitMarker) : void
  205. {
  206. for (var i:int = 0; i < clusters_.length; ++i)
  207. {
  208. if (clusters_[i].remove(marker))
  209. {
  210. clusters_[i].redraw_();
  211. return;
  212. }
  213. }
  214. } */
  215. private function redraw_ () : void
  216. {
  217. // @20100212
  218. // this._pane.clear();
  219. for each(var cluster:Cluster in listClusterInViewport_()){
  220. cluster.redraw(true);
  221. }
  222. }
  223. private function listCluster():Array{
  224. return this.clusters_;
  225. }
  226. private function listClusterInViewport_ () : Array
  227. {
  228. var clusters : Array = [];
  229. var curBounds:LatLngBounds = map_.getLatLngBounds();
  230. var nw:Point = map_.fromLatLngToViewport(curBounds.getNorthWest());
  231. var se:Point = map_.fromLatLngToViewport(curBounds.getSouthEast());
  232. var rect:Rectangle = new Rectangle(nw.x, nw.y, se.x - nw.x, se.y - nw.y);
  233. //for (i = 0; i < clusters_.length; i ++)
  234. for each(var cluster:Cluster in this.listCluster()){
  235. // if (cluster.isInBounds(curBounds))
  236. // if((clusters_[i] as Cluster).isInRectangle(rect))
  237. if(cluster.isInRectangle(rect))
  238. {
  239. clusters.push(cluster);
  240. }
  241. }
  242. return clusters;
  243. }
  244. /**
  245. * This getter property is intented for Cluster use
  246. */
  247. internal function get maxZoom() : Number
  248. {
  249. return this.options.maxZoom;
  250. }
  251. /**
  252. * This getter property is intented for Cluster use
  253. */
  254. internal function get zoom():Number{
  255. return map_.getZoom();
  256. }
  257. /**
  258. * This getter property is intented for Cluster use
  259. */
  260. internal function get maximumResolution():Number{
  261. return map_.getCurrentMapType().getMaximumResolution();
  262. }
  263. // this will be removed!!
  264. // if cluster needs any info of map, this class should provide methods for it,
  265. // not to provide whole map object.
  266. /* internal function get map (): IMap
  267. {
  268. return map_;
  269. } */
  270. /**
  271. * This getter property is intented for Cluster use
  272. */
  273. internal function get gridSize () : Number
  274. {
  275. return this.options.gridSize;
  276. }
  277. /*
  278. private function getTotalMarkers () : int
  279. {
  280. var result : int = 0;
  281. for each(var cluster:Cluster in clusters_){
  282. result += cluster.getTotalMarkers();
  283. }
  284. return result;
  285. } */
  286. /*
  287. private function getTotalClusters () : int
  288. {
  289. return clusters_.length;
  290. }
  291. */
  292. public function resetViewport (force:Boolean=false) : void{
  293. // this new method will clear all markers, and then rebuild.
  294. var tmpMarkers:Array = this._markers.concat();
  295. this._pane.clear();
  296. this.clearMarkers();
  297. clusters_ = new Array;
  298. this._markers = new Array;
  299. this.reAddMarkers_(tmpMarkers);
  300. // this.reAddMarkers_(this._markers);
  301. this.redraw_();
  302. }
  303. public function resetViewport0 (force:Boolean=false) : void {
  304. var clusters : Array = listClusterInViewport_();
  305. var tmpMarkers : Array = [];
  306. var removed : int = 0;
  307. for each(var cluster:Cluster in clusters)
  308. {
  309. // cluster = clusters[i];
  310. var oldZoom : Number = cluster.getCurrentZoom();
  311. if (isNaN(oldZoom)) continue;
  312. var curZoom : Number = map_.getZoom();
  313. if (curZoom != oldZoom || force)
  314. {
  315. // If the cluster zoom level changed then destroy the cluster
  316. // and collect its markers.
  317. for each(var mk:UnitMarker in cluster.getMarkers()){
  318. tmpMarkers.push(mk);
  319. }
  320. cluster.clearMarkers();
  321. removed++;
  322. for (var j:int = 0; j < clusters_.length; ++j){
  323. if (cluster == clusters_[j]){
  324. clusters_.splice(j, 1);
  325. }
  326. }
  327. } // for each previous cluster.
  328. }
  329. // I am not sure what is the difference
  330. // between tmpMarkers and original markers in constructor.!!
  331. reAddMarkers_(tmpMarkers);
  332. redraw_();
  333. }
  334. public function addMarkers (markers : Array) : void
  335. {
  336. for each(var marker:UnitMarker in markers){
  337. this.addMarker(marker, true, false, new Array(), true);
  338. }
  339. // redraw_();
  340. this.resetViewport();
  341. }
  342. }
  343. }