百度地图 JavaScript API GL + mapV GL + bmap-draw 实现地图聚合点以及聚合点框选功能开发

百度地图 JavaScript API GL 地图聚合点、地图框选功能开发

参考地址: 百度地图的开发者频道
注意:使用前要先登录百度账号申请一个浏览器端的 AK,详见 AK 申请

创建地图以及初始化交通流量图层

引用 GL 版本的 JS API:

<script src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=你的密钥"></script>

初始化地图的 JavaScript 代码:

const map = new BMapGL.Map('map', {
  // minZoom: 11,
  // maxZoom: 20,
});
map.centerAndZoom('武汉', 12, {
  callback: () => {
    // 一定要在回调中开启交通流量图层
    map.setTrafficOn();
  },
});
map.enableScrollWheelZoom(true);

BMapGL.Map

BMapGL.Map 类是地图API的核心类,用来实例化一个地图。请注意 WebGL 版本的地图 API 的命名空间是BMapGL。

构造函数描述
Map(container: String | HTMLElement, opts: MapOptions )在指定的容器内创建地图实例,之后需要调用Map.centerAndZoom()方法对地图进行初始化,未进行初始化的地图将不能进行任何操作。

MapOptions

属性类型描述
minZoomNumber地图允许展示的最小级别
maxZoomNumber地图允许展示的最大级别
mapTypeMapTypeId地图类型,默认为BMAP_NORMAL_MAP
enableAutoResizeBoolean开启自动适应地图容器变化,默认启用
enableTiltBoolean是否允许地图倾斜
enableRotateBoolean是否允许地图旋转
enableRotateGesturesBoolean是否允许通过手势旋转地图。
enableTiltGesturesBoolean是否允许通过手势倾斜地图。
overlayTopBoolean覆盖物是否显示在文字上面,默认false
fixCenterWhenPinchBoolean手势缩放是否固定中心点,默认不固定,由手指中心点决定
displayOptionsObject配置地图显示元素。该参数详细信息请参见 setDisplayOptions方法

map.centerAndZoom() 对地图进行初始化

调用 new BMapGL.Map 之后需要使用 map.centerAndZoom() 对地图进行初始化,否则不能对地图进行任何操作。

入参:
centerAndZoom(center: Point , zoom: Number)

描述:
初始化地图,如果 center 的类型为 Point 时,zoom 必须赋值,范围 3-19 级,若调用高清底图(针对移动端开发)时,zoom 可赋值范围为 3-18 级。如果 center 类型为字符串时,比如“北京”,zoom可以忽略,地图将自动根据 center 适配最佳 zoom 级别。

设置地图元素

通过 map.setDisplayOptions({option: displayOptions }) 可以设置地图元素显隐。

displayOptions

属性类型描述
poiboolean是否显示 POI 信息。注意:poipoiTextpoiIcon 均用来配置 POI 显示。当 poitrue 时,可配置另外两个选项;如果为 false,则另外两个选项不再生效。
poiTextboolean是否显示POI文字信息
poiIconboolean是否显示POI的Icon
overlayboolean是否显示覆盖物
buildingboolean是否显示3D建筑物(仅支持 WebGL 方式渲染的地图)
indoorboolean是否显示室内图(仅支持 WebGL 方式渲染的地图)
streetboolean是否显示路网(只对卫星图和地球模式有效)
skyColorArray配置天空的颜色,数组中首个元素表示地面颜色,第二个元素表示天空颜色。从而形成渐变,支持只传入一个元素。

点覆盖物 (Marker)

通过 Marker 构造函数可以根据经纬度在地图上生成各类的点覆盖物。

构造函数Marker(point: Point , opts: MarkerOptions )

使用自定义图片生成指定大小的 Marker,并且设置名称(label)

示例:生成一个 32 * 47 像素的图片 Marker,并且将地图的中心点定位到点位的经纬度上。

map.centerAndZoom('武汉', 12, {
  callback: () => {
    // 构造一个包含经纬度和点位名称的数据
    const data = {
      lng: 114.32947483740818,
      lat: 30.62295757673447,
      name: '汉口江滩三期',
    };

    // 提前定义 Marker 的宽高
    const width = 32;
    const height = 47;

    // 构造一个 Point 点位
    const point = new BMapGL.Point(data.lng, data.lat);

    // 构造一个 Marker
    const marker = new BMapGL.Marker(point, {
      icon: new BMapGL.Icon( // icon 接收一个 Icon 对象,通过 BMapGL.Icon 构造函数生成
        '/marker-red.png', // 图标的绝对路径
        new BMapGL.Size(width, height), // 图标的尺寸,通过 BMapGL.Size 构造函数生成
        // Marker 的配置项,参考:
        // https://mapopen-pub-jsapi.bj.bcebos.com/jsapi/reference/jsapi_webgl_1_0.html#a3b2
        {
          // anchor 可以理解为要将图片的哪个坐标点位设置到对应的经纬度上,左上角为 (0,0) ,右下为正
          // new BMapGL.Size(width / 2, height) 的意思就是将图片底边的中心点定到对应的经纬度上去
          anchor: new BMapGL.Size(width / 2, height),
          imageOffset: new BMapGL.Size(0, 0),
          imageSize: new BMapGL.Size(width, height),
        }
      ),
    });

    // 设置 marker 的 z-index 层级
    marker.setZIndex(2);

    // 构造 label,参考:
    // https://mapopen-pub-jsapi.bj.bcebos.com/jsapi/reference/jsapi_webgl_1_0.html#a3b8
    const label = new BMapGL.Label(
      // label 的 html
      `<span title="${data.name}">${data.name}</span>`, 
      {
        position: point,
        // label 的偏移, (0,10) 代表向下偏移 10 像素
        offset: new BMapGL.Size(0, 10),
      }
    );
    // 使用 Object 对象描述 label 的样式
    label.setStyle({
      border: 0,
      backgroundColor: 'rgba(0, 14, 17, 0.6)',
      maxWidth: '120px',
      // 使用 css 实现横向灵活居中而不是 offset
      transform: 'translateX(-50%)',
      whiteSpace: 'nowrap',
      overflow: 'hidden',
      textOverflow: 'ellipsis',
      borderRadius: '4px',
      padding: '2px 6px',
      color: '#fff',
      fontSize: '18px',
    });
    // 设置层级
    label.setZIndex(2);
    // 绑定 marker
    marker.setLabel(label);
    // 覆盖物上图
    map.addOverlay(marker);
    // 将地图定位到点位,并且设置层级为18
    // noAnimation 设置为 true 可以取消 setViewport 的动画过程
    map.setViewport(
      {
        center: point,
        zoom: 18,
      },
      {
        noAnimation: true,
      }
    );
  },
});
map.enableScrollWheelZoom(true);

信息窗体 (Infowindow)

通过构造函数生成infowindow对象并设置 HTML 内容:

// 构造 infowindow
const infoWindowContent = `
  <div class="info-content">
    <div class="label">名称:${data.name}</div>
    <div class="label">位置:${data.lng},${data.lat}</div>
  </div>
  `;
const infoWindow = new BMapGL.InfoWindow(infoWindowContent, {
  title: '点位信息',
  width: 360
});
// 点标记添加点击事件
marker.addEventListener('click', () => {
  map.setViewport(
    {
      center: point,
      zoom: 18,
    },
    {
      noAnimation: true,
      callback: () => {
        map.openInfoWindow(infoWindow, point);
        infoWindow.disableCloseOnClick();
      },
    }
  );
});

关于 infowindow 自带的丑箭头以及立体阴影效果

百度地图渲染出的 infowindow 自带一个白色的箭头以及一个向右上方倾斜的阴影,如下图:

自带的infowindow样式
infowindow的默认阴影
如果你不想要它,可以使用 css 隐藏自带箭头的图片元素以及自带的阴影,并自己用 css 写一个箭头,下面给一个简单的例子:

/* 隐藏箭头 */
img[src="http://webmap0.bdimg.com/image/api/iw_tail.png"] {
  display: none;
}
/* 写一个普通箭头 */
.BMap_bubble_pop::after {
  position: absolute;
  top: 100%;
  left: 50%;
  content: '';
  margin-left: -8px;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-top: 8px solid #e4e4e4;
  border-bottom: 8px solid transparent;
}
/* 隐藏阴影 */
.shadow[type='infowindow_shadow'] {
  display: none;
}

聚合点 (Cluster)

BMapGL 中没有关于聚合点的渲染相关的类,那是因为百度将其放在了 MapVGL 中。

聚合点分为点聚合和图标聚合,如果想自定义不同聚合程度下的图标样式,推荐使用图标聚合 IconClusterLayer,代码参考官方示例即可。

热力图(heatmap)

热力图也是热力图分为 热力点热力图,代码参考官方示例即可。

鼠标绘制工具

基于百度地图 JSAPI GL 版本,百度推出了 bmap-draw ,它提供了一系列 API 用于完成鼠标绘制的诸多功能,比如绘制矩形,圆形,多边形等。

绘制(Draw)

绘制类 用于鼠标绘制点线面,支持吸附绘制,是鼠标绘制的基础能力。开启之后鼠标会变成十字准星样式,按照提示操作鼠标即可完成图形的绘制。

选择工具(Select)

bmap-draw 提供了 Select 类用于地图中的 Marker 的框选,以 矩形框选 为例,配置项 type 设置为 DrawingType.DRAWING_RECTANGLE,基于绑定监听 OperateEventType.COMPLETE 事件,实现框选功能。

聚合点的框选

在 bmap-draw 的选择工具代码示例中可以看出选择工具默认支持的是 GeoJSONLayer 图层,也即点(marker)、线(polyline)、面(polygon),看起来似乎无法支持聚合图层的框选,但有时我们就是需要框选出地图上的聚合点,这时就需要使用百度推出的另一个工具 BMapGLLib

BMapGLLib 是基于百度地图 JSAPI GL 版的 JavaScript 开源工具库。拥有一下能力:

  1. 鼠标绘制工具条库 示例

    提供鼠标绘制点、线、面、多边形(矩形、圆)的编辑工具条的开源代码库。且用户可使用JavaScript API对应覆盖物(点、线、面等)类接口对其进行属性(如颜色、线宽等)设置、编辑(如开启线顶点编辑等)等功能。

  2. 测距工具 示例

    百度地图的测距工具类,对外开放。 允许用户在地图上点击完成距离的测量。 使用者可以自定义测距线段的相关样式,例如线宽、颜色、测距结果所用的单位制等等。

  3. 几何运算 示例

    GeoUtils类提供若干几何算法,用来帮助用户判断点与矩形、 圆形、多边形线、多边形面的关系,并提供计算折线长度和多边形的面积的公式。

  4. 视角轨迹动画 示例

    TrackAnimation类提供视角轨迹动画展示效果。

  5. 区域限制 示例

    百度地图浏览区域限制类,对外开放。 允许开发者输入限定浏览的地图区域的Bounds值, 则地图浏览者只能在限定区域内浏览地图。

  6. 自定义信息窗口 顶部示例 底部示例

    百度地图的infoBox。类似于infoWindow,比infoWindow更有灵活性,比如可以定制border,关闭按钮样式等。

  7. 富标注 示例1 示例2

    百度地图的富Marker类,对外开放。 允许用户在自定义丰富的Marker展现样式,并添加点击、双击、拖拽等事件。

  8. 路书 示例

    百度地图路书类,实现Marker沿路线运动,对外开放。 用户可以在地图上自定义轨迹运动,支持暂停/停止功能,并可以自定义路过某个点的图片,文字介绍等。

下面是一个框选聚合点的完整示例:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>百度地图 js api 使用教程</title>

    <script src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=J3N7G7JzrLDHYXqIx7JwJyTUwWOdxtTY"></script>
    <script src="https://unpkg.com/mapvgl/dist/mapvgl.min.js"></script>
    <script src="/bmap-draw.min.js"></script>
    <script src="/GeoUtils.js"></script>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      #map {
        width: 100vw;
        height: 100vh;
      }

      .btn-con {
        position: absolute;
        top: 24px;
        left: 40px;
        z-index: 6;
      }
      .btn {
        width: 90px;
        height: 32px;
        border-radius: 3px;
        background-color: #2985f7;
        color: #fff;
        border-width: 0;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <div class="btn-con">
      <button class="btn" id="openCircle">圆形框选</button>
      <button class="btn" id="openRect">矩形框选</button>
      <button class="btn" id="openPolygon">多边形框选</button>
      <button class="btn" id="close">关闭</button>
      <button class="btn" id="clear">清除</button>
    </div>
    <div id="map"></div>

    <script>
      const map = new BMapGL.Map('map');
      map.centerAndZoom('武汉', 14, {
        callback: () => {
          renderClusterLayer();
          initDrawUtil();
          initEvents();
        },
      });
      map.enableScrollWheelZoom(true);

      // 鼠标绘制工具
      let scene, rectDraw, polygonDraw, circleDraw, activeDraw;

      // 图标聚合图层
      let iconClusterLayer = null;

      // 绑定事件
      function initEvents() {
        document.querySelector('#openCircle').addEventListener('click', openCircle);
        document.querySelector('#openRect').addEventListener('click', openRect);
        document.querySelector('#openPolygon').addEventListener('click', openPolygon);
        document.querySelector('#close').addEventListener('click', close);
        document.querySelector('#clear').addEventListener('click', clear);

        function openCircle() {
          activeDraw = circleDraw;
          circleDraw.open();
        }
        function openRect() {
          activeDraw = rectDraw;
          rectDraw.open();
        }
        function openPolygon() {
          activeDraw = polygonDraw;
          polygonDraw.open();
        }

        function close() {
          activeDraw.close();
        }

        function clear() {
          scene && scene.clearData();
        }
      }

      // 渲染聚合点
      function renderClusterLayer() {
        const data = [
          {
            name: '位置1',
            lng: 114.32074427093403,
            lat: 30.614411917020718,
          },
          {
            name: '位置2',
            lng: 114.31715104889412,
            lat: 30.614038958043224,
          },
          {
            name: '位置3',
            lng: 114.31513884455177,
            lat: 30.60950117426803,
          },
          {
            name: '位置4',
            lng: 114.31844460882849,
            lat: 30.611490366197277,
          },
          {
            name: '位置5',
            lng: 114.32268461083558,
            lat: 30.611490366197277,
          },
        ];

        const geojsonData = [];
        data.forEach((item) => {
          geojsonData.push({
            geometry: {
              type: 'Point',
              coordinates: [item.lng, item.lat],
            },
            properties: {
              extraData: item, // 将数据本身保存到这里后面要用
              icon: 'https://maponline0.bdimg.com/sty/map_icons2x/MapRes/zhongyangjigou.png',
              width: 40,
              height: 40,
            },
          });
        });

        const view = new mapvgl.View({
          map: map,
        });

        iconClusterLayer = new mapvgl.IconClusterLayer({
          textOptions: {
            fontSize: 20,
            format: function (count) {
              return count;
            },
          },
          iconOptions: {
            width: 40,
            height: 40,
          },
          iconExtent: {
            0: 'https://a.amap.com/jsapi_demos/static/images/blue.png',
            5: 'https://a.amap.com/jsapi_demos/static/images/green.png',
            10: 'https://a.amap.com/jsapi_demos/static/images/orange.png',
            15: 'https://a.amap.com/jsapi_demos/static/images/red.png',
            34: 'https://a.amap.com/jsapi_demos/static/images/darkRed.png',
          },
          // enablePicked: true,
          onClick: (e) => {
            if (!e.dataItem) {
              return;
            }
            const { id, children, geometry, properties } = e.dataItem;
            if (children) {
              console.log(children);
              alert('点击了聚合点');
            } else {
              console.log(properties);
              alert('点击了Marker');
            }
          },
        });
        view.addLayer(iconClusterLayer);
        iconClusterLayer.setData(geojsonData);
      }

      // 初始化鼠标绘制工具
      function initDrawUtil() {
        scene = new BMapGLDraw.DrawScene(map, {
          noLimit: true, // 不对框选范围做限制
        });
        const labelOptions = {
          borderRadius: '2px',
          background: '#b5d3fb',
          border: '1px solid #b5d3fb',
          color: '#333',
          fontSize: '12px',
          letterSpacing: '0',
          padding: '5px',
        };
        const baseOpts = {
          fillColor: '#fff',
          strokeColor: '#00ffee',
          strokeWeight: 5,
          strokeOpacity: 1,
          fillOpacity: 0.2,
        };

        rectDraw = new BMapGLDraw.RectDraw(scene, {
          isOpen: false,
          isSeries: false,
          labelOptions,
          baseOpts,
        });
        polygonDraw = new BMapGLDraw.PolygonDraw(scene, {
          isSeries: false,
          isOpen: false,
          labelOptions,
          baseOpts,
        });
        circleDraw = new BMapGLDraw.CircleDraw(scene, {
          isOpen: false,
          isSeries: false,
          labelOptions,
          baseOpts,
        });

        const rectCallback = (e) => {
          if (!e.target.overlay.toGeoJSON) {
            alert('当前jsapi版本不支持圆选');
            return;
          }
          // 获取当前层级下地图中的聚合数据
          const clusterData = iconClusterLayer.getClusterData(map.getBounds(), map.getZoom());
          console.log('clusterData', clusterData);
          const clusterPoints = [];
          clusterData.forEach((v) => {
            const pt = new BMapGL.Point(...v.geometry.coordinates);
            const bds = e.target.overlay.getBounds();
            if (BMapGLLib.GeoUtils.isPointInRect(pt, bds)) {
              clusterPoints.push(v);
            }
          });
          // clusterPoints 是框选到的聚合点和普通 marker 点
          console.log(getClusterInnerMarkers(clusterPoints));
        };

        const polygonCallback = (e) => {
          if (!e.target.overlay.toGeoJSON) {
            alert('当前jsapi版本不支持圆选');
            return;
          }
          // 获取当前层级下地图中的聚合数据
          const clusterData = iconClusterLayer.getClusterData(map.getBounds(), map.getZoom());
          const clusterPoints = [];
          clusterData.forEach((v) => {
            const pt = new BMapGL.Point(...v.geometry.coordinates);
            if (BMapGLLib.GeoUtils.isPointInPolygon(pt, e.target.overlay)) {
              clusterPoints.push(v);
            }
          });
          console.log(getClusterInnerMarkers(clusterPoints));
        };

        const circleCallback = (e) => {
          if (!e.target.overlay.toGeoJSON) {
            alert('当前jsapi版本不支持圆选');
            return;
          }

          const center = e.target.overlay.getCenter();
          const circle = new BMapGL.Circle(center, e.target.overlay.radius); // 圆
          // 获取当前层级下地图中的聚合数据
          const clusterData = iconClusterLayer.getClusterData(map.getBounds(), map.getZoom());
          const clusterPoints = [];
          clusterData.forEach((v) => {
            const pt = new BMapGL.Point(...v.geometry.coordinates);
            if (BMapGLLib.GeoUtils.isPointInCircle(pt, circle)) {
              clusterPoints.push(v);
            }
          });
          console.log(getClusterInnerMarkers(clusterPoints));
        };

        rectDraw.addEventListener(BMapGLDraw.OperateEventType.COMPLETE, rectCallback);
        polygonDraw.addEventListener(BMapGLDraw.OperateEventType.COMPLETE, polygonCallback);
        circleDraw.addEventListener(BMapGLDraw.OperateEventType.COMPLETE, circleCallback);
      }

      // 聚合点的数据提取出来
      function getClusterInnerMarkers(clusterPoints) {
        let clusterMarkers = [];
        clusterPoints.forEach((pt) => {
          if (pt.type === 'Feature') {
            // 聚合点
            clusterMarkers = clusterMarkers.concat(iconClusterLayer.getClusterPoints(pt.id));
          } else {
            clusterMarkers.push(pt);
          }
        });
        return clusterMarkers;
      }
    </script>
  </body>
</html>

其中有几个 API 比较关键,但是没找到具体的文档,只能从源码中获取相关信息:

  1. iconClusterLayer.getClusterData

    该方法的入参为 Bound 和 Zoom,顾名思义是获取地图的聚合图层上对应的 Bound 和 Zoom 上所有的点,包括聚合点和普通点,一般情况下需要对返回值遍历判断类型

  2. iconClusterLayer.getClusterPoints(id)

    通过聚合点的 id 获取聚合点下的所有的普通 marker 点的 geojson 数组。
    该方法的入参为聚合点的 geojson 数据的 id,在上面的 getClusterData 方法返回的聚合点数组中会返回。

  3. BMapGLLib.GeoUtils.isPointInRect

    该方法判断一个点是否在一个矩形内

  4. BMapGLLib.GeoUtils.isPointInPolygon

    该方法判断一个点是否在一个多边形内

  5. BMapGLLib.GeoUtils.isPointInCircle

    该方法判断一个点是否在一个圆形内

over~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值