2011-08-08 9 views
11

Tengo una vista de mapa con muchos marcadores, la mayoría de los cuales están concentrados en grupos de una milla de ancho. Cuando se amplía, los marcadores se superponen y parecen ser solo uno. Lo que quiero lograr es a un cierto nivel de zoom reemplazar los marcadores superpuestos con un marcador de grupo que mostrará la densidad de los marcadores y onClick hará un zoom para mostrar todos los marcadores dentro. Sé que puedo hacer esto con mediciones de distancia de fuerza bruta, pero debe haber una manera más eficiente. ¿Alguien tiene alguna solución o algoritmos inteligentes sobre cómo puedo lograr esto?Android Mapview: fusión de marcadores superpuestos en un nuevo marcador

Respuesta

11

Um ... suponiendo que los marcadores no están agrupados, en capas o algo por el estilo: por qué - antes de mostrarlos - ¿no creas una cuadrícula de cierta densidad y simplemente colocas los marcadores en las celdas de tu cuadrícula?

Si cuenta que varios marcadores caen en el mismo contenedor (celda de cuadrícula), puede agruparlos. Si necesita una agrupación ligeramente más inteligente, también puede verificar las celdas vecinas.

Tal vez que suena un poco primitivo, pero:

  • No n^2 algoritmos
  • No suposición acerca de la adquisición de la entrada
  • No hay necesidad de, además, marcadores de proceso que no van a ser mostrado

el código para la cuadrícula:

Nota: Vengo del mundo de C++ (aquí me tocó mediante la etiqueta [algorithm]), así que me quedaré con el pseudo-C++. No sé la API de la vista de mapa. Pero me sorprendería que esto no se pueda traducir eficientemente a cualquier idioma/biblioteca que esté utilizando.

de entrada: - lista de marcadores - el rectángulo ventana en coordenadas mundiales de visualización (sección del mundo que están estudiando)

En la forma más simple, se vería algo como esto:

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

} 

El problema que puede aparecer es que una cuadrícula indeseada puede aparecer dentro de un grupo agrupado, formando dos GroupMarkers. Para contrarrestar eso, es posible que desee considerar no solo una celda de cuadrícula, sino también sus vecinos en la sección "\ drawing", y, si está agrupada, marque las celdas vecinas como visitó.

+0

¿Podría darme algún código de muestra sobre cómo crearía la red de manera eficiente? – NSjonas

+0

¿Este código funciona correctamente cuando se acerca el mapa? Porque supongo que los cuadros de cuadrícula deben ser más grandes cuando el mapa se aleja. – adrianTNT

+0

El tamaño de la cuadrícula depende directamente de las coordenadas del mundo del rectángulo de vista 'v' en las líneas donde se calculan los valores de' binIdx'. En consecuencia, se adaptará a su nivel de acercamiento. La "densidad fija" es la densidad de la cuadrícula en el espacio de la pantalla, no en el espacio del mundo. – CygnusX1

2

Asumiendo que sus marcadores estén agrupados en una Objetorevelado, puede crear un método que se invocó cuando se amplió el mapa. Esto compararía las coordenadas de píxeles de cada marcador para ver si se superponen y establecen una bandera. Luego, en el método de sorteo, puede dibujar el marcador agrupado o los individuos;

Algo así como:

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

Gracias, voy a intentar esto. Podría ver que se vuelve un poco lento si hay miles de marcadores, pero supongo que no hay mejor manera. Esperaba que hubiera algo de soporte de API que he visto – NSjonas

1

Si se agrupan los marcadores, usted tendrá una idea clara a nivel de zoom que lo que debe mostrar los marcadores individuales o del grupo de marcación por ejemplo, nivel de zoom> 17 luego muestra marcadores individuales; de lo contrario, muestra el marcador de grupo. Solía ​​código de algo como esto en mi ItemizedOverlay cambiar mis marcadores:

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

Si su posible tener los marcadores individuales en una colección, usted puede fácilmente obtener el mayor y el menor latitud y longitud y la diferencia entre ellos se le da el rango de latitud y longitud (esto podría usarse para acercarse al tramo para mostrar el grupo de marcadores). Divida los tramos por 2 y debe tener el punto central para colocar el marcador de grupo.

+0

¿Qué quieres decir con agrupado? Mis marcadores están todos en el mismo ItemizedOverlay. – NSjonas

+0

Sí, pero podría tener los geopoints de su marcador en algunas colecciones, p. Ej.si tiene muchos marcadores en el cuadrante superior izquierdo del mapa, estos pueden estar en 1 colección y luego es más fácil identificar un punto central. Alternativamente, divida su mapa en sectores y tenga una colección para cada sector. –

2

Lo que está buscando generalmente se llama clustering. Hay técnicas comunes para hacer esto, puede referirse, por ejemplo, a este SO question, esto lleva a post.

La idea básica consiste en dividir el mapa en las plazas en función del nivel de zoom actual (se puede almacenar en caché los cálculos basados ​​en el nivel de zoom para evitar un nuevo cálculo cuando el usuario inicia el zoom), y agruparlos basan en qué cuadrado al que pertenecen . Así que terminas teniendo algún tipo de agrupamiento basado en el nivel de zoom, es decir, para el nivel 1-5 simplemente dibuja los marcadores, para el nivel 5-8 agrupalos en cuadrados de 20 millas, para 9-10 en cuadrados de 50 millas, y así en.

Aquí es otra cuestión relevante encendido de modo que es posible que desee echar un vistazo, no estoy seguro sobre el rendimiento de esto, sin embargo: Android Maps Point Clustering

+0

gracias, muy buena información. Tuve que darle la recompensa a CygnusX1 porque ya se había tomado la molestia de escribir esta solución, ya que le pregunté – NSjonas

3

He convertido la respuesta de Cygnus X1 a Java. Coloque este método en su Overlay personalizado y modifique drawSingle() y drawGroup() para adaptarlo a sus necesidades. También mejora el rendimiento, como convertir ArrayLists en matrices primitivas.

@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

¿qué es mOverlays? ¿Qué necesito declarar como? – Shrikant

+0

Son los resúmenes que obtiene a través de su MapView, por ejemplo, MapView.getOverlays(). Tenga en cuenta que este código es realmente descuidado, y debe convertir los ArrayLists en matrices primitivas para obtener un impulso de rendimiento masivo. – Maarten

0

Este es el enfoque que he utilizado. Sin embargo, es O (n^2).

Las clavijas se deben clasificar según su prominencia.

Pin de selección con el más alto prominente. Mire todos los alfileres que lo rodean. Absorbe los pernos cerca de ese pin.

Luego avance al siguiente pin más prominente. Hacer lo mismo. Repetir.

Simple.

Las cosas se complican si mueve el mapa, acerca y aleja la imagen, y quiere asegurarse de que las nuevas patillas no se vuelven a dibujar. Entonces, debe verificar cada grupo si tienen que dividirse durante el acercamiento, y luego verificar cada grupo si tienen que fusionarse durante el alejamiento. Luego quita los alfileres que se han ido y agrega nuevos alfileres. Por cada pin que agregue, compruebe si deben unirse a un clúster o formar su propio clúster.

Cuestiones relacionadas