Google Maps Android v2 中的集群标记
Posted
技术标签:
【中文标题】Google Maps Android v2 中的集群标记【英文标题】:Cluster markers in Google Maps Android v2 【发布时间】:2013-03-07 20:56:20 【问题描述】:我必须通过 Google Maps android v2 API 在 GoogleMap 上放置不同的标记。问题是多个标记被设置到相同的位置(纬度/经度),所以用户只能看到“最新”的标记。
是否有可能(在最好的情况下:图书馆)聚集来自同一区域的不同标记(相对于缩放级别)?
我已经阅读了 MarkerClusterer,但这是为 javascript API 设计的。
【问题讨论】:
【参考方案1】:Google 在其 Google Maps Android API 实用程序库中提供了一个实用程序来执行此操作:
https://developers.google.com/maps/documentation/android/utility/marker-clustering
来源:
https://github.com/googlemaps/android-maps-utils
【讨论】:
这适用于谷歌地图 v1。正确的?我们不能再将 v1 用于新应用了。 @cmcromance 适用于地图 v2。为什么你认为它适用于 v1? 当我访问您编写的 dev-google 页面时。然后我单击“获取 API 密钥”链接。他们说“v1 密钥不可用”所以我认为这些信息仅与 v1 有关。所以,我可以将此解决方案调整为我的 v2,对吗?我会试试看。感谢您的信息。 有时谷歌图书馆将集群标记放置在错误的位置 @Bytecode 您是否在github.com/googlemaps/android-maps-utils/issues?state=open 报告了此问题?【参考方案2】:我还没有机会尝试这个,但看起来很有希望:
http://code.google.com/p/android-maps-extensions/
来自this post
这是另一个库,其中包含一些非常酷的集群动画:
https://github.com/twotoasters/clusterkraf
【讨论】:
是的,我已将链接添加到您的帖子中。正如我所说,我还没有时间尝试,但我很快就会尝试。到时候我会告诉你的。 所有这些聚类解决方案(例如地图扩展和 Clusterkraf)的问题在于它们没有考虑原始问题解决的主要问题 - 具有完全相同坐标的标记。想象一张地图显示电影。一个电影院可能正在播放 10 部电影。那个电影院有一个地址,可以对一个坐标进行地理编码。对于所有占据完全相同位置的 10 个标记,聚类将无济于事。这 10 个标记的扩展蜘蛛效果将是完美的。这已在 Maps v3 中仅使用 JS 解决。 @ToddPainton 没有什么可以阻止您将这种效果添加到这些库中。单击群集后,我创建了一个PoC in AME 以显示未群集的Marker
s。见the demo app。【参考方案3】:
对于正在寻找实现自己的集群代码的其他人。请快速检查我的聚类算法,效果非常好。
我查看了各种库,发现它们非常复杂,一个词都看不懂,所以我决定制作自己的聚类算法。这是我的java代码。
static int OFFSET = 268435456;
static double RADIUS = 85445659.4471;
static double pi = 3.1444;
public static double lonToX(double lon)
return Math.round(OFFSET + RADIUS * lon * pi / 180);
public static double latToY(double lat)
return Math.round(OFFSET
- RADIUS
* Math.log((1 + Math.sin(lat * pi / 180))
/ (1 - Math.sin(lat * pi / 180))) / 2);
public static int pixelDistance(double lat1, double lon1, double lat2,
double lon2, int zoom)
double x1 = lonToX(lon1);
double y1 = latToY(lat1);
double x2 = lonToX(lon2);
double y2 = latToY(lat2);
return (int) (Math
.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2))) >> (21 - zoom);
static ArrayList<Cluster> cluster(ArrayList<Marker> markers, int zoom)
ArrayList<Cluster> clusterList = new ArrayList<Cluster>();
ArrayList<Marker> originalListCopy = new ArrayList<Marker>();
for (Marker marker : markers)
originalListCopy.add(marker);
/* Loop until all markers have been compared. */
for (int i = 0; i < originalListCopy.size();)
/* Compare against all markers which are left. */
ArrayList<Marker> markerList = new ArrayList<Marker>();
for (int j = i + 1; j < markers.size();)
int pixelDistance = pixelDistance(markers.get(i).getLatitude(),
markers.get(i).getLongitude(), markers.get(j)
.getLatitude(), markers.get(j).getLongitude(),
zoom);
if (pixelDistance < 40)
markerList.add(markers.get(j));
markers.remove(j);
originalListCopy.remove(j);
j = i + 1;
else
j++;
if (markerList.size() > 0)
markerList.add(markers.get(i));
Cluster cluster = new Cluster(clusterList.size(), markerList,
markerList.size() + 1, originalListCopy.get(i)
.getLatitude(), originalListCopy.get(i)
.getLongitude());
clusterList.add(cluster);
originalListCopy.remove(i);
markers.remove(i);
i = 0;
else
i++;
/* If a marker has been added to cluster, add also the one */
/* we were comparing to and remove the original from array. */
return clusterList;
Just pass in your array list here containing latitude and longitude then to display clusters. Here goes the function.
@Override
public void onTaskCompleted(ArrayList<FlatDetails> flatDetailsList)
LatLngBounds.Builder builder = new LatLngBounds.Builder();
originalListCopy = new ArrayList<FlatDetails>();
ArrayList<Marker> markersList = new ArrayList<Marker>();
for (FlatDetails detailList : flatDetailsList)
markersList.add(new Marker(detailList.getLatitude(), detailList
.getLongitude(), detailList.getApartmentTypeString()));
originalListCopy.add(detailList);
builder.include(new LatLng(detailList.getLatitude(), detailList
.getLongitude()));
LatLngBounds bounds = builder.build();
int padding = 0; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
googleMap.moveCamera(cu);
ArrayList<Cluster> clusterList = Utils.cluster(markersList,
(int) googleMap.getCameraPosition().zoom);
// Removes all markers, overlays, and polylines from the map.
googleMap.clear();
// Zoom in, animating the camera.
googleMap.animateCamera(CameraUpdateFactory.zoomTo(previousZoomLevel),
2000, null);
CircleOptions circleOptions = new CircleOptions().center(point) //
// setcenter
.radius(3000) // set radius in meters
.fillColor(Color.TRANSPARENT) // default
.strokeColor(Color.BLUE).strokeWidth(5);
googleMap.addCircle(circleOptions);
for (Marker detail : markersList)
if (detail.getBhkTypeString().equalsIgnoreCase("1 BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk1)));
else if (detail.getBhkTypeString().equalsIgnoreCase("2 BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_2)));
else if (detail.getBhkTypeString().equalsIgnoreCase("3 BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_3)));
else if (detail.getBhkTypeString().equalsIgnoreCase("2.5 BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk2)));
else if (detail.getBhkTypeString().equalsIgnoreCase("4 BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_4)));
else if (detail.getBhkTypeString().equalsIgnoreCase("5 BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk5)));
else if (detail.getBhkTypeString().equalsIgnoreCase("5+ BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_5)));
else if (detail.getBhkTypeString().equalsIgnoreCase("2 BHK"))
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_2)));
for (Cluster cluster : clusterList)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.cluster_marker, options);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.white));
paint.setTextSize(30);
canvas.drawText(String.valueOf(cluster.getMarkerList().size()), 10,
40, paint);
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(cluster.getClusterLatitude(), cluster
.getClusterLongitude()))
.snippet(String.valueOf(cluster.getMarkerList().size()))
.title("Cluster")
.icon(BitmapDescriptorFactory.fromBitmap(bitmap)));
【讨论】:
【参考方案4】:我来晚了,但是 Clusterkraf 是一个很棒的库,看看吧:
https://github.com/twotoasters/clusterkraf
【讨论】:
哇,我之前用过上面的android map extensins,这个库看起来更好,标记也更好。 这看起来很不错。【参考方案5】:您需要按照以下步骤操作:
-
我们需要实现/安装Google Maps Android API utility library
Android 工作室/Gradle:
dependencies
compile 'com.google.maps.android:android-maps-utils:0.3+'
-
其次你需要从谷歌阅读官方文档 -> Google Maps Android Marker Clustering Utility
添加一个简单的标记聚类器
按照以下步骤创建一个包含十个标记的简单集群。这 结果看起来像这样,尽管标记的数量 显示/群集将根据缩放级别而变化:
以下是所需步骤的摘要:
-
实现
ClusterItem
以表示地图上的标记。集群项将标记的位置作为 LatLng 对象返回。
添加新的ClusterManager
以根据缩放级别对集群项(标记)进行分组。
将地图的OnCameraChangeListener()
设置为ClusterManager
,因为ClusterManager
实现了监听器。
如果要添加特定功能以响应标记单击事件,请将地图的 OnMarkerClickListener()
设置为 ClusterManager
,因为 ClusterManager
实现了侦听器。
将标记输入ClusterManager
。
查看更详细的步骤:要创建包含十个标记的简单集群,首先创建一个实现 ClusterItem
的 MyItem
类。
public class MyItem implements ClusterItem
private final LatLng mPosition;
public MyItem(double lat, double lng)
mPosition = new LatLng(lat, lng);
@Override
public LatLng getPosition()
return mPosition;
在您的地图活动中,添加ClusterManager
并将集群项目提供给它。注意类型参数<MyItem>
,它将ClusterManager 声明为MyItem
类型。
private void setUpClusterer()
// Declare a variable for the cluster manager.
private ClusterManager<MyItem> mClusterManager;
// Position the map.
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10));
// Initialize the manager with the context and the map.
// (Activity extends context, so we can pass 'this' in the constructor.)
mClusterManager = new ClusterManager<MyItem>(this, getMap());
// Point the map's listeners at the listeners implemented by the cluster
// manager.
getMap().setOnCameraChangeListener(mClusterManager);
getMap().setOnMarkerClickListener(mClusterManager);
// Add cluster items (markers) to the cluster manager.
addItems();
private void addItems()
// Set some lat/lng coordinates to start with.
double lat = 51.5145160;
double lng = -0.1270060;
// Add ten cluster items in close proximity, for purposes of this example.
for (int i = 0; i < 10; i++)
double offset = i / 60d;
lat = lat + offset;
lng = lng + offset;
MyItem offsetItem = new MyItem(lat, lng);
mClusterManager.addItem(offsetItem);
演示
https://www.youtube.com/watch?v=5ZnVraO1mT4
Github
如果您需要更多信息/已完成的项目,请访问:https://github.com/dimitardanailov/googlemapsclustermarkers
如果您有更多问题,请与我联系。
【讨论】:
我不太明白。我也必须在地图上添加标记吗? @qwertz 我不明白你的问题,但如果你不想要集群,你可以向谷歌地图添加标记。如果你想拥有集群标记,你需要使用ClusterManager
@d.danailov,我在 android studio 中的 .addItems(offsetItem) 上遇到错误,它说我将 offsetitem 转换为 Collection谁在使用 Clusterkraf 时遇到了问题(我无法让它工作!)以及谁不能使用 android-maps-extension 因为它附带Play Services 库的自定义构建,我想推荐这个其他库,它很小,写得很好并且开箱即用:
集群:https://github.com/mrmans0n/clusterer
花了一上午的时间尝试使用其他两个库后,这个库拯救了我的一天!
【讨论】:
你说 AME 附带一个自定义构建的 GPServices 是什么意思? @MaciejGórski AME 拥有自己的 Google Maps API 实现,因此它带有自己的 google-play-services.jar。我在引用原始 Google Play 服务库以获取推送通知的项目中引用 AME 时遇到了麻烦,例如... AME 使用未经修改的 google-play-services.jar。如果您使用其他服务,只需删除 google-play-services_lib 项目并使用 android-maps-extensions 作为替代。我想如果有一个图书馆以同样的方式工作,那就不好了。用于游戏 API,但我正在努力将其解耦。 我给了AME和Clusterer工具的机会,必须承认Clusterer一目了然,几乎没有设置时间,作者使用与我相同的逻辑进行碰撞检测(像素范围距离 )。只有 3 个没有依赖关系的类,我将对其进行更多调整以适应我的用例并获得高性能。 @MaciejGórski 您在 AME 方面也做得很好,但是如果您决定使用工具包方法而不是扩展 Map,那么您的库将最适合我,但是除了船上的集群之外,您还有很多其他有趣的东西,动态集群也是必须的!【参考方案7】:如果您只获得最新的标记,那么您需要对所有标记进行聚类
> private ClusterManager<MyItem> mClusterManager;
添加项目后
mClusterManager.addItem("your item");
mClusterManager.cluster();
【讨论】:
【参考方案8】:对于那些在 Google Maps Android API 实用程序库的性能方面遇到问题的人,我们创建了自己的快速聚类库:https://github.com/sharewire/google-maps-clustering。它可以轻松处理地图上的 20000 个标记。
【讨论】:
以上是关于Google Maps Android v2 中的集群标记的主要内容,如果未能解决你的问题,请参考以下文章
谷歌地图 V2 中的错误 java.lang.ClassNotFoundException: com.google.android.gms.maps.MapFragment
Android Google Maps v2 从资产中的图像加载 OverlayTile
在 Google Maps Android SDK (v2) 中更改建筑物颜色
Android - Google maps V2 - 计算当前不在视野中的标记是屏幕的北、东、南还是西
Android studio v0.3.2, gradle, google maps v2.0 找不到类“com.google.android.gms.maps.MapFragment