百度地图SDK Android版开发 9 覆盖物示例3批量点、海量点、点聚合

前言

前文介绍了如何在地图上添加一个Overlay,本文重点介绍如何批量添加和删除海量点点聚合的功能。

  • 批量操作:一次性向地图上大批量添加Overlay的接口和一次性清除地图上的所有覆盖物(Overlay对象和infoWindow)的接口。
  • 海量点:支持海量点图层绘制,用于批量展现坐标点数据,并支持点击事件。
  • 点聚合:支持通过缩小地图层级,将定义范围内的大量标注点聚合显示成一个标注点。

相关的类和接口

批量添加和删除

百度地图BaiduMap地图类除了提供添加一个覆盖物的接口外,还提供了批量添加和删除的接口:

  • addOverlay接口可以向地图添加点、线、面、弧、圆和文本覆盖物。
  • addOverlays接口可以一次性向地图上添加大批量的点、线、面等覆盖物。
  • clear接口则一次性清除地图上的所有覆盖物(Overlay对象和infoWindow)。
  • setOnMarkerClickListenerremoveMarkerClickListener接口可以添加和移除Marker单击事件。
类型 方法 说明
Overlay addOverlay(OverlayOptions options) 向地图添加一个 Overlay
List< Overlay > addOverlays(List< OverlayOptions > options) 向地图添加多个 Overlay
void clear() 清空地图所有的 Overlay 覆盖物以及 InfoWindow
void setOnMarkerClickListener(BaiduMap.OnMarkerClickListener listener) 设置地图 Marker 覆盖物点击事件监听者
自3.4.0版本起可设置多个监听对象,
停止监听时调用removeMarkerClickListener移除监听对象
void removeMarkerClickListener(BaiduMap.OnMarkerClickListener listener) 移除一个地图 Marker 覆盖物点击事件监听者

海量点

MultiPoint海量点覆盖物,通过MultiPointOption类来设置海量点图层的属性。

  • 部分覆盖物类关系图:
classDiagram
    Marker --|> Overlay
    MultiPoint --|> Overlay
    Polyline --|> Overlay
    Polygon --|> Overlay
    Overlay <|-- Arc
    Overlay <|-- Circle
    Overlay <|-- Text
  • 部分覆盖物选项类关系图:
classDiagram
    MarkerOptions --|> OverlayOptions
    MultiPointOption --|> OverlayOptions
    PolylineOptions --|> OverlayOptions
    PolygonOptions --|> OverlayOptions
    OverlayOptions <|-- ArcOptions
    OverlayOptions <|-- CircleOptions
    OverlayOptions <|-- TextOptions

BaiduMap类

百度地图SDKBaiduMap地图类与海量点相关的接口:

  • addOverlay接口可以向地图添加海量点。

  • setOnMultiPointClickListener接口设置海量点单击事件

类型 方法 说明
void setOnMultiPointClickListener(BaiduMap.OnMultiPointClickListener listener) 设置地图 MultiPoint 覆盖物点击事件监听者

BaiduMap.OnMultiPointClickListener接口

// 地图MultiPoint覆盖物点击事件监听接口
public interface OnMultiPointClickListener {
    /**
     * 地图 MultiPoint 覆盖物点击事件监听函数
     * @param point 被点击的 MultiPoint
     * @param item 被点击的 MultiPointItem
     * @return
     */
    boolean onMultiPointClick(MultiPoint point, MultiPointItem item);
}

MultiPointOption 海量点选项

  • getter
类型 方法 说明
BitmapDescriptor getIcon() 获取 MultiPoint 覆盖物图标
List< MultiPointItem > getMultiPointItems() 获取 MultiPoint 覆盖物数据集合
float getAnchorX() 获取 MultiPoint 覆盖物水平方向锚点比例
float getAnchorY() 获取 MultiPoint 覆盖物垂直方向锚点比例
int getPointSizeHeight() 获取 MultiPoint 覆盖物纹理点的高度
int getPointSizeWidth() 获取 MultiPoint 覆盖物纹理点的宽度
boolean isVisible() 获取MultiPoint 覆盖物可见性
  • setter
类型 方法 说明
MultiPointOption setIcon(BitmapDescriptor icon) 设置 MultiPoint 覆盖物的图标 (必填)
MultiPointOption setMultiPointItems(List< MultiPointItem > multiPointItems) 添加海量点数据集合(必填)
MultiPointOption setPointSize(int pointSizeWidth, int pointSizeHeight) 纹理渲染大小,默认为icon图片大小
MultiPointOption setAnchor(float anchorX, float anchorY) 设置 MultiPoint 覆盖物的锚点比例,
默认(0.5f, 0.5f)水平居中,垂直下对齐
MultiPointOption setClickable(boolean isClickable) 设置MultiPoint是否可点击
MultiPointOption visible(boolean visible) 设置MultiPoint 覆盖物可见性,默认 true 显示

MultiPointItem 海量点单个点对象

类型 方法 说明
MultiPointItem(LatLng point) MultiPoint覆盖物单个点构造函数
LatLng getPoint() 获取MultiPoint覆盖物单个点经纬度
String getTitle() 获取MultiPoint覆盖物单个点标题
void setTitle(String title) 设置MultiPoint覆盖物单个点标题

点聚合

百度地图SDK点聚合部分已开放源码,位于Democlusterutil包中,可以下载后自行修改使用。

相关代码文件清单:

com/baidu
└── mapapi
    └── clusterutil
        ├── MarkerManager.java
        ├── clustering
        │   ├── Cluster.java
        │   ├── ClusterItem.java
        │   ├── ClusterManager.java
        │   ├── algo
        │   │   ├── Algorithm.java
        │   │   ├── NonHierarchicalDistanceBasedAlgorithm.java
        │   │   ├── PreCachingAlgorithmDecorator.java
        │   │   └── StaticCluster.java
        │   └── view
        │       ├── ClusterRenderer.java
        │       └── DefaultClusterRenderer.java
        ├── projection
        │   ├── Bounds.java
        │   ├── Point.java
        │   └── SphericalMercatorProjection.java
        ├── quadtree
        │   └── PointQuadTree.java
        └── ui
            ├── IconGenerator.java
            ├── RotationLayout.java
            └── SquareTextView.java

相关资源文件清单:

└── res
    ├── layout
    │   └── text_bubble.xml
    ├── raw
    │   └── locations.json
    └── values
        └── styles.xml

示例

界面布局

1-布局.png
  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MapMarkersActivity">

    <com.baidu.mapapi.map.MapView
        android:id="@+id/bmapView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:clickable="true"
        app:layout_constraintBottom_toTopOf="@id/bottomView"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/bottomView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bmapView">

        <RadioGroup
            android:id="@+id/RadioGroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/background_dark"
            android:gravity="center_horizontal"
            android:orientation="horizontal"
            android:paddingHorizontal="10dp">

            <RadioButton
                android:id="@+id/bulk"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:checked="true"
                android:onClick="setMarkerFlag"
                android:text="批量点"
                android:textColor="@color/white"
                android:textStyle="bold" />

            <RadioButton
                android:id="@+id/multiPoint"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:onClick="setMarkerFlag"
                android:text="海量点"
                android:textColor="@color/white"
                android:textStyle="bold" />

            <RadioButton
                android:id="@+id/cluster"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:onClick="setMarkerFlag"
                android:text="点聚合"
                android:textColor="@color/white"
                android:textStyle="bold" />

            <RadioButton
                android:id="@+id/clear"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:onClick="setMarkerFlag"
                android:text="清除"
                android:textColor="@color/white"
                android:textStyle="bold" />
        </RadioGroup>

    </androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>

MapMarks类

  • 以下是MapMarks部分代码。

常量

public static final String BULK = "Bulk"; // 批量点
public static final String MULTI_POINT = "MultiPoint"; // 海量点
public static final String CLUSTER = "Cluster"; // 点聚合

成员变量

// 覆盖物列表
List<Overlay> overlays = new ArrayList<>();
// 海量点选中高亮
Marker selectedMarker;
// 点聚合管理类
ClusterManager<MyClusterItem> clusterManager;

// 选中的状态
List<String> selectedFlags = new ArrayList<>();

// 气泡图标
ArrayList<BitmapDescriptor> bitmaps = new ArrayList<>();
// 点图标
BitmapDescriptor dotBitmap;

// Marker点击事件
BaiduMap.OnMarkerClickListener bulkMarkerClickListener;
// 海量点点击事件
BaiduMap.OnMultiPointClickListener multiPointClickListener;

初始化

  • 初始化点聚合管理类
// 初始化点聚合管理类
clusterManager = new ClusterManager<>(context, map);
// 设置地图监听,当地图状态发生改变时,进行点聚合运算
map.setOnMapStatusChangeListener(clusterManager);
selectedFlags.add(BULK);

int[] drawableIds = BubbleIcons.Number;
for (int drawableId : drawableIds) {
    BitmapDescriptor bitmap = BitmapDescriptorFactory.fromResource(drawableId);
    bitmaps.add(bitmap);
}
dotBitmap = BitmapDescriptorFactory.fromResource(CircleIcons.BLueDot[0]);

创建覆盖物

public void addMarkers() {
    if (selectedFlags.isEmpty())
        return;

    for (String flag : selectedFlags) {
        switch (flag) {
        case BULK:
            addBulk();
            break;
        case MULTI_POINT:
            addMultiPoint();
            break;
        case CLUSTER:
            addCluster();
            break;
        }
    }
}

批量点

  • 创建OverlayOptions的集合
  • 在地图上批量添加覆盖物
  • 设置Marker单击事件
private void addBulk() {
    // 构造大量坐标数据
    List<LatLng> points = new ArrayList<>();
    points.add(new LatLng(39.97923, 116.357428));
    points.add(new LatLng(39.94923, 116.397428));
    points.add(new LatLng(39.97923, 116.437428));
    points.add(new LatLng(39.92353, 116.490705));
    points.add(new LatLng(40.023537, 116.289429));
    points.add(new LatLng(40.022211, 116.406137));

    // 创建OverlayOptions的集合
    List<OverlayOptions> optionsList = new ArrayList<>();
    for (int i = 0; i < points.size(); ++i) {
        // 创建OverlayOptions属性
        OverlayOptions option = new MarkerOptions()
                .position(points.get(i))
                .icon(bitmaps.get(i));
        // 将OverlayOptions添加到list
        optionsList.add(option);
    }

    // 在地图上批量添加
    List<Overlay> newOverlays = map.addOverlays(optionsList);
    overlays.addAll(newOverlays);

    // Marker单击事件
    if (bulkMarkerClickListener == null) {
        bulkMarkerClickListener = new BaiduMap.OnMarkerClickListener() {
            // marker被点击时回调的方法
            // 若响应点击事件,返回true,否则返回false
            // 默认返回false
            @Override
            public boolean onMarkerClick(Marker marker) {
                showToast("点击 marker");
                return true;
            }
        };
    }
    map.setOnMarkerClickListener(bulkMarkerClickListener);
}

海量点

  • 加载大量点数据(参考自官网示例Demo)
  • 设置海量点数据
  • 添加海量点覆盖物
  • 设置海量点单击事件
private void addMultiPoint() {
    // 加载大量点(参考官网示例Demo)
    List<LatLng> points = getLocations();

    ArrayList<MultiPointItem> items = new ArrayList<>();
    for (int i = 0; i < points.size(); i++) {
        // 创建覆盖物单个点对象
        MultiPointItem item = new MultiPointItem(points.get(i));
        items.add(item);
    }
    // 设置海量点数据
    MultiPointOption option = new MultiPointOption();
    option.setMultiPointItems(items);
    option.setIcon(dotBitmap);

    // 添加海量点覆盖物
    MultiPoint multiPoint = (MultiPoint) map.addOverlay(option);
    overlays.add(multiPoint);

    // 海量点单击事件
    if (multiPointClickListener == null) {
        multiPointClickListener = new BaiduMap.OnMultiPointClickListener() {
            @Override
            public boolean onMultiPointClick(MultiPoint point, MultiPointItem item) {
                if (selectedMarker != null)
                    selectedMarker.remove();

                MarkerOptions markerOptions = new MarkerOptions();
                markerOptions.position(item.getPoint());
                markerOptions.icon(bitmaps.get(0));
                selectedMarker = (Marker) map.addOverlay(markerOptions);
                return true;
            }
        };
        map.setOnMultiPointClickListener(multiPointClickListener);
    }
}

点聚合

private void addCluster() {
    List<LatLng> points = new ArrayList<>();
    points.add(new LatLng(39.963175, 116.400244));
    points.add(new LatLng(39.942821, 116.369199));
    points.add(new LatLng(39.939723, 116.425541));
    points.add(new LatLng(39.906965, 116.401394));
    points.add(new LatLng(39.956965, 116.331394));
    points.add(new LatLng(39.886965, 116.441394));
    points.add(new LatLng(39.996965, 116.411394));

    List<MyClusterItem> items = new ArrayList<>();
    for (int i = 0; i < points.size(); i++) {
        // 创建覆盖物单个点对象
        items.add(new MyClusterItem(points.get(i), bitmaps.get(i)));
    }

    clusterManager.addItems(items);
    clusterManager.cluster();

    // 点聚合单击事件
    map.setOnMarkerClickListener(clusterManager);
    clusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener<MyClusterItem>() {
        @Override
        public boolean onClusterClick(Cluster<MyClusterItem> cluster) {
            showToast("有" + cluster.getSize() + "个点");
            return true;
        }
    });
    clusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<MyClusterItem>() {
        @Override
        public boolean onClusterItemClick(MyClusterItem item) {
            showToast("点击单个Item");
            return true;
        }
    });
}
  • 实现ClusterItem
// ClusterItem接口的实现类
public static class MyClusterItem implements ClusterItem {
    LatLng position;
    BitmapDescriptor bitmap;

    public MyClusterItem(LatLng position, BitmapDescriptor bitmap) {
        this.position = position;
        this.bitmap = bitmap;
    }

    @Override
    public LatLng getPosition() {
        return position;
    }

    @Override
    public BitmapDescriptor getBitmapDescriptor() {
        return bitmap;
    }
}

移除覆盖物

public void removeOverlay() {
    // 批量删除添加的多个 Overlay
    //map.removeOverLays(overlays);

    // 清空地图所有的 Overlay 覆盖物以及 InfoWindow
    // map.clear();

    // 删除覆盖物
    for (Overlay overlay : overlays) {
        overlay.remove();
    }
    overlays.clear();

    if (selectedMarker != null)
        selectedMarker.remove();
    selectedMarker = null;

    // 删除点聚合
    clusterManager.clearItems();
    clusterManager.cluster();

    // 避免批量与点聚合单击事件冲突
    map.removeMarkerClickListener(bulkMarkerClickListener);
    map.removeMarkerClickListener(clusterManager);
}

设置属性

public void setFlags(String flag) {
    selectedFlags.clear();
    if (flag != null)
        selectedFlags.add(flag);

    removeOverlay();
    addMarkers();
}

加载地图和移除地图

public void onMapLoaded() {
    addMarkers();
}

public void onMapDestroy() {
    removeOverlay();

    for (BitmapDescriptor bitmap : bitmaps) {
        bitmap.recycle();
    }
    bitmaps = null;

    if (dotBitmap != null)
        dotBitmap.recycle();
}

MapMarkersActivity类

  • 以下是MapMarkersActivity类部分代码

控件响应事件

public void setMarkerFlag(View view) {
    boolean checked = ((RadioButton) view).isChecked();
    if (!checked)
        return;

    int id = view.getId();
    String flag;
    if (id == R.id.bulk)
        flag = MapMarkers.BULK;
    else if (id == R.id.multiPoint)
        flag = MapMarkers.MULTI_POINT;
    else if (id == R.id.cluster)
        flag = MapMarkers.CLUSTER;
    else if (id == R.id.clear)
        flag = null;
    else
        return;

    mapMarkers.setFlags(flag);
}

运行效果图

批量点 海量点 点聚合
1-批量.png
2-海量点1.png
3-点聚合1.png
2-海量点2.png
3-点聚合2.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容