Harita Kümeleme Algoritması

8 Cevap php

Benim şu anki kod oldukça hızlı, ama daha hızlı bu yüzden bile daha fazla göstergelerin ağırlayacak yapmak gerekir. Herhangi bir öneriniz?

Notlar:

  • Kendisi işvardırtçileri kümeleme çok kısmi bir iş yok (aynı konumda belirteçlerin isimleri her zaman aynı değil ama sıklıkla) - SQL deyimi işvardırtleyici adıyla sipariş edildiğinde kod hızlı çalışır.
  • Dinamik olarak aranır ve filtrelenmiş olabilir çünkü ben, belirteçleri önceden küme olamaz.
  • Ben ızgara tabanlı kümeleme denedim - ama sonuç genellikle çok güzel değildir.
  • Ben kümeleri hafifçe Mercator projeksiyonuna çarpık olduğunu biliyoruz.
  • Ben ticari bir kümelenme hizmet ilgilenmiyorum.

Kodu:

$singleMarkers = array();
$clusterMarkers = array();

while (count($markers)) {
    $marker  = array_pop($markers);
    $cluster = array();

    // Compvardır marker against all remaining markers.
    foreach ($markers as $key => $compvardırMarker) {
        // This function returns the distance between two markers, at a defined zoom level.
        $pixels = pixelDistance($marker['lat'], $marker['lng'], $compvardırMarker['lat'], $compvardırMarker['lng'], $zoomLevel);
        // If two markers vardır closer than defined distance, remove compvardırMarker from array ve add to cluster.
        if ($pixels < $distance) {
            unset($markers[$key]);
            $cluster[] = $compvardırMarker;
        }
    }

    // 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
}

UPDATE

Burada geçerli kod:

$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 compvardırd.
while (count($markers)) {
    $marker  = array_pop($markers);
    $cluster = array();

    // Compvardır against all markers which vardır left.
    foreach ($markers as $key => $target) {
        $pixels = abs($marker['lat']-$target['lat']) + abs($marker['lng']-$target['lng']);

        // If the two markers vardır closer than given distance remove target marker from array ve 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;
    }
}

8 Cevap

Aslında Euclidean distance hesaplamak gerekir mi? Sadece mesafeler göreli büyüklüklerini karşılaştırarak iseniz, muhtemelen kullanarak paçayı Manhattan distance, hangi size iki pow() aramaları ve sqrt() için birini kurtaracak :

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);
}

Emin değil misiniz hesaplamalar için >> (21 - $zoom) biraz gerekir, bu yüzden içeri bıraktı Ama aslında başka hesaplanmış mesafe değerleri kullanmak gerekir sürece, muhtemelen sadece doğrudan enlem / boylam kullanarak paçayı eğer (bir şey ile çarpmak gerek yok) ve Manhattan mesafe alarak önceden hesaplamak $distance sığacak tüm mesafeleri coercing daha hesaplama açısından çok daha ucuz olacak, bu önlemin, ile uygun varsayarak birimlerin ve büyüklüğü ile $distance.

EDIT: Ben geçen yıl bu sorunu araştırmaktadır zaman, ben Wikipedia'da bazı yararlı şeyler buldum - Evet, bu olabilir ;-)

Ben de çok coğrafi verilere hem de veri analizi diğer alanlarda değil, sadece uygulanan, büyük derinlemesine kümelenme gider kitap Programming Collective Intelligence: Building Smart Web 2.0 Applications tavsiye ederim.

John ne dedi genişletilmesi, ben bu işlev inlining denemek gerektiğini düşünüyorum. Işlevi çok yavaş PHP çağırır, böylece bundan iyi bir hıza almalısınız.

Bu döngü içinde çalışır beri pixelDistance () fonksiyonu, çözümün parçası olabilir hızlandırmak gibi görünüyor. Bu ilk bakmak için, ama bu kodu yoktu iyi bir yer olduğunu, bu yüzden emin olamıyorum.

Burada iki olası gelişmeler görebilirsiniz:

  • Can you just loop through $markers with a for loop instead of popping them off the array? The array popping is completely unneeded - you should really only be using arrays as queues if you're adding and removing items to them at the same time (which you aren't; you're just processing them then throwing them away)

  • You should try calculating the count() of the arrays at the beginning, and then manually increase or decrease a $count variable. Recalculating the size of an array each loop is wasteful.

Aşağıda büyük sorunu olan vaka performans uygulamaya bazı fikirler:

  • Reduce the dimensionality of the data: you have 2d data of long/lat, perhaps you can try projecting it down to 1D using something like Multidimensional Scaling (MDS) which tries to reduce the number of dimensions while preserving the distances in the original space, thus the distance function will only have to deal with one feature instead of two. An alternative way is to use Principal component analysis (PCA)
  • Faster search: the step of computing the distance to each marker can be improved using KD-trees.

Bu teknik hem de çevrimdışı bir ortamda uygulanır, bu yüzden genellikle bir kez hesaplanır ve daha sonra birçok kez kullandım ..

Basit bir optimizasyon (x) sqrt yararlanmak olacaktır < sqrt (y) doğrudur ancak ve ancak x < y Eğer döngü dışında karesi $ mesafeyi pixelDistance olarak sqrt ihmal ve hesaplayabilirsiniz böylece. Ayrıca 21 hesaplayabilirsiniz - döngü dışında $ ZoomLevel, sen kare değerleri karşılaştırarak eğer 2 ile çarpmak gerekir. Başka bir küçük optimizasyon 10000000 kirliliğe önce $ x1 $ x2 yaparak 2 çarpar kaydetmek olacak. Ve bir nebze daha hızlı pow fonksiyonu dışında muhtemelen bir var içinde delta depolama ve kendisi ile çarpılarak olacaktır. Ve biraz daha sizin için pixeldistance fonksiyonunu satır içi. Optimizasyonu Bu tür sadece bir sabit faktör hıza verecektir.

Daha büyük bir Speedup için size ivme datastructure çeşit gerekir. Kolay bir mesafe büyüklüğünde kareler halinde bin belirteçleri olacaktır. Sonra belirteçler işaretleyici düşer bin merkezinin hangi tarafında bağlı olarak seçilen yalnızca aynı bin ve 3 diğerleri ile küme aramak bidonları üzerinde çalıştırabilirsiniz. Bu daha büyük resultsets için kuadratik algoritması herhangi bir iyileştirme yenecek lineer zaman kümelenme neden olur.

Eğer yapabilirsen, ilk arama boylam tarafından işaretçileri sıralamak; sonra en kısa sürede bir işaretleyici kriteri listedeki bir sonraki işarete bir işaret genişliğinden daha fazla olduğu gibi, kesinlikle kalan işaretleri üst üste olmayacak biliyorum, bu yüzden daha sonra foreach döngüsünü kırmak ve kendinize işleme zaman bir ton kaydedebilirsiniz. Ben kendi sitesinde bu hayata geçirdik ve çok verimli çalışır.

Python içinde bazı kaynak kodu var here.

Yani burada ne yaptım - Ben Mercator aşağıdaki fonksiyonları kullanarak enlem ve boylam değerlerini dönüştürülmüş ile (nokta) tablo belirteçleri iki ilave sütun eklendi:

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);
}

Bu şekilde ben doğru kümeleri yer alabilir. Ben hala array_pop kullanarak ve her zaman yoluyla bisiklet önlemek için nasıl çalışmak çalışıyorum. Şimdiye kadar onun oldukça verimli alt-1000 işaretleri ile. Daha sonra 5 K sonuçlarını ve 10 K işaretçileri gönderme olacak.

PixelDistance işlevini kaçınmak ve o sahip satır içi neredeyse yarıya performansını artırır!