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 以显示未群集的Markers。见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

查看更详细的步骤:要创建包含十个标记的简单集群,首先创建一个实现 ClusterItemMyItem 类。

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 并将集群项目提供给它。注意类型参数&lt;MyItem&gt;,它将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。你能告诉我错误是什么吗? @vivek 我想为响应缓慢而道歉,但我不在办公室。你能分享有错误的截图吗?【参考方案6】:

谁在使用 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

Google maps v2 android,信息窗口在点击时没有以默认突出显示颜色突出显示?