[Pkg-javascript-commits] [leaflet-markercluster] 151/479: improve performance 4-20 times with grid-based near neighbor search

Jonas Smedegaard dr at jones.dk
Thu Oct 16 16:00:23 UTC 2014


This is an automated email from the git hooks/post-receive script.

js pushed a commit to branch master
in repository leaflet-markercluster.

commit 58aaf379244fc1eec3721c27438145394f7f8fa5
Author: Vladimir Agafonkin <agafonkin at gmail.com>
Date:   Thu Aug 9 16:24:51 2012 +0300

    improve performance 4-20 times with grid-based near neighbor search
---
 build/deps.js             |   5 +-
 src/DistanceGrid.js       | 112 +++++++++++++++++++++++++++++++++++++
 src/MarkerClusterGroup.js | 137 +++++++++++++++++++++-------------------------
 3 files changed, 177 insertions(+), 77 deletions(-)

diff --git a/build/deps.js b/build/deps.js
index 00e290d..6a466ea 100644
--- a/build/deps.js
+++ b/build/deps.js
@@ -1,8 +1,9 @@
 var deps = {
-	
+
 	Core: {
 		src: ['MarkerClusterGroup.js',
-		      'MarkerCluster.js'],
+		      'MarkerCluster.js',
+		      'DistanceGrid.js'],
 		desc: 'The core of the library.'
 	},
 
diff --git a/src/DistanceGrid.js b/src/DistanceGrid.js
new file mode 100644
index 0000000..d9b1f28
--- /dev/null
+++ b/src/DistanceGrid.js
@@ -0,0 +1,112 @@
+
+L.DistanceGrid = function (cellSize) {
+	this._cellSize = cellSize;
+	this._sqCellSize = cellSize * cellSize;
+	this._grid = {};
+};
+
+L.DistanceGrid.prototype = {
+
+	addObject: function (obj, point) {
+		var x = Math.floor(point.x / this._cellSize),
+		    y = Math.floor(point.y / this._cellSize),
+		    row = this._grid[y] = this._grid[y] || {},
+			cell = row[x] = row[x] || [];
+
+		obj._dGridCell = cell;
+		obj._dGridPoint = point;
+		cell.push(obj);
+	},
+
+	updateObject: function (obj, point) {
+		this.removeObject(obj);
+		this.addObject(obj, point);
+	},
+
+	removeObject: function (obj) {
+		var oldCell = obj._dGridCell,
+			point = obj._dGridPoint,
+			x, y, i, len;
+
+		for (i = 0, len = oldCell.length; i < len; i++) {
+			if (oldCell[i] === obj) {
+				oldCell.splice(i, 1);
+
+				if (len === 1) {
+					x = Math.floor(point.x / this._cellSize),
+					y = Math.floor(point.y / this._cellSize),
+					delete this._grid[y][x];
+				}
+				break;
+			}
+		}
+	},
+
+	replaceObject: function (newObj, oldObj) {
+		var cell = oldObj._dGridCell,
+			i, len;
+
+		for (i = 0, len = cell.length; i < len; i++) {
+			if (cell[i] === oldObj) {
+				cell.splice(i, 1, newObj);
+				newObj._dGridCell = oldObj._dGridCell;
+				newObj._dGridPoint = oldObj._dGridPoint;
+				break;
+			}
+		}
+	},
+
+	eachObject: function (fn, context) {
+		var i, j, k, len, row, cell, removed,
+			grid = this._grid;
+
+		for (i in grid) {
+			if (grid.hasOwnProperty(i)) {
+				row = grid[i];
+				for (j in row) {
+					if (row.hasOwnProperty(j)) {
+						cell = row[j];
+						for (k = 0, len = cell.length; k < len; k++) {
+							removed = fn.call(context, cell[k]);
+							if (removed) {
+								k--;
+								len--;
+							}
+						}
+					}
+				}
+			}
+		}
+	},
+
+	getNearObject: function (point) {
+		var x = Math.floor(point.x / this._cellSize),
+		    y = Math.floor(point.y / this._cellSize),
+		    i, j, k, row, cell, len, obj;
+
+		for (i = y - 1; i <= y + 1; i++) {
+			row = this._grid[i];
+			if (row) {
+				for (j = x - 1; j <= x + 1; j++) {
+					cell = row[j];
+					if (cell) {
+						for (k = 0, len = cell.length; k < len; k++) {
+							obj = cell[k];
+							if (this._sqDist(obj._dGridPoint, point) < this._sqCellSize) {
+								return obj;
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return null;
+	},
+
+	_sqDist: function (p, p2) {
+		var dx = p2.x - p.x,
+			dy = p2.y - p.y;
+		return dx * dx + dy * dy;
+	}
+};
diff --git a/src/MarkerClusterGroup.js b/src/MarkerClusterGroup.js
index ea4c245..8fb0882 100644
--- a/src/MarkerClusterGroup.js
+++ b/src/MarkerClusterGroup.js
@@ -221,6 +221,7 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
 			maxZoom = this._map.getMaxZoom(),
 			currentZoom = this._map.getZoom();
 
+		//console.time('cluster');
 		this._topClusterLevel = this._clusterToMarkerCluster(this._needsClustering, maxZoom);
 		this._needsClustering = [];
 
@@ -228,6 +229,7 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
 		while (minZoom < this._topClusterLevel._zoom) {
 			this._topClusterLevel = this._clusterToMarkerCluster(this._topClusterLevel._childClusters.concat(this._topClusterLevel._markers), this._topClusterLevel._zoom - 1);
 		}
+		//console.timeEnd('cluster');
 
 		//Remember the current zoom level and bounds
 		this._zoom = currentZoom;
@@ -258,24 +260,13 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
 
 	//Takes a list of markers and clusters the new marker in to them
 	//Will return null or the new MarkerCluster. The clustered in marker is removed from the given array
-	_clusterOne: function (unclusteredMarkers, newMarker, zoom) {
-		var markerPos = newMarker._projCenter || this._map.project(newMarker.getLatLng(), zoom),
-			clusterDiameterSqrd = 2 * this.options.maxClusterRadius * 2 * this.options.maxClusterRadius,
-			i, m, mPos;
-
-		for (i = unclusteredMarkers.length - 1; i >= 0; i--) {
-			m = unclusteredMarkers[i];
-			mPos = m._projCenter || this._map.project(m.getLatLng(), zoom);
-
-			if (this._sqDist(markerPos, mPos) <= clusterDiameterSqrd) {
-				//Create a new cluster with these 2
-				var newCluster = new L.MarkerCluster(this, m, newMarker);
-				delete m._projCenter;
-				delete newMarker._projCenter;
-
-				unclusteredMarkers.splice(i, 1);
-				return newCluster;
-			}
+	_clusterOne: function (unclustered, newMarker, markerPoint) {
+		var marker = unclustered.getNearObject(markerPoint);
+
+		if (marker) {
+			// create a new cluster with these 2
+			unclustered.removeObject(marker);
+			return new L.MarkerCluster(this, marker, newMarker);
 		}
 
 		return null;
@@ -283,73 +274,69 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
 
 	//Takes a list of objects that have a 'getLatLng()' function (Marker / MarkerCluster)
 	//Performs clustering on them (using a greedy algorithm) and returns those clusters.
-	//toCluster: List of Markers/MarkerClusters to cluster
+	//markers: List of Markers/MarkerClusters to cluster
 	//Returns { 'clusters': [new clusters], 'unclustered': [unclustered markers] }
-	_cluster: function (toCluster, zoom) {
-		var clusterRadiusSqrd = this.options.maxClusterRadius * this.options.maxClusterRadius,
-		    clusters = [],
-		    unclustered = [],
-		    i, j, c;
-
-		//go through each point
-		for (i = toCluster.length - 1; i >= 0; i--) {
-			var point = toCluster[i],
-				used = false;
-
-			point._projCenter = this._map.project(point.getLatLng(), zoom); //Calculate pixel position
-
-			//try add it to an existing cluster
-			for (j = clusters.length - 1; j >= 0; j--) {
-				c = clusters[j];
-				if (this._sqDist(point._projCenter, c._projCenter) <= clusterRadiusSqrd) {
-					c._addChild(point);
-					delete point._projCenter;
-					c._projCenter = this._map.project(c.getLatLng(), zoom);
-
-					used = true;
-					break;
-				}
-			}
-
-			//otherwise, look through all of the markers we haven't managed to cluster and see if we should form a cluster with them
-			if (!used) {
-				var newCluster = this._clusterOne(unclustered, point);
+	_cluster: function (markers, zoom) {
+		var radius = this.options.maxClusterRadius,
+		    clusters = new L.DistanceGrid(radius),
+		    unclustered = new L.DistanceGrid(radius),
+		    i, j, marker, markerPoint, cluster, newCluster;
+
+		// go through each point
+		for (i = markers.length - 1; i >= 0; i--) {
+			marker = markers[i];
+			markerPoint = this._map.project(marker.getLatLng(), zoom); // calculate pixel position
+
+			// try add it to an existing cluster
+			cluster = clusters.getNearObject(markerPoint);
+
+			if (cluster) {
+				cluster._addChild(marker);
+				clusters.updateObject(cluster, this._map.project(cluster.getLatLng(), zoom));
+			} else {
+				// otherwise, look through all of the markers we haven't managed to cluster and see if we should form a cluster with them
+				newCluster = this._clusterOne(unclustered, marker, markerPoint);
 				if (newCluster) {
-					newCluster._projCenter = this._map.project(newCluster.getLatLng(), zoom);
-					clusters.push(newCluster);
+					clusters.addObject(newCluster, this._map.project(newCluster.getLatLng(), zoom));
 				} else {
-					//Didn't manage to use it
-					unclustered.push(point);
+					// didn't manage to use it
+					unclustered.addObject(marker, markerPoint);
 				}
 			}
 		}
 
-		//Any clusters that did not end up being a child of a new cluster, make them a child of a new cluster
-		for (i = unclustered.length - 1; i >= 0; i--) {
-			c = unclustered[i];
-			delete c._projCenter;
+		var result = [],
+			group = this;
+
+		// any clusters that did not end up being a child of a new cluster, make them a child of a new cluster
+		unclustered.eachObject(function (cluster) {
+			if (cluster instanceof L.MarkerCluster) {
+				newCluster = new L.MarkerCluster(group, cluster);
+				newCluster._haveGeneratedChildClusters = true;
+
+				clusters.addObject(newCluster, cluster._dGridPoint);
+				unclustered.removeObject(cluster);
 
-			if (c instanceof L.MarkerCluster) {
-				var nc = new L.MarkerCluster(this, c);
-				nc._haveGeneratedChildClusters = true;
-				clusters.push(nc);
-				unclustered.splice(i, 1);
+				return true;
 			}
-		}
+		});
 
-		//Remove the _projCenter temp variable from clusters
-		for (i = clusters.length - 1; i >= 0; i--) {
-			delete clusters[i]._projCenter;
-			clusters[i]._baseInit();
-		}
+		unclustered.eachObject(function (marker) {
+			result.push(marker);
+		});
 
-		return { 'clusters': clusters, 'unclustered': unclustered };
+		// initialize created clusters
+		clusters.eachObject(function (cluster) {
+			cluster._baseInit();
+			result.push(cluster);
+		});
+
+		return result;
 	},
-	
+
 	//Clusters the given markers (with _cluster) and returns the result as a MarkerCluster
-	_clusterToMarkerCluster: function (toCluster, zoom) {
-		var res = this._cluster(toCluster, zoom),
-			toAdd = res.clusters.concat(res.unclustered),
+	_clusterToMarkerCluster: function (markers, zoom) {
+		var toAdd = this._cluster(markers, zoom),
 			result = new L.MarkerCluster(this),
 			i;
 
@@ -369,7 +356,7 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
 			height = L.Browser.mobile ? 0 : Math.abs(bounds.max.y - bounds.min.y),
 			sw = map.unproject(new L.Point(bounds.min.x - width, bounds.min.y - height)),
 			ne = map.unproject(new L.Point(bounds.max.x + width, bounds.max.y + height));
-		
+
 		return new L.LatLngBounds(sw, ne);
 	}
 });
@@ -462,7 +449,7 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
 		});
 
 		//Remove the old clusters and close the zoom animation
-		
+
 		setTimeout(function () {
 			//update the positions of the just added clusters/markers
 			me._topClusterLevel._recursively(bounds, depthToStartAt, 0, function (c) {
@@ -541,4 +528,4 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
 
 		L.Util.falseFn(document.body.offsetWidth);
 	}
-});
\ No newline at end of file
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/leaflet-markercluster.git



More information about the Pkg-javascript-commits mailing list