diff --git a/deploy/ClusterExample.swf b/deploy/ClusterExample.swf index d0765c1..ba14315 100644 Binary files a/deploy/ClusterExample.swf and b/deploy/ClusterExample.swf differ diff --git a/src/com/kelvinluck/gmaps/Clusterer.as b/src/com/kelvinluck/gmaps/Clusterer.as index d8ca224..964fb63 100644 --- a/src/com/kelvinluck/gmaps/Clusterer.as +++ b/src/com/kelvinluck/gmaps/Clusterer.as @@ -1,23 +1,20 @@ -package com.kelvinluck.gmaps -{ - import com.google.maps.overlays.Marker; - +package com.kelvinluck.gmaps { import flash.geom.Point; - import flash.utils.Dictionary; /** * Distance based clustering solution for google maps markers. - * - *

Algorithm based on Mika Tuupola's "Introduction to Marker + * + *

Algorithm based on Mika Tuupola's "Introduction to Marker * Clustering With Google Maps" adapted for use in a dynamic * flash map.

- * + * * @author Kelvin Luck + * @author Christoph Polcin - speedup calculateClusters * @see http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps */ - public class Clusterer + public class Clusterer { - + public static const DEFAULT_CLUSTER_RADIUS:int = 25; private var _clusters:Array; @@ -30,17 +27,16 @@ package com.kelvinluck.gmaps return _clusters; } - private var _markers:Array; public function set markers(value:Array):void { - if (value != _markers) { - _markers = value; - _positionedMarkers = []; - for each (var marker:Marker in value) { - _positionedMarkers.push(new PositionedMarker(marker)); - } - _invalidated = true; - } + var i:int = -1, + l:int = value.length; + + _positionedMarkers = new Vector.(l); + while(++i; public function Clusterer(markers:Array, zoom:int, clusterRadius:int = DEFAULT_CLUSTER_RADIUS) { @@ -74,90 +70,69 @@ package com.kelvinluck.gmaps private function calculateClusters():Array { - var positionedMarkers:Dictionary = new Dictionary(); - var positionedMarker:PositionedMarker; - for each (positionedMarker in _positionedMarkers) { - positionedMarkers[positionedMarker.id] = positionedMarker; - } - - // Rather than taking a sqaure root and dividing by a power of 2 to calculate every distance we - // do the calculation once here (backwards). - var compareDistance:Number = Math.pow(_clusterRadius * Math.pow(2, 21 - _zoom), 2); - - var clusters:Array = []; - var cluster:Array; - var p1:Point; - var p2:Point; - var x:int; - var y:int; - var compareMarker:PositionedMarker; - for each (positionedMarker in positionedMarkers) { - if (positionedMarker == null) { - continue; - } - positionedMarkers[positionedMarker.id] = null; - cluster = [positionedMarker.marker]; - for each (compareMarker in positionedMarkers) { - if (compareMarker == null) { + var clusterMArAr : Array = [], + om : PositionedMarker, + cm : PositionedMarker, + // Rather than taking a sqaure root and dividing by a power of 2 to calculate every distance we + // do the calculation once here (backwards). + compareDistance : Number = Math.pow(_clusterRadius * Math.pow(2, 21 - _zoom), 2), + clusterMAr : Array, + p1 : Point, + p2 : Point, + x : int, + y : int, + i:int = -1, + j:int, + l:int = _positionedMarkers.length, + doneV : Vector. = new Vector.(l); + + while(++i < l) { + if(doneV[i]) continue; + + om = _positionedMarkers[i]; + + doneV[i] = 1; + clusterMAr = [om.marker]; + j = i; + while(++j < l) { + if(doneV[j]) + continue; + + cm = _positionedMarkers[j]; + + p1 = om.point; + p2 = cm.point; + x = p1.x - p2.x; + y = p1.y - p2.y; + if (x * x + y * y < compareDistance) { + clusterMAr.push(cm.marker); + doneV[j] = 1; + } } - p1 = positionedMarker.point; - p2 = compareMarker.point; - x = p1.x - p2.x; - y = p1.y - p2.y; - if (x * x + y * y < compareDistance) { - cluster.push(compareMarker.marker); - positionedMarkers[compareMarker.id] = null; - } + clusterMArAr.push(clusterMAr); } - clusters.push(cluster); - } - return clusters; + + return clusterMArAr; } } } -import com.google.maps.LatLng; import com.google.maps.overlays.Marker; import flash.geom.Point; -internal class PositionedMarker +final internal class PositionedMarker { - - public static const OFFSET:int = 268435456; - public static const RADIUS:Number = OFFSET / Math.PI; - - // public properties are quicker than getters - speed is important here... - public var position:LatLng; public var point:Point; - - private var _marker:Marker; - public function get marker():Marker - { - return _marker; - } - - private var _id:int; - public function get id():int - { - return _id; - } - - private static var globalId:int = 0; + public var marker:Marker; public function PositionedMarker(marker:Marker) { - _marker = marker; - _id = globalId++; - position = marker.getLatLng(); - - var o:int = OFFSET; - var r:Number = RADIUS; - var d:Number = Math.PI / 180; - var x:int = Math.round(o + r * position.lng() * d); - var lat:Number = position.lat(); - var y:int = Math.round(o - r * Math.log((1 + Math.sin(lat * d)) / (1 - Math.sin(lat * d))) / 2); - point = new Point(x, y); + this.marker = marker; + + with(marker.getLatLng()){ + point = new Point(Math.round(268435456 /*off*/ + 85445659.44705395 /* rad = off / pi */ * lng() * 0.017453292519943295) /* long to X */, Math.round(268435456 /*off*/ - 85445659.44705395 /* rad = off / pi */ * Math.log((1 + Math.sin(lat() * 0.017453292519943295)) / (1 - Math.sin(lat() * 0.017453292519943295))) / 2)/* lat to Y */); + } } }