2011-08-08 11 views
11

Mam więc MapView z wieloma znacznikami, z których większość jest skoncentrowana w klastrach o szerokości mili. Po powiększeniu znaczniki zachodzą na siebie i wydają się być tylko jeden. To, co chcę osiągnąć, to na pewnym poziomie powiększenia zastąpić zachodzące na siebie znaczniki znacznikiem grupy, który będzie wyświetlał gęstość znaczników, a onClick będzie powiększał, aby wyświetlić wszystkie znaczniki w środku. Wiem, że mogę to zrobić z pomiarem odległości w brutalnej sile, ale musi być bardziej efektywny sposób. Ktoś ma jakieś rozwiązanie lub inteligentne algorytmy, w jaki sposób mogę to osiągnąć?Android Mapview: Łączenie nakładających się znaczników na nowy znacznik

Odpowiedz

11

Um ... zakładając, że znaczniki nie są zgrupowane, warstwowe lub cokolwiek: dlaczego - przed ich wyświetleniem - nie tworzysz siatki o określonej gęstości i po prostu wkładasz znaczniki do komórek siatki?

Jeśli następnie policzycie, że kilka markerów wpada do tego samego pojemnika (komórki siatki) - można je zgrupować. Jeśli potrzebujesz nieco sprytniejszego grupowania, możesz również sprawdzić sąsiednie komórki.

Może brzmi to nieco prymitywne ale:

  • Nie N^2 algorytmy
  • No założenie o kolejności wejścia
  • Nie trzeba dodatkowo markery procesu, które nie będzie pokazany

Kod na siatce:

Uwaga - Pochodzę ze świata C++ (dotarłem tutaj przez znacznik [algorithm]), więc trzymam się pseudo C++. Nie znam interfejsu API widoku mapy. Ale byłbym zaskoczony, gdyby nie można go było skutecznie przetłumaczyć na język/bibliotekę, której używasz.

Wejście: - lista znaczników - prostokąt okna we współrzędnych światowych oglądania (część świata poszukujemy at)

W najprostszej formie, to będzie wyglądać mniej więcej tak:

void draw(MarkerList mlist, View v) { 

    //binning: 

    list<Marker> grid[densityX][densityY]; //2D array with some configurable, fixed density 
    foreach(Marker m in mlist) { 
     if (m.within(v)) { 
      int2 binIdx; 
      binIdx.x=floor(densityX*(m.coord.x-v.x1)/(v.x2-v.x1)); 
      binIdx.y=floor(densityY*(m.coord.y-v.y1)/(v.y2-v.y1)); 
      grid[binIdx.x][binIdx.y].push(m); //just push the reference 
     } 

    //drawing: 

    for (int i=0; i<densityX; ++i) 
    for (int j=0; j<densityY; ++j) { 
     if (grid[i][j].size()>N) { 
      GroupMarker g; 
      g.add(grid[i][j]); //process the list of markers belonging to this cell 
      g.draw(); 
     } else { 
      foreach (Marker m in grid[i][j]) 
       m.draw() 
     } 
    } 

} 

Problem, który może się wydawać, polega na tym, że niechciany podział siatki może pojawić się w obrębie grupy klastrowej, tworząc dwa GroupMarkery. Aby temu przeciwdziałać, możesz rozważyć nie tylko jedną komórkę siatki, ale także jej sąsiadów w sekcji "\ rysunek" i - jeśli są pogrupowane - zaznacz sąsiednie komórki jako odwiedzane.

+0

Czy możesz podać mi przykładowy kod na temat tego, jak wydajnie utworzyć sieć? – NSjonas

+0

Czy ten kod działa poprawnie podczas powiększania mapy? Ponieważ zakładam, że pola siatki muszą być większe, gdy mapa jest pomniejszona. – adrianTNT

+0

Rozmiar siatki zależy bezpośrednio od światowych współrzędnych prostokąta widoku 'v' w liniach, w których obliczane są wartości' binIdx'. W konsekwencji dostosuje się do Twojego poziomu powiększenia. "Stała gęstość" to gęstość siatki w przestrzeni ekranu, a nie w przestrzeni świata. – CygnusX1

2

Zakładając, że twoje znaczniki są pogrupowane w ItemizedOverlay, możesz utworzyć metodę, która została wywołana podczas powiększania mapy. To porównałoby współrzędne pikseli każdego znacznika, aby zobaczyć, czy nakładają się one i ustawiają flagę. Następnie w metodzie losowania możesz narysować zgrupowany znacznik lub pojedyncze osoby;

Coś jak:

//this would need to be wired to be called when the mapview is zoomed 
    //it sets the drawgrouped flag if co-ordinates are close together 
    Boolean drawGrouped=false; 
    public void onMapZoom(MapView mapView){ 
     //loop thru overlay items 
     Integer i,l=this.size(); 
     OverlayItem item; 
     Integer deltaX=null,deltaY=null; 
     Projection proj = mapView.getProjection(); 
     Point p=new Point(); 
     Integer x=null,y=null; 
     Integer tolerance = 10; //if co-ordinates less than this draw grouped icon 
     for(i=0;i<l;i++){ 
     //get the item 
     item=this.getItem(i); 
     //convert the overlays position to pixels 
     proj.toPixels(item.getPoint(), p); 
     proj.toPixels(item.getPoint(), p); 
     //compare co-ordinates 
     if(i==0){ 
      x=p.x; 
      y=p.y; 
      continue; 
     } 
     deltaX=Math.abs(p.x-x); 
     deltaY=Math.abs(p.y-y); 

     //if the co-ordinates are too far apart dont draw grouped 
     if(deltaX>tolerance || deltaY>tolerance){ 
      drawGrouped=false; 
      return; 
     } 
     x=p.x; 
     y=p.y; 
     } 
     //all co-ords are within the tolerance 
     drawGrouped=true; 
    } 

    public void draw(android.graphics.Canvas canvas, MapView mapView, boolean shadow){ 
     if(drawGrouped==true){ 
      //draw the grouped icon *needs to be optimised to only do it once 
      drawGrouped(canvas,mapView,shadow); 
      return; 
     } 
     //not grouped do regular drawing 
     super.draw(canvas, mapView, shadow); 
    } 
+0

Dzięki temu spróbuję. Mogłem zobaczyć, że robi się powoli, jeśli są tysiące markerów, ale chyba nie ma lepszego sposobu. Miałem nadzieję, że było jakieś wsparcie dla api, które obejrzałem. – NSjonas

1

Jeśli znaczniki są zgrupowane, będziesz mieć sprawiedliwy pomysł powiększenia na poziomie co powinno być wyświetlanie pojedynczych markery lub grupy markerów np poziom powiększenia> 17 następnie wyświetlaj poszczególne znaczniki, w przeciwnym razie wyświetlaj znacznik grupy. Użyłem kodu coś takiego w moim ItemizedOverlay zmienić swoje znaczniki:

@Override 
public void draw(Canvas canvas, MapView mapv, boolean shadow) 
{  
    int zoom = mapv.getZoomLevel(); 

    switch(zoom) 
    { 
     case 19: 
      setMarkersForZoomLevel19(); 
      break; 
     case 18: 
      setMarkersForZoomLevel18(); 
      break; 
     case 17: 
      setMarkersForZoomLevel17(); 
      break; 
     case 16: 
      setMarkersForZoomLevel16(); 
      break; 
     default: 
      // Hide the markers or remove the overlay from the map view.     
      mapv.getOverlays().clear(); 
    }  

    area.drawArea(canvas, mapv); 

    // Putting this call here rather than at the beginning, ensures that 
    // the Overlay items are drawn over the top of canvas stuff e.g. route lines. 
    super.draw(canvas, mapv, false);   

} 


private void setMarkersForZoomLevel19() 
{  
    for (JourneyOverlayItem item : mOverlays) 
    {    
     item.setMarker(areaPointIcon48);    
    } 
} 

jeśli jego możliwości aby poszczególne znaczniki w kolekcji, można łatwo dostać się do największej i najmniejszej szerokości i długości geograficznej, a różnica między nimi będzie Podaje szerokość i długość geograficzną (można to wykorzystać do powiększenia zakresu, aby pokazać grupę znaczników). Podziel przęsła o 2 i powinieneś mieć punkt środkowy do umieszczenia znacznika grupy.

+0

Co dokładnie masz na myśli przez zgrupowanie? Moje znaczniki znajdują się na tej samej ItemizedOverlay. – NSjonas

+0

Tak, ale możesz mieć swoje geopoints markerowe zawarte w kilku kolekcjach, np.jeśli masz dużo znaczników w lewym górnym kwadracie mapy, może to być jedna kolekcja, a następnie łatwiej jest zidentyfikować punkt centralny. Możesz również podzielić mapę na sektory i zebrać kolekcję dla każdego sektora. –

2

To, czego szukasz, zwykle nazywa się klastrowaniem. Istnieją powszechne techniki, aby to zrobić, możesz odwołać się na przykład do tego SO question, prowadzi to do tego post.

Podstawową ideą jest podział mapy na kwadraty w oparciu o bieżący poziom powiększenia (można wykonywać obliczenia pamięci podręcznej na podstawie poziomu powiększenia, aby uniknąć ponownego obliczania, gdy użytkownik rozpoczyna powiększanie) i grupować je na podstawie kwadratu, do którego należą. . Kończy się to w pewnym grupowaniu na podstawie poziomu powiększenia, tzn. Dla poziomu 1-5 wystarczy narysować znaczniki, dla poziomu 5-8 grupować je w kwadraty 20 mil, dla 9-10 na kwadratach 50 mil, a więc na.

Oto kolejny istotne pytanie na SO, że może warto spojrzeć, nie wiedząc o wykonywaniu tego jednak: Android Maps Point Clustering

+0

dzięki, naprawdę dobra informacja. Musiałem przekazać nagrodę CygnusX1, ponieważ już wcześniej zadał sobie trud napisania tego rozwiązania, gdy zapytałem: – NSjonas

3

Konwertowałem odpowiedź Cygnus X1 na Javę. Umieść tę metodę w niestandardowej nakładce i zmodyfikuj drawSingle() i drawGroup(), aby pasowały do ​​twoich potrzeb. Poprawiasz także wydajność, na przykład konwertujesz obiekty ArrayLists na prymitywne tablice.

@Override 
    public void draw(Canvas canvas, MapView mapView, boolean shadow) { 
     // binning: 
     int densityX = 10; 
     int densityY = 10; 
     // 2D array with some configurable, fixed density 
     List<List<List<OverlayItem>>> grid = new ArrayList<List<List<OverlayItem>>>(
       densityX); 

     for(int i = 0; i<densityX; i++){ 
      ArrayList<List<OverlayItem>> column = new ArrayList<List<OverlayItem>>(densityY); 
      for(int j = 0; j < densityY; j++){ 
       column.add(new ArrayList<OverlayItem>()); 
      } 
      grid.add(column); 
     } 

     for (OverlayItem m : mOverlays) { 
       int binX; 
       int binY; 

       Projection proj = mapView.getProjection(); 
       Point p = proj.toPixels(m.getPoint(), null); 

      if (isWithin(p, mapView)) { 
       double fractionX = ((double)p.x/(double)mapView.getWidth()); 
       binX = (int) (Math.floor(densityX * fractionX)); 
       double fractionY = ((double)p.y/(double)mapView.getHeight()); 
       binY = (int) (Math 
         .floor(densityX * fractionY)); 
//    Log.w("PointClusterer absolute", p.x+ ", "+p.y); 
//    Log.w("PointClusterer relative", fractionX+ ", "+fractionY); 
//    Log.w("PointClusterer portion", "Marker is in portion: " + binX 
//      + ", " + binY); 
       grid.get(binX).get(binY).add(m); // just push the reference 
      } 
     } 

     // drawing: 

     for (int i = 0; i < densityX; i++) { 
      for (int j = 0; j < densityY; j++) { 
       List<OverlayItem> markerList = grid.get(i).get(j); 
       if (markerList.size() > 1) { 
        drawGroup(canvas, mapView, markerList); 
       } else { 
        // draw single marker 
        drawSingle(canvas, mapView, markerList); 
       } 
      } 
     } 
    } 

    private void drawGroup(Canvas canvas, MapView mapView, 
      List<OverlayItem> markerList) { 
     GeoPoint point = markerList.get(0).getPoint(); 
     Point ptScreenCoord = new Point(); 
     mapView.getProjection().toPixels(point, ptScreenCoord); 
     Paint paint = new Paint(); 
     paint.setTextAlign(Paint.Align.CENTER); 
     paint.setTextSize(30); 
     paint.setAntiAlias(true); 
     paint.setARGB(150, 0, 0, 0); 
     // show text to the right of the icon 
     canvas.drawText("GROUP", ptScreenCoord.x, ptScreenCoord.y + 30, paint); 
    } 

    private void drawSingle(Canvas canvas, MapView mapView, 
      List<OverlayItem> markerList) { 
     for (OverlayItem item : markerList) { 
      GeoPoint point = item.getPoint(); 
      Point ptScreenCoord = new Point(); 
      mapView.getProjection().toPixels(point, ptScreenCoord); 
      Paint paint = new Paint(); 
      paint.setTextAlign(Paint.Align.CENTER); 
      paint.setTextSize(30); 
      paint.setAntiAlias(true); 
      paint.setARGB(150, 0, 0, 0); 
      // show text to the right of the icon 
      canvas.drawText("SINGLE", ptScreenCoord.x, ptScreenCoord.y + 30, 
        paint); 
     } 
    } 

    public static boolean isWithin(Point p, MapView mapView) { 
     return (p.x > 0 & p.x < mapView.getWidth() & p.y > 0 & p.y < mapView 
       .getHeight()); 
    } 
} 
+0

co to jest mOverlays? Co muszę zadeklarować jako? – Shrikant

+0

To przeglądy, które przechodzą przez twój MapView, np. MapView.getOverlays(). Zauważ, że ten kod jest naprawdę niechlujny i powinieneś przekonwertować ArrayLists na prymitywne tablice, aby uzyskać ogromny wzrost wydajności. – Maarten

0

Jest to podejście, którego użyłem. Jednak jest to O (n^2).

Piny muszą być posortowane na podstawie wybitnych.

Sworzeń z najwyżej widocznym. Spójrz na wszystkie szpilki wokół niego. Absorbuj sworznie w pobliżu tego sworznia.

Następnie przejdź do następnej najwyżej widocznej szpilki. Zrobić to samo. Powtarzać.

Proste.

Rzeczy się komplikują, gdy przesuwasz mapę, powiększasz, oddalasz i chcesz, aby nowe szpilki nie były przerysowane. Sprawdzasz więc każdy klaster, czy musi on dzielić się podczas powiększania, a następnie sprawdza każdy klaster, czy musi się scalić podczas pomniejszania. Następnie usuwasz szpilki, które zniknęły i dodajesz nowe szpilki. Dla każdej dodanej szpilki sprawdzasz, czy powinni dołączyć do klastra, czy utworzyć własny klaster.

Powiązane problemy