我目前的代码很快,但我需要更快,所以我们可以容纳更多的标记。有什么建议吗?
注意:
代码:
$singleMarkers = array();
$clusterMarkers = array();
while (count($markers)) {
$marker = array_pop($markers);
$cluster = array();
// Compare marker against all remaining markers.
foreach ($markers as $key => $compareMarker) {
// This function returns the distance between two markers, at a defined zoom level.
$pixels = pixelDistance($marker['lat'], $marker['lng'], $compareMarker['lat'], $compareMarker['lng'], $zoomLevel);
// If two markers are closer than defined distance, remove compareMarker from array and add to cluster.
if ($pixels < $distance) {
unset($markers[$key]);
$cluster[] = $compareMarker;
}
}
// If a marker was added to cluster, also add the marker we were comparing to.
if (count($cluster) > 0) {
$cluster[] = $marker;
$clusterMarkers[] = $cluster;
} else {
$singleMarkers[] = $marker;
}
}
function pixelDistance($lat1, $lon1, $lat2, $lon2, $zoom) {
$x1 = $lon1*10000000; //This is what I did to compensate for using lat/lon values instead of pixels.
$y1 = $lat1*10000000;
$x2 = $lon2*10000000;
$y2 = $lat2*10000000;
return sqrt(pow(($x1-$x2),2) + pow(($y1-$y2),2)) >> (21 - $zoom); //21 is the max zoom level
}
更新
这是当前的代码:
$singleMarkers = array();
$clusterMarkers = array();
// Minimum distance between markers to be included in a cluster, at diff. zoom levels
$DISTANCE = (10000000 >> $ZOOM) / 100000;
// Loop until all markers have been compared.
while (count($markers)) {
$marker = array_pop($markers);
$cluster = array();
// Compare against all markers which are left.
foreach ($markers as $key => $target) {
$pixels = abs($marker['lat']-$target['lat']) + abs($marker['lng']-$target['lng']);
// If the two markers are closer than given distance remove target marker from array and add it to cluster.
if ($pixels < $DISTANCE) {
unset($markers[$key]);
$cluster[] = $target;
}
}
// If a marker has been added to cluster, add also the one we were comparing to.
if (count($cluster) > 0) {
$cluster[] = $marker;
$clusterMarkers[] = $cluster;
} else {
$singleMarkers[] = $marker;
}
}
答案 0 :(得分:7)
你真的需要计算Euclidean distance吗?如果您只是比较相对距离的大小,您可以使用Manhattan distance,这可以为您节省两次pow()
和一次sqrt()
的调用:
function pixelDistance($lat1, $lon1, $lat2, $lon2, $zoom) {
$x1 = $lon1*10000000; //This is what I did to compensate for using lat/lon values instead of pixels.
$y1 = $lat1*10000000;
$x2 = $lon2*10000000;
$y2 = $lat2*10000000;
return ($x1-$x2) + ($y1-$y2) >> (21 - $zoom);
}
不确定你的计算是否需要>> (21 - $zoom)
位,所以我把它留在了。但除非你真的需要在其他地方使用计算的距离值,否则你可能只需要使用纬度/经度就可以了直接(不需要乘以任何东西)并取曼哈顿距离,假设您预先计算$distance
以适应该度量,这将在计算方面比强迫所有距离适应更便宜$distance
的单位和幅度。
我还强烈推荐 Programming Collective Intelligence: Building Smart Web 2.0 Applications 这本书,它非常深入地进行聚类,不仅适用于地理数据,也适用于其他数据分析领域。
答案 1 :(得分:4)
扩展约翰所说的,我认为你应该尝试内联这个功能。 PHP中的函数调用非常慢,所以你应该从中得到一个不错的加速。
答案 2 :(得分:2)
所以这就是我所做的 - 我在标记(点)表中添加了两个额外的列,使用以下函数为经纬度转换了纬度和经度值:
public static $offset = 268435456;
public static $radius = 85445659.44705395; /* $offset / pi(); */
function LonToX($lon)
{
return round(self::$offset + self::$radius * $lon * pi() / 180);
}
function LatToY($lat)
{
return round(self::$offset - self::$radius * log((1 + sin($lat * pi() / 180)) / (1 - sin($lat * pi() / 180))) / 2);
}
通过这种方式,我可以准确地放置群集。我还在努力弄清楚如何避免每次都使用array_pop和循环。到目前为止,使用低于1000的标记非常有效。我稍后会发布+ 5K和+ 10K标记的结果。
避免使用pixelDistance功能并使其内联可将性能提高近一半!
答案 3 :(得分:1)
似乎加速pixelDistance()函数可能是您解决方案的一部分,因为它在循环内运行。这是一个首先看的好地方,但你没有包含那些代码,所以我不能确定。
答案 4 :(得分:1)
我可以在这里看到两个可能的改进:
您可以循环浏览$ markers 使用for循环而不是弹出 他们离阵?数组弹出是完全不需要的 - 如果您同时向它们添加和删除项目(您不是;您只是处理它们然后扔掉它们),您应该只使用数组作为队列/ p>
你应该尝试计算 数组的count() 开始,然后手动增加 或减少$ count变量。 重新计算数组的大小 每个循环都是浪费。
答案 5 :(得分:1)
以下是您可以实施的一些想法,以防性能出现问题:
这两种技术都应用于离线设置,因此通常会计算一次,然后多次使用..
答案 6 :(得分:1)
一个简单的优化就是利用sqrt(x)&lt;如果x <&lt; sqf(y),则为真。 y,所以你可以省略pixelDistance中的sqrt并计算循环外的$ distance平方。您还可以在循环外计算21 - $ zoomLevel,如果您要比较平方值,则必须将它乘以2。另一个小优化是在缩放10000000之前通过执行$ x1- $ x2来节省2倍。而对于更多一点,将delta存储在var中并将其自身相乘可能比pow函数更快。而对于更多内容,您可以内联像素距离函数。这种优化只会产生一个恒定的因子加速。
对于更大的加速,您需要某种加速度数据结构。一个简单的方法是将标记分成距离大小的正方形。然后你可以在垃圾箱上方查找标记,只在同一个垃圾箱中聚类,另外选择3个其他垃圾箱,这取决于标记落在垃圾箱中心的哪一侧。这将导致线性时间聚类,这将超过对更大结果集的二次算法的任何优化。
答案 7 :(得分:1)
如果可以,请在初始搜索时按经度对标记进行排序;然后,一旦标记超过排序列表中下一个标记的标记宽度,您肯定知道其余标记不会重叠,因此您可以打破foreach循环并节省大量处理时间。我已经在我自己的网站上实现了它,它的工作效率很高。
我在Python here中有一些源代码。