【问题标题】:Cluster markers in Google Maps Android v2Google Maps Android v2 中的集群标记
【发布时间】:2013-03-07 20:56:20
【问题描述】:

我必须通过 Google Maps Android v2 API 在 GoogleMap 上放置不同的标记。问题是多个标记被设置到相同的位置(纬度/经度),所以用户只能看到“最新”的标记。

是否有可能(在最好的情况下:图书馆)聚集来自同一区域的不同标记(相对于缩放级别)?

我已经阅读了 MarkerClusterer,但这是为 JavaScript API 设计的。

【问题讨论】:

    标签: android google-maps-android-api-2 markerclusterer


    【解决方案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】:

      您需要按照以下步骤操作:

      1. 我们需要实现/安装Google Maps Android API utility library

      Android 工作室/Gradle:

      dependencies {
          compile 'com.google.maps.android:android-maps-utils:0.3+'
      }
      
      1. 其次你需要从谷歌阅读官方文档 -> Google Maps Android Marker Clustering Utility

      添加一个简单的标记聚类器

      按照以下步骤创建一个包含十个标记的简单集群。这 结果看起来像这样,尽管标记的数量 显示/群集将根据缩放级别而变化:

      以下是所需步骤的摘要:

      1. 实现ClusterItem 以表示地图上的标记。集群项将标记的位置作为 LatLng 对象返回。
      2. 添加新的ClusterManager 以根据缩放级别对集群项(标记)进行分组。
      3. 将地图的OnCameraChangeListener() 设置为ClusterManager,因为ClusterManager 实现了监听器。
      4. 如果要添加特定功能以响应标记单击事件,请将地图的 OnMarkerClickListener() 设置为 ClusterManager,因为 ClusterManager 实现了侦听器。
      5. 将标记输入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 个标记。

        【讨论】:

          猜你喜欢
          • 2013-06-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-28
          • 2016-09-09
          • 2013-03-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多