Düzensiz poligon centroidi bulmak için algoritma (etiket noktası)


13

Google Haritalar'da düzensiz şekilli çokgenler için bir sentroid (veya etiket noktası) bulmam gerekiyor. Ben parsel için InfoWindows gösteriyorum ve yüzeyde olması garanti InfoWindow sabitlemek için bir yere ihtiyacım var. Aşağıdaki resimlere bakın.

alternatif metin alternatif metin

Gerçekte, Google Haritalar'a özgü hiçbir şeye ihtiyacım yok, sadece bu noktayı otomatik olarak nasıl bulacağınıza dair bir fikir arıyorum.

İlk fikrim, ortalama lat ve lng'leri alarak rastgele yerleştirmeyi ve çokgeni kesen bir tanesini bulana kadar rastgele yerleştirmeyi "yanlış" sentroid bulmaktı. Zaten çokgen noktasında kod var. Bu bana çok kötü geliyor.

Ben ST_PointOnSurface (the_geom) gibi bir şey yapamam geometri çıktı sunucu tarafı kodu herhangi bir erişim olmadığını unutmayın.

Yanıtlar:


6

Hızlı ve kirli: "Yanlış" sentroid çokgende değilse o noktaya en yakın tepe noktasını kullanın.


Bunu düşünmemiştim. İdeal olarak bu noktayı poligonda istiyorum, kenarda değil, ama geri döndüğüm şey olabilir.
Jason

Bir kenar noktası bulduktan sonra, çokgenle o noktada ortalanmış küçük bir kareyi kesebilir ve sonra kavşağın merkezini seçebilirsiniz. Kare yeterince küçük olduğunda, bunun bir iç nokta olduğu garanti edilir (tabii ki bir kenara çok yakın olacaktır).
whuber

@Jason Gerçek centroid kullanıyorsanız bu sorunla karşılaşma olasılığınız daha düşük olabilir. Bir şeyi JavaScript'e hızlıca çevirmek çok zor olmamalı: github.com/cartesiananalytics/Pipra/blob/master/src/…
Dandy

Çözümüm (sahte sentroidden gelen ışınlar) çoğu zaman işe yarayacak olsa da, bu çözüm muhtemelen basitliği ve en azından kenarda bir nokta bulmayı garanti ettiğiniz ve muhtemelen kolayca değişebileceği için en iyi sonucu alacağını düşünüyorum. çok az çaba ile poligonun içinde olmak.
Jason

3

Şuna bakmak isteyebilirsiniz: http://github.com/tparkin/Google-Maps-Point-in-Polygon

Sunulan durumla eşleşen bir Ray Casting algoritması kullanıyor gibi görünüyor.

Burada bir blog yazısı var. http://appdelegateinc.com/blog/2010/05/16/point-in-polygon-checking/


Bunu sunucu tarafında uygulamak istiyorsanız, hem JTS (Java) hem de Geos (C) bu işlevselliği uygular.
DavidF

Evet, muhtemelen benim "hesaplanan" sentroid çokgen içinde olup olmadığını belirlemek için kod var zaten eklemeliydim. Aslında istediğim, poligon içinde bir sentroid oluşturmanın bir yolu.
Jason

3

(Daha eski) bir ESRI algoritması kütle merkezini hesaplar ve çokgene dahil edilmek üzere test ettikten sonra gerekirse çokgenin içine yerleşene kadar yatay olarak hareket ettirir. (Bu, programlama ortamınızda hangi temel işlemlerin mevcut olduğuna bağlı olarak birçok şekilde yapılabilir.) Bu, çokgenin görsel merkezine oldukça yakın etiket noktaları üretme eğilimindedir: Bunu resimde deneyin.


1

Sorunumu, popüler epoly kodunu http://econym.org.uk/gmap adresinden uzatarak çözdüm . Temelde yaptığım şey şuydu:

  • "Sahte sentroid" den başlayıp her köşeye ve yana uzanan bir dizi ışın oluşturun (toplam 8)
  • Her bir ışın için yüzde 10,20,30 ... kademeli olarak bir nokta oluşturun ve bu noktanın orijinal poligonumuzda olup olmadığını görün

Aşağıda genişletilmiş epoly kodu:

google.maps.Polygon.prototype.Centroid = function() {
var p = this;
var b = this.Bounds();
var c = new google.maps.LatLng((b.getSouthWest().lat()+b.getNorthEast().lat())/2,(b.getSouthWest().lng()+b.getNorthEast().lng())/2);
if (!p.Contains(c)){
    var fc = c; //False Centroid
    var percentages = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]; //We'll check every 10% down each ray and see if we're inside our polygon
    var rays = [
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getSouthWest().lng())]}),
        new google.maps.Polyline({path:[fc,b.getNorthEast()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,b.getSouthWest()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),b.getSouthWest().lng())]})
    ];
    var lp;
    for (var i=0;i<percentages.length;i++){
        var percent = percentages[i];
        for (var j=0;j<rays.length;j++){
            var ray = rays[j];
            var tp = ray.GetPointAtDistance(percent*ray.Distance()); //Test Point i% down the ray
            if (p.Contains(tp)){
                lp = tp; //It worked, store it
                break;
            }
        }
        if (lp){
            c = lp;
            break;
        }
    }
}
return c;}

Hala biraz acayip ama işe yarıyor gibi görünüyor.


Bu yöntem bazı kıvrımlı çokgenler için başarısız olacaktır. Örneğin, {{0, 9}, {10, 20}, {9, 9}, {20, 10}, {9, 0}, {20, -10}, {9, -9} çoklu satırını arabelleğe alın , {10, -20}, {0, -9}, {-10, -20}, {-9, -9}, {-20, -10}, {-9, 0}, {-20, 10}, {-9, 9}, {-10, 20}, {0,9}} ile 1/2 arasında. Örneğin, Dandy'nin QAD yöntemine kıyasla da verimsizdir.
whuber

1

Bunu yapmak için başka bir 'kirli' algoritma:

  • Geometrinin sınırlayıcı kutusunu alın (Xmax, Ymax, Xmin, Ymin)

  • ( Xmin+rand*(Xmax-Xmin), Ymin+rand*(Ymax-Ymin) ) Geometride rastgele bir nokta bulunana kadar döngü yapın ( Google-Maps-Point-in-Polygon kullanarak )


+1 çünkü bunun ikinci kez isabet şansı olabilir. "Rastgele" kullanıcı rahatsız değil her zaman tekrarlanabilir olduğu sürece bu da geçerli bir çözümdür. Yakında geçerli bir noktaya çarpmama ihtimali, özellikle iyi bir tahmin noktasıyla başlarsanız, zayıftır.
Dandy

1
@Dandy: Aslında, bazı durumlarda bu gerçekten zayıf bir algoritma olabilir. Örneğin dar bir çapraz şeridi düşünün. Bunlar pratikte mevcuttur (örneğin, uzun yol cephesi parselleri) ve sınırlayıcı kutunun% 0.1'inden daha azını (bazen çok daha az) işgal edebilir. Bu teknikle böyle bir poligona çarptığından makul bir şekilde emin olmak için (% 95 emin olmak için) yaklaşık 3.000 tekrar gerekir.
whuber

@Whuber: Kötü bir başlangıç ​​yeri seçerseniz, tamamlanması biraz zaman alabilir. Ayrıca, tıklamaların% 95'inin daha arzu edilen geometriler üzerinde olacağını düşünürseniz, bu yalnızca% 5'lik bir sorun olabilir. Ayrıca başka bir CBS'de olduğu gibi, performansın amacı tek bir çözüm yoksa, sezgisel yöntemlere dayalı taktikleri değiştirmek en iyisidir. 3000 iterasyon için bunu çalıştırmak için hiçbir neden yoktur. Her zaman 10 sonra benim QAD için kefalet olabilir. Konumu daha cazip olabilir gibi birkaç iterations için bunu denemek için buna değer olacağını düşünüyorum.
Dandy

@Dandy: Ama QAD çözümünüzün sorunu nedir? Hatta orijinal deneme etiket noktasından çokgenin bazı dahili tamponundaki en yakın tepe noktasına geçerek küçük bir değişiklik yapabilirsiniz: hala QAD, ancak şimdi orijinal özelliğin dahili bir yerine ineceği garanti edilmektedir. BTW, yakında kurtarma stratejiniz iyi bir stratejidir. Ne zaman böyle rasgele bir sonda kodlarsam, özellik alanının sınırlayıcı kutununkine oranını önceden hesaplar, başarıya ulaşmak için beklenen zamanı bulmak için onu kullanır ve uzun sürebilirse kullanıcıyı hemen uyarırım.
whuber

@ Alan oranı sezgisel tarama harika bir fikir çünkü alanı hesaplarken neredeyse sentroidi hesaplıyorsunuz. QAD çözümümle ilgili soruna gelince: kenarda. Eğer o noktayı seçer ve söylediğin gibi arabelleğe alırsam, "küçük" yarıçap o dar bölümdeki uzunluktan daha büyük olabilir. Her zaman bir köşe çantası vardır. Dikkate alınması gereken çok şey var, sadece kullanıcı arayüzünü dağıtacak ve geometriyi yine de gizleyecek bir balon yapmak için. Muhtemelen en yüksek veya en düşük tepe noktasını seçmek daha iyidir.
Dandy

1

Kesinlikle iç mekanı tercih edeceğiniz konusundaki son açıklamanız ışığında, Medial Eksen Dönüşümünde çokgenin sınırında olmayan herhangi bir noktayı seçebilirsiniz. (Bir MAT için kodunuz yoksa, çokgeni negatif olarak arabelleğe alarak yaklaşık olarak belirleyebilirsiniz. İkili veya sekant araması, hızla MAT'ın bir kısmına yaklaşan küçük bir iç çokgen üretecektir; sınırındaki herhangi bir noktayı kullanın.)


Bir geometrinin kenarını kullanmakla ilgili söylediklerinizi anlıyorum ki kenar ilgilenilen çokgenin iç kısmında olacak. Bu kenarı / tepe noktasını nasıl oluşturacağınızı anlamıyorum. Aklıma gelen tek şey, ilgilenilen noktadan seçilen noktanın segmentinin karşısındaki parçaya dik bir ışınla keserek sanal bir üçgen yapmaktır. Bu iki nokta arasındaki orta nokta o sanal üçgenin tepesi olabilir.
Dandy

@ Candy: Bu onun kalbine geliyor. CBS'nizin doğal olarak ne yaptığına bağlı olarak bununla ilgili birçok yol vardır. Örneğin, bir dizi pozitif uzunluktaki orijinal özellikle kesişen bir ışın bulduğunuzda, bu kesişme çizgi segmentlerinin ayrık bir birleşimi olacaktır. Bu segmentlerden herhangi birinin merkezini kullanın. Başka bir yol, özellik üzerindeki herhangi bir nokta ile başlamaktır (tercihen ortasına yakın, QED yönteminizin başardığı şeydir), orada ortalanmış küçük bir basit çokgen (örn. Kare) oluşturmak, orijinal özellikle kesişmek, benzersiz bağlantıyı seçmek bileşen ...
whuber

(devam) ... başlangıç ​​noktasını içerir ve tekrar tekrar bu bileşen için bir merkez seçer. CBS'niz, özelliğin sınırını tanımlayan köşe dizileri arasında geçiş yapmanıza izin verdiğinde çok sayıda yöntem olacaktır. Negatif tamponlar destekleniyorsa, bir dizi maksimum mesafe iç noktasını (MAT'nin bir alt kümesi olan "iskelet") tekrarlı olarak bulabilirsiniz. Bu biraz pahalıdır, ancak programlanması oldukça kolaydır ve mükemmel etiket noktaları üretir.
whuber

0

Neden sentroidi sadece dikey (enlem) pozisyon için kullanmıyorsunuz? Ardından, bu enlemdeki ortalama boylamı seçerek etiketi yatay olarak konumlandırabilirsiniz . (Bunun için, herhangi bir sorun vermemesi gereken belirli bir enlemde çokgen kenarın boylam değerini bulmanız gerekir).

Ayrıca, U şekillerine ve daha karmaşık olanlara dikkat edin. :) Muhtemelen, bilgi penceresi bu şekilde yönlendirildiğinden, en sağdaki boylam çiftinin ortalamasını seçin (her bir çift çokgenin bir dilimine karşılık gelir)?

Bu da konumlandırma üzerinde biraz daha kontrol sağlar; örneğin, çokgenin daha fazla görünmesini sağlamak için bilgi penceresini dikey olarak% 66 veya% 75 olarak konumlandırmak iyi olabilir. (Ya da olmayabilir! Ama ayar yapmak için topuzunuz var.)


0

Kullanıcının seçtiği, kullanıcının seçmek için tıklattığı noktayı kullanmaya ne dersiniz.


Bir fare tıklaması veya uzamsal olmayan sorgu ile seçilebilir, bu nedenle bu her zaman işe yaramaz.
Jason

0

Ben de bunu çözmeye çalışıyorum. Çokgenlerime, tarif edeceğim şeylere giren kesişen çizgilere sahip olamayacakları bir koşul getirdim.

Bu yüzden yaklaşımım üçgenleme kullanıyor. Rastgele bir tepe noktası alın (muhtemelen aşırı N, E, W veya S'de bir tepe noktası alın, işleri basitleştirebilir).

Bu tepe noktasından, bir köşe uzaktaki tepe noktasına çizgiler çizin, yani tepe noktanız köşe 3 ise, köşe 3 + 2'ye bakın.

Orijinal tepe noktanızdan bu tepe noktasına bir çizgi oluşturun. Eğer inşa hattı:

  1. başka çizgiyi geçmez ve
  2. orta noktası çokgenin dışında değil

Sonra çokgenin içinde bir üçgen inşa ettiniz. Başarılı tepe noktası n + 2 ise, üçgeniniz {v, v1, v2} olarak adlandırdığımız {n, n + 1, n + 2} olur. Değilse, bir sonraki köşeyi deneyin ve tüm köşeler denenene kadar devam edin.

Bir üçgen bulduğunuzda, v köşesinden v1 ve v2'nin orta noktasına bir çizgi alarak bunun merkezini bulun. Bu çizginin orta noktasının üçgenin içinde ve çokgenin içinde olması garanti edilir.

Bunu henüz kodlamadım, ancak geçiş çizgileri olan bir çokgenin aslında bunun işe yaramadığı bazı egzotik koşullara neden olacağını düşündüğüm gibi görebiliyorum. Sahip olduğunuz çokgen türüyse, çokgendeki her bir çizgi parçasını test etmeniz ve çaprazlanmadığından emin olmanız gerekir. Çaprazlanan çizgi parçalarını atlayın ve bence işe yarayacak.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.