Fix and accelerate ol.source.Vector#getClosestFeatureToCoordinate

The previous implementation contained a bug (the reduced extent was
calculated around the closest point found, it should have been
calculated around the coordinate being searched for).  This allows a
speed-up that requires only a single traversal of the R-Tree (as opposed
to many traversals).
This commit is contained in:
Tom Payne
2013-12-09 18:45:06 +01:00
parent f4fe0046b2
commit b4c6a36a4f
+23 -38
View File
@@ -174,54 +174,39 @@ ol.source.Vector.prototype.getClosestFeatureToCoordinate =
// Find the closest feature using branch and bound. We start searching an // Find the closest feature using branch and bound. We start searching an
// infinite extent, and find the distance from the first feature found. This // infinite extent, and find the distance from the first feature found. This
// becomes the closest feature. We then compute a smaller extent which any // becomes the closest feature. We then compute a smaller extent which any
// closer feature must overlap. We search again with this smaller extent, // closer feature must intersect. We continue searching with this smaller
// trying to find a closer feature. The loop continues until no closer // extent, trying to find a closer feature. Every time we find a closer
// feature can be found. // feature, we update the extent being searched so that any even closer
// feature must intersect it. We continue until we run out of features.
var x = coordinate[0]; var x = coordinate[0];
var y = coordinate[1]; var y = coordinate[1];
var closestFeature = null; var closestFeature = null;
var closestPoint = [NaN, NaN]; var closestPoint = [NaN, NaN];
var minSquaredDistance = Infinity; var minSquaredDistance = Infinity;
var extent = [-Infinity, -Infinity, Infinity, Infinity]; var extent = [-Infinity, -Infinity, Infinity, Infinity];
/** @type {Object.<string, boolean>} */ this.rBush_.forEachInExtent(extent,
var visitedFeatures = {};
var callback =
/** /**
* @param {ol.Feature} feature Feature. * @param {ol.Feature} feature Feature.
* @return {ol.Feature|undefined} Closer feature.
*/ */
function(feature) { function(feature) {
// Make sure that we only test against each feature once. This both avoids var geometry = feature.getGeometry();
// unnecessary calculation and prevents an infinite loop which would occur goog.asserts.assert(!goog.isNull(geometry));
// if the coordinate is exactly half way between two features. var previousMinSquaredDistance = minSquaredDistance;
var featureKey = goog.getUid(feature).toString(); minSquaredDistance = geometry.closestPointXY(
if (visitedFeatures.hasOwnProperty(featureKey)) { x, y, closestPoint, minSquaredDistance);
return undefined; if (minSquaredDistance < previousMinSquaredDistance) {
} else { closestFeature = feature;
visitedFeatures[featureKey] = true; // This is sneaky. Reduce the extent that it is currently being
} // searched while the R-Tree traversal using this same extent object
var geometry = feature.getGeometry(); // is still in progress. This is safe because the new extent is
if (goog.isNull(geometry)) { // strictly contained by the old extent.
return undefined; var minDistance = Math.sqrt(minSquaredDistance);
} extent[0] = x - minDistance;
var previousMinSquaredDistance = minSquaredDistance; extent[1] = y - minDistance;
minSquaredDistance = geometry.closestPointXY( extent[2] = x + minDistance;
x, y, closestPoint, minSquaredDistance); extent[3] = y + minDistance;
goog.asserts.assert(minSquaredDistance <= previousMinSquaredDistance); }
if (minSquaredDistance < previousMinSquaredDistance) { });
closestFeature = feature;
var minDistance = Math.sqrt(minSquaredDistance);
extent[0] = closestPoint[0] - minDistance;
extent[1] = closestPoint[1] - minDistance;
extent[2] = closestPoint[0] + minDistance;
extent[3] = closestPoint[1] + minDistance;
return closestFeature;
} else {
return undefined;
}
};
while (goog.isDef(this.rBush_.forEachInExtent(extent, callback))) {
}
return closestFeature; return closestFeature;
}; };