所以我有一个带有很多标记的MapView,其中大多数都集中在英里宽的簇中。缩放时,标记重叠并且看起来只有一个。我想要实现的是在某个缩放级别用一个组标记替换重叠标记,组标记将显示标记的密度,onClick将缩放以显示内部的所有标记。我知道我可以用蛮力距离测量做到这一点,但必须有一种更有效的方法。任何人都有任何解决方案或智能算法如何实现这一目标?
答案 0 :(得分:11)
嗯......假设标记没有分组,分层或任何东西:为什么 - 在显示它们之前 - 你不创建一定密度的网格并简单地将标记分成网格的单元格吗?
如果您计算出几个标记落入同一个bin(网格单元格) - 您可以对它们进行分组。如果您需要稍微更聪明的分组,您也可以检查相邻的单元格。
也许听起来有点原始但是:
网格代码:
注意 - 我来自C ++世界(通过[algorithm]标签获取)所以我将坚持使用伪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”部分中的邻居,并且 - 如果已分组 - 将相邻单元格标记为已访问。
答案 1 :(得分:3)
基于像素距离的以下实用解决方案对我来说确实最有效:
http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps
答案 2 :(得分: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());
}
}
答案 3 :(得分: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);
}
答案 4 :(得分:2)
您正在寻找的通常称为群集。有一些常用的技巧,例如,你可以参考这个SO question,它会导致这个post。
基本思想是根据当前缩放级别在方块上划分地图(您可以根据缩放级别缓存计算以避免在用户开始缩放时重新计算),并根据它们所属的方格对它们进行分组。所以你最终会根据缩放级别进行某种分组,即1-5级只绘制标记,5-8级将它们分组为20英里的正方形,9-10英寸为50英里的正方形,等等上。
以下是关于SO的另一个相关问题,您可能需要查看一下,但不确定其性能如何:Android Maps Point Clustering
答案 5 :(得分:1)
如果您的标记已经分组,那么您可以清楚地知道应该在哪个缩放级别显示单个标记或组标记,例如缩放级别&gt; 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,您应该有放置组标记的中心点。
答案 6 :(得分:0)
这是我使用的方法。但是,它是O(n ^ 2)。
必须根据突出部分对引脚进行分类。
挑选最突出的针脚。看看它周围的所有针脚。吸收该引脚附近的引脚。
然后转到下一个最高的引脚。照着做。重复。
简单。
如果你移动地图,放大,缩小,并且你想确保没有重新绘制新的引脚,事情会变得复杂。因此,如果必须在放大期间进行拆分,则检查每个群集,然后检查每个群集是否必须在缩小期间进行合并。然后你删除已经消失的引脚并添加新的引脚。对于您添加的每个引脚,您可以检查它们是应该加入群集还是形成自己的群集。