Android Mapview: объединение перекрывающихся маркеров в новый маркер - PullRequest
11 голосов
/ 09 августа 2011

Итак, у меня есть MapView с большим количеством маркеров, большинство из которых сосредоточены в кластерах шириной в милю.При увеличении маркеры перекрываются и кажутся только одним.Чего я хочу добиться, так это при определенном уровне масштабирования заменить перекрывающиеся маркеры групповым маркером, который будет отображать плотность маркеров, а onClick будет изменять масштаб, чтобы отобразить все маркеры внутри.Я знаю, что могу сделать это с помощью измерения расстояния грубой силой, но должен быть более эффективный способ.У кого-нибудь есть какое-нибудь решение или умные алгоритмы, как мне этого добиться?

Ответы [ 7 ]

11 голосов
/ 25 октября 2011

Гм ... при условии, что маркеры не сгруппированы, не наслоены или что-то еще: почему - прежде чем показывать их - вы не создаете сетку определенной плотности и просто помещаете маркеры в ячейки вашей сетки?

Если вы считаете, что несколько маркеров попадают в одну ячейку (ячейку сетки) - вы можете сгруппировать их.Если вам нужно немного более умное группирование, вы можете также проверить соседние ячейки.

Возможно, это звучит немного примитивно, но:

  • Нет n ^ 2 алгоритмов
  • Нет предположения о порядке ввода
  • Нет необходимости дополнительно обрабатывать маркеры, которые не будут отображаться

Код для сетки:

Примечание. Я из мира C ++ (попал сюда через тег [attribute]), поэтому я буду придерживаться псевдо-C ++.Я не знаю API карты.Но я был бы удивлен, если бы это не могло быть эффективно переведено на любой язык / библиотеку, которую вы используете.

Ввод: - список маркеров - окно просмотра прямоугольника в мировых координатах (часть мира, которую мы сейчас ищемat)

В простейшей форме это будет выглядеть примерно так:

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", и - если они сгруппированы - пометить соседние ячейки как посещенные.

3 голосов
/ 20 июля 2012

Я преобразовал ответ 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());
    }
}
3 голосов
/ 28 октября 2011

Следующее прагматическое решение, основанное на расстоянии в пикселях, действительно работает лучше всего для меня:

http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps

2 голосов
/ 25 октября 2011

То, что вы ищете, обычно называется кластеризацией.Для этого есть общие приемы, например, вы можете сослаться на SO вопрос , это приводит к этому post .

Основная идея - разделитькарта на квадратах, основанная на текущем уровне масштабирования (вы можете кэшировать вычисления на основе уровня масштабирования, чтобы избежать пересчета, когда пользователь начинает масштабирование), и группировать их на основе того, к какому квадрату они принадлежат.Таким образом, у вас получится группировка, основанная на уровне масштабирования, то есть для уровня 1-5 просто нарисуйте маркеры, для уровня 5-8 сгруппируйте их в квадраты по 20 миль, по 9-10 в квадраты по 50 миль и т. Д.on.

Вот еще один важный вопрос о SO, на который вы, возможно, захотите взглянуть, хотя и не уверены в его производительности: Точка кластеризации Android Maps

2 голосов
/ 20 октября 2011

Предполагая, что ваши маркеры сгруппированы в оверлей Itemized, вы можете создать метод, который вызывался при увеличении карты.Это позволит сравнить координаты пикселей каждого маркера, чтобы увидеть, перекрываются ли они, и установить флаг.Затем в методе рисования вы можете нарисовать либо сгруппированный маркер, либо отдельные лица;

Что-то вроде:

    //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);
    }
1 голос
/ 21 октября 2011

Если ваши маркеры сгруппированы, у вас будет четкое представление о том, на каком уровне масштабирования вы должны отображать отдельные маркеры или маркер группы, например, уровень масштабирования> 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 голосов
/ 06 сентября 2012

Это подход, который я использовал. Однако это O (n ^ 2).

Штифты должны быть отсортированы по виду.

Подберите булавку с наибольшим выступом. Посмотрите на все булавки вокруг него. Поглотите контакты рядом с этим контактом.

Затем перейдите к следующему по высоте выступу. Делать то же самое. Повторите.

Simple.

Ситуация усложняется, если вы перемещаете карту, увеличиваете, уменьшаете масштаб и хотите убедиться, что новые выводы не перерисовываются. Таким образом, вы проверяете каждый кластер, если они должны разделиться во время увеличения, а затем вы проверяете каждый кластер, если они должны объединиться во время уменьшения. Затем вы удаляете булавки, которые ушли, и добавляете новые булавки. Для каждого добавляемого вами пина вы проверяете, должны ли они присоединиться к кластеру или сформировать собственный кластер.

...