2011-08-08 101 views
11

所以我有一個帶有大量標記的MapView,其中大部分都集中在英里寬的集羣中。當縮放標記重疊並似乎只有一個。我想要達到的是在某個縮放級別下用重疊的標記替換重疊的標記,該標記將顯示標記的密度,並且onClick將縮放以顯示內部的所有標記。我知道我可以用蠻力測距來做到這一點,但是必須有更高效的方法。任何人都可以通過解決方案或者智能算法來實現這個目標?Android Mapview:將重疊標記合併到一個新標記中

回答

11

嗯......假設標記沒有分組,分層或任何東西:爲什麼 - 在顯示它們之前 - 您是不是創建了一定密度的網格並將標記簡單地放入網格的單元格中?

如果您再計算幾個標記落入同一個bin(網格單元格) - 您可以對它們進行分組。如果你需要稍微更聰明的分組,你也可以檢查鄰近的單元格。

也許這聽起來有點原始,但:

  • 沒有N^2種算法
  • 關於輸入
  • 沒有必要的排序沒有設想到另外它不會處理標記來顯示

網格的代碼:

注 - 我來自C++世界(通過[算法]標籤得到這裏),所以我會堅持僞C++。我不知道mapview的API。但如果這不能有效地翻譯成你正在使用的任何語言/圖書館,我會感到驚訝。

輸入: - 的標誌 列表 - 矩形觀看世界座標​​窗口(我們目前正在尋找世界的科)

在最簡單的形式,它會是這個樣子:

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

} 

可能出現的問題是不需要的網格拆分可能出現在某個聚簇組中,形成兩個GroupMarkers。爲了解決這個問題,你可能不僅要考慮一個網格單元,還要考慮其在「\ drawing」部分中的鄰居,並且 - 如果分組 - 將相鄰單元標記爲已訪問。

+0

你能給我一些關於如何有效地創建網格的示例代碼嗎? – NSjonas

+0

當您縮放地圖時,此代碼是否正常工作?因爲我認爲地圖縮小時網格框需要更大。 – adrianTNT

+0

網格的大小直接取決於計算'binIdx'值的行中視圖矩形'v'的世界座標。因此,它將適應您的縮放級別。 「固定密度」是屏幕空間中的網格密度,而不是世界空間。 – CygnusX1

2

假設您的標記在一個ItemizedOverlay中組合在一起,您可以創建一個在地圖縮放時調用的方法。這將比較每個標記的像素座標以查看它們是否重疊並設置標記。然後在繪製方法中,您可以繪製分組標記或個人;

喜歡的東西:

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

謝謝,我會試試看。如果有成千上萬的標記,我可以看到它變得緩慢,但我猜這是沒有更好的辦法。我希望有一些API支持,我已經看過 – NSjonas

1

如果標記分組,你必須在什麼縮放級別的公平的想法,你應該顯示單個標記或標記組例如縮放級別> 17,然後顯示單個標記,否則顯示組標記。我使用的代碼這樣的事情在我的ItemizedOverlay改變我的標記:

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

如果它可能有一個集中的個體標記,你可以很容易地獲得最大和最小的緯度和經度以及它們之間的差異將給你經緯度範圍(這可以用來縮放跨度以顯示標記組)。除以2的跨度,你應該有放置組標記的中心點。

+0

你是什麼意思分組。我的標記都在同一個ItemizedOverlay上。 – NSjonas

+0

是的,但您可以在幾個集合中包含您的標記地理位置,例如如果地圖左上角有很多標記,則這些標記可能在1個集合中,然後更容易識別中心點。或者,將您的地圖劃分爲不同的部門併爲每個部門收集一份。 –

2

你在找什麼通常稱爲聚類。有一些常見的技術可以做到這一點,例如,你可以參考這個SO question,它會導致這post

其基本思路是根據當前縮放級別將地圖分成平方(可以基於縮放級別緩存計算以避免在用戶開始縮放時重新計算),並根據它們屬於哪個平方。所以你最終會根據縮放級別進行某種分組,例如1-5級只畫出標記,5-8級將它們分成20英里的正方形,在50英里的正方形中放置9-10個,等等上。

這裏是SO另一個相關的問題,你可能想看看,不知道這雖然表現:Android Maps Point Clustering

+0

謝謝,確實不錯的資料。我必須向CygnusX1提供賞金,因爲他已經經歷了寫出這個解決方案的麻煩,因爲我問了 – NSjonas

3

我將Cygnus X1的答案轉換爲Java。將此方法放入自定義疊加層中,並修改drawSingle()和drawGroup()以滿足您的需要。您也可以提高性能,例如將ArrayLists轉換爲原始數組。

@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

什麼是mOverlays? 我需要將其聲明爲? – Shrikant

+0

這是您通過MapView獲得的概述,例如MapView.getOverlays()。請注意,這段代碼是真實的馬虎,您應該將ArrayLists轉換爲原始數組以獲得巨大的性能提升。 – Maarten

0

這是我使用的方法。但是,它是O(n^2)。

引腳必須根據突出來排序。

突出最高的挑針。看看它周圍的所有引腳。吸收靠近該引腳的引腳。

然後移動到下一個最高的突出銷。照着做。重複。

簡單。

如果您移動地圖,放大圖像,縮小圖像,並且希望確保新圖釘未被重新繪製,情況就會變得複雜。因此,您需要檢查每個羣集是否必須在放大期間進行拆分,然後檢查每個羣集是否需要在縮小期間進行合併。然後你刪除不存在的引腳並添加新的引腳。對於添加的每個引腳,您都要檢查他們是否應該加入羣集或形成自己的羣集。