高德地图实现自定义区域下钻

源码:https://github.com/wforguo/daily-code/blob/master/vue-app/vue2-app/src/views/AmapDrop/index.vue

演示:https://forguo.cn/app/amap-drill.html

一、地图下钻

  • antv区域钻取

https://l7.antv.vision/zh/examples/choropleth/drill#order-drill
antv-under.gif

  • 高德区域钻取

https://lbs.amap.com/demo/amap-ui/demos/amap-ui-districtexplorer/index
gaode-under.gif

上面是关于正常的省市区的一个地图下钻,现成的地图或者组件可以实现,
但是如果遇到自定义的地图层级钻取就哑火了

二、需求

先来看一下最终效果

gaode-under-custom-min.gif

如图,最终我们要实现科室、部门、经销商的三级钻取,类似我们所说的大区下钻;

方案大概有两个,

  • a、每个区域数据都用svg勾勒出来,每次点击通过id及层级去切换
  • b、通过高德地图尺量图去完成;

显然,a方案耗时,不够友好,下面就用高德来实现这个需求;

三、自定义区域

可以用来实现自定义科室,自定义部门以及自定义经销商

高德尺量图形

https://lbs.amap.com/api/jsapi-v2/documentation#polygon

翻阅高德api,发现AMap.Polygon这个api,可以绘制构造多边形对象,通过PolygonOptions指定多边形样式

这里有path这个参数,就可以把自定义的每一级所对应的地区边缘经纬度坐标拿到,用尺量图渲染出来,就得到了一个自定义的区域,也就可以绘制各种想要的异形地图了

image.png

实现

参考示列:https://lbs.amap.com/demo/jsapi-v2/example/overlayers/polygon-draw

1、先拿到当前自定义区域所对应的经纬度坐标数组,这里以上海为列子

核心代码:

  /**
   * 需要绘制的经纬度数据源
   * 三维数组,这里以上海为列子
   */
  let paths = [
      // 由于每个区域并非是连一起的,所以每个小的区域是去绘制的,
      [
          // 这里的经纬度是一个数组,由于参数 path 是这种格式,保持一致即可
          [121.7789, 31.3102],
          [121.5723, 31.4361],
          [121.5624, 31.4864],
          [121.7694, 31.3907],
          [121.7789, 31.3102],
      ],
      [
          [121.9433, 31.2155],
          [121.9573, 31.2304],
          [122.0086, 31.221],
          [121.9957, 31.1608],
          [121.9596, 31.1593],
          [121.9433, 31.2155],
      ],
  ];

2、遍历该区域下的坐标,绘制每个子区域矢量图

**小细节:**每个小区域都需要用尺量图绘制,一起绘制是可以的,但是后面地图的自适应就不好使了

核心代码:

/**
 * 尺量图集合
 */
let polygons = [];

/**
 * 构造多边形对象
 * @param path 多边形轮廓线的节点坐标数组
 * @param color
 */
let addPolygon = function (path, color) {
    // 用于在地图上绘制线、面等矢量地图要素的类型
    let polygon = new AMap.Polygon({
        strokeWeight: 2, // 线条宽度,默认为 1
        path: path, // 多边形轮廓线的节点坐标数组
        fillOpacity: 0.4,
        clickable: false,
        fillColor: color, // 多边形填充颜色
        strokeColor: color, // 线条颜色
        lineJoin: 'round', // 折线拐点的绘制样式,默认值为'miter'尖角,其他可选值:'round'圆角、'bevel'斜角
    });
    polygons.push(polygon);
}

/**
 * tips:小细节,
 * 每个小区域都需要用尺量图绘制,一起绘制是可以的,但是后面地图的自适应就不好使了
 * 遍历每个小区域并绘制
 * @param path 多边形轮廓线的节点坐标数组
 */
paths.map(path => {
    addPolygon(path);
});
3、添加到地图,并做自适应

**小细节:**渲染到地图之后,用setFitView做个地图窗口自适应

// 渲染尺量图到地图
map.add(polygons);

/**
 * tips:小细节,
 * 绘制完成之后,做个窗口自适应
 */
map.setFitView(polygons);
4、添加中心点Marker

https://lbs.amap.com/api/jsapi-v2/documentation#marker

自定义区域绘制好了,接下来就得将区域名称及数据展示出来
需要使用Marker组件将对应信息绘制在这个区域的中心位置

这里的Marker有两个用途

  • 展示区域信息及相关数据
  • 通过点击实现地图下钻

通过MarkerextData属性来携带当前层级数据,便于下一级的钻取

image.png

核心代码:

  /**
   * 自定义marker内容
   * @param item { title: '', count: '', position: []}
   * @returns {string}
   */
  let renderMarker = function (item) {
      const {
          title = '',
          count = 0,
          center = [],
      } = item;

      // 创建纯文本标记
      let marker = new AMap.Marker({
          content: `<div class='area-map-marker' style='color: ${item.color || '#000'}'>
                      <div class='area-map-marker__title' style='font-weight: bold;'>${title}</div>
                      <div class='area-map-marker__title'>${count || 0}</div>
                    </div>`,
          anchor: 'center', // 设置文本标记锚点
          draggable: false,
          cursor: 'pointer',
          position: center,
          extData: item,
          zIndex: 1000,
      });
      markers.push(marker);
      // 通过点击实现地图下钻
      marker.on('mousedown', (e) => {
          handleAreaClick(e);
      });
      marker.setMap(map);
  }

这里需要将每个merker放到一个集合markers,用于后期的回收

// 清空markers
if (markers.length >  0) {
    map.remove(markers);
    markers = [];
}
5、中心位置的获取

通过当前所有的经纬度集合【原数据需要做展开处理】,计算得到中心点的经纬度

核心代码:

/**
 * 获取随机数
 */
function getRandomNum (min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

/**
 * @desc 返回中心点的[经度,纬度]
 * @param points  points = [[经度,纬度], [经度,纬度]]; 参数数组points的每一项为每一个点的:[经度,纬度]
 * @returns {number[]} 返回中心点的数组[经度,纬度]
 */
function getPointsCenter (points) {
    try {
        let point_num = points.length; // 坐标点个数
        let X = 0, Y = 0, Z = 0;
        for (let i = 0; i < points.length; i++) {
            if (points[i] == '') {
                continue;
            }
            let point = points[i];
            let lat, lng, x, y, z;
            lng = parseFloat(point[0]) * Math.PI / 180;
            lat = parseFloat(point[1]) * Math.PI / 180;
            x = Math.cos(lat) * Math.cos(lng);
            y = Math.cos(lat) * Math.sin(lng);
            z = Math.sin(lat);
            X += x;
            Y += y;
            Z += z;
        }
        X = X / point_num;
        Y = Y / point_num;
        Z = Z / point_num;

        let tmp_lng = Math.atan2(Y, X);
        let tmp_lat = Math.atan2(Z, Math.sqrt(X * X + Y * Y));

        // 经纬度分别小数点后2位加随机数,防止Marker完全重叠
        let x = getRandomNum(2, 12) * 0.01;
        let y = getRandomNum(3, 12) * 0.01;
        return [(tmp_lng * 180 / Math.PI) + x, (tmp_lat * 180 / Math.PI) + y];
    } catch (e) {
        console.warn('获取中心坐标失败');
        console.log(e);
    }
}

四、数据整合

上面的是一个简单步骤,最重要的还是数据,这里需要得到两个数据

经纬度边缘坐标

https://lbs.amap.com/api/webservice/guide/api/district/
通过省市区code看来查询全国所有的省、市、区对应的经纬度边缘坐标,并通过省市区code关联

extensionsall,才能得到对应的边界坐标,这个也最好让服务端来批量获取并存下来,
数据比较多,可以做稀疏处理,大概6倍即可,当然数据越多轮廓越精细

https://restapi.amap.com/v3/config/district?keywords=310000&key=56e119b97e84efd95dbca95cd2be3126&subdistrict=2&extensions=all

polyline就是我们最终需要的经纬度边缘坐标集合了,然后整合成二位数组
image.png

将省市区code和经纬度数组整合成Object,键为省市区code,值为坐标集合

最终结构如下
最好可以放在CDN,来做一个缓存

// 对应上海,苏州和无锡
let areaPath = {
    "310000": [
        // 由于每个区域并非是连一起的,所以每个小的区域是去绘制的,
        [
            // 这里的经纬度是一个数组,由于参数 path 是这种格式,保持一致即可
            [121.7789, 31.3102],
            [121.5723, 31.4361],
            [121.5624, 31.4864],
            [121.7694, 31.3907],
            [121.7789, 31.3102],
        ],
        [
            [121.627, 31.445],
            [121.5758, 31.4782],
            [121.635, 31.453],
            [121.627, 31.445],
        ],
        [
            [121.9433, 31.2155],
            [121.9573, 31.2304],
            [122.0086, 31.221],
            [121.9957, 31.1608],
            [121.9596, 31.1593],
            [121.9433, 31.2155],
        ],
    ],
    "320500": [
        [[121.3435, 31.51206], [121.32251, 31.49936], [121.32016, 31.50588], [121.30553, 31.50534], [121.2986,
        31.49151], [121.26887, 31.48746], [121.25943, 31.47795], [121.25445, 31.48375], [121.24707, 31.47706], [
        121.2419, 31.49329], [121.23524, 31.49312], [121.23036, 31.47743], [121.2138, 31.47891], [121.18082,
        31.45146], [121.14734, 31.44393], [121.14783, 31.43619], [121.16434, 31.43106], [121.14625, 31.42107], [
        121.15938, 31.40579], [121.14383, 31.39233], [121.14844, 31.38541], [121.11375, 31.37445], [121.12022,
        31.36867], [121.10695, 31.3666], [121.10728, 31.35525], [121.11798, 31.35141], [121.11797, 31.34345], [
        121.13044, 31.34421], [121.12995, 31.30259], [121.14378, 31.3097], [121.16156, 31.28299], [121.14521,
        31.27529], [121.12065, 31.28703], [121.10545, 31.27365], [121.08879, 31.29208], [121.08215, 31.27154], [
        121.06246, 31.26745], [121.05716, 31.24643], [121.06435, 31.24614], [121.0668, 31.19496], [121.0773,
        31.16602], [121.06912, 31.1487], [121.04542, 31.15403], [121.04077, 31.13759], [121.02672, 31.14377], [
        121.01849, 31.1341], [120.98391, 31.13171], [120.93034, 31.14141], [120.87874, 31.13338], [120.8568,
        31.10283], [120.89235, 31.09749], [120.90472, 31.0805], [120.89462, 31.05866], [120.90136, 31.0175], [
        120.89135, 31.00374], [120.8655, 30.98968], [120.8474, 30.9896], [120.82298, 31.0057], [120.80291, 31.00541],
        [120.76993, 30.99662], [120.7697, 30.9773], [120.74594, 30.9625], [120.7256, 30.97154], [120.6982, 30.97079], [
        120.68445, 30.95518], [120.69756, 30.95034], [120.71063, 30.92905], [120.71326, 30.88505], [120.70128,
        30.88336], [120.70392, 30.87094], [120.69312, 30.87066], [120.68282, 30.88254], [120.66326, 30.86131], [
        120.65869, 30.86527], [120.65488, 30.84704], [120.64356, 30.85546], [120.60862, 30.84825], [120.58896,
        30.85443], [120.55949, 30.83152], [120.50444, 30.75797], [120.48909, 30.76369], [120.47553, 30.80386], [
        120.45528, 30.81685], [120.4604, 30.83986], [120.4413, 30.85629], [120.45318, 30.86972], [120.4345,
        30.88798], [120.44254, 30.90168], [120.435, 30.92082], [120.4201, 30.92771], [120.42422, 30.90058], [
        120.37941, 30.89077], [120.36466, 30.88047], [120.35682, 30.911], [120.35967, 30.93294], [120.37129,
        30.94867], [120.25066, 30.92571], [120.1979, 30.92831], [120.13252, 30.94308], [120.05245, 31.00574], [
        120.00041, 31.02775], [119.98852, 31.05922], [119.94364, 31.10466], [119.93982, 31.14295], [119.9205,
        31.16234], [119.9197, 31.17074], [120.11023, 31.264], [120.17372, 31.30881], [120.20961, 31.34566], [
        120.41771, 31.44768], [120.47418, 31.44656], [120.52361, 31.46873], [120.54297, 31.46993], [120.55524,
        31.47787], [120.54804, 31.49508], [120.5552, 31.50758], [120.59124, 31.52751], [120.59666, 31.51627], [
        120.60549, 31.52402], [120.59457, 31.57601], [120.57384, 31.58595], [120.56742, 31.584], [120.57303,
        31.57747], [120.55021, 31.57502], [120.54309, 31.60174], [120.56647, 31.60193], [120.57708, 31.61416], [
        120.6006, 31.61712], [120.5921, 31.62504], [120.59213, 31.65028], [120.55862, 31.65733], [120.57023,
        31.66932], [120.56821, 31.68546], [120.58645, 31.69071], [120.60081, 31.70885], [120.58245, 31.72117], [
        120.58436, 31.73447], [120.60002, 31.74463], [120.58424, 31.78215], [120.57071, 31.79378], [120.55838,
        31.78571], [120.55589, 31.7942], [120.53156, 31.78779], [120.52254, 31.80629], [120.53131, 31.82785], [
        120.50328, 31.84171], [120.49088, 31.87133], [120.46882, 31.87962], [120.4665, 31.88998], [120.37867,
        31.91374], [120.39126, 31.92861], [120.37353, 31.94644], [120.3707, 31.99082], [120.40376, 32.01622], [
        120.46567, 32.04583], [120.5038, 32.04102], [120.62839, 32.00117], [120.76158, 32.02045], [120.78204,
        32.01599], [120.80313, 31.98844], [120.86033, 31.87306], [120.91664, 31.79366], [120.9595, 31.78304], [
        121.06064, 31.78306], [121.10122, 31.76252], [121.14533, 31.75392], [121.28911, 31.61628], [121.37221,
        31.55321], [121.3435, 31.51206]
        ]
    ],
    "320200": [
        [[120.3707, 31.99082], [120.37353, 31.94644], [120.39126, 31.92861], [120.37867, 31.91374], [120.4665,
    31.88998], [120.46882, 31.87962], [120.49088, 31.87133], [120.50328, 31.84171], [120.53131, 31.82785], [
    120.52254, 31.80629], [120.53156, 31.78779], [120.55589, 31.7942], [120.55838, 31.78571], [120.57071,
    31.79378], [120.59766, 31.75503], [120.60002, 31.74463], [120.58171, 31.72763], [120.58509, 31.71443], [
    120.59995, 31.7059], [120.58645, 31.69071], [120.56821, 31.68546], [120.57023, 31.66932], [120.55858,
    31.65851], [120.59213, 31.65028], [120.5921, 31.62504], [120.6006, 31.61712], [120.57708, 31.61416], [
    120.56647, 31.60193], [120.54309, 31.60174], [120.55021, 31.57502], [120.57303, 31.57747], [120.56742,
    31.584], [120.57384, 31.58595], [120.59457, 31.57601], [120.60502, 31.54607], [120.60235, 31.51899], [
    120.59666, 31.51627], [120.59124, 31.52751], [120.5552, 31.50758], [120.54804, 31.49508], [120.55524,
    31.47787], [120.54297, 31.46993], [120.52361, 31.46873], [120.47418, 31.44656], [120.41771, 31.44768], [
    120.20961, 31.34566], [120.17372, 31.30881], [120.11023, 31.264], [119.91862, 31.17019], [119.87874,
    31.1608], [119.82785, 31.17454], [119.82345, 31.16582], [119.82937, 31.15827], [119.80986, 31.14852], [
    119.79128, 31.15661], [119.79252, 31.17146], [119.77932, 31.17878], [119.71589, 31.16958], [119.70388,
    31.1519], [119.67261, 31.168], [119.64186, 31.14843], [119.63705, 31.13453], [119.61373, 31.12918], [
    119.59978, 31.10917], [119.57651, 31.11027], [119.57133, 31.12901], [119.53259, 31.15909], [119.55357,
    31.17916], [119.55386, 31.22105], [119.52259, 31.24217], [119.53576, 31.27566], [119.52003, 31.31824], [
    119.53051, 31.33088], [119.52817, 31.36524], [119.54172, 31.39431], [119.53624, 31.40787], [119.55177,
    31.4155], [119.5525, 31.4325], [119.57719, 31.43077], [119.5896, 31.44766], [119.58833, 31.46669], [
    119.56515, 31.46434], [119.57515, 31.4808], [119.56716, 31.50508], [119.58362, 31.50454], [119.60787,
    31.55318], [119.64445, 31.57296], [119.63938, 31.60026], [119.67303, 31.60933], [119.68512, 31.60383], [
    119.69979, 31.57655], [119.70996, 31.576], [119.71482, 31.55618], [119.73326, 31.56316], [119.79218,
    31.55338], [119.83272, 31.5291], [119.8478, 31.5298], [119.8622, 31.54634], [119.93563, 31.55272], [
    119.97172, 31.53596], [119.97357, 31.51586], [119.99611, 31.4975], [119.99716, 31.50812], [120.04568,
    31.49091], [120.03761, 31.42589], [120.02107, 31.38301], [120.0237, 31.36495], [120.04176, 31.34588], [
    120.10015, 31.33533], [120.09614, 31.35249], [120.04485, 31.35881], [120.03971, 31.37813], [120.05479,
    31.43429], [120.11007, 31.46153], [120.10874, 31.48099], [120.12847, 31.50547], [120.11957, 31.50834], [
    120.12067, 31.51615], [120.10303, 31.51407], [120.0992, 31.54823], [120.06026, 31.55799], [120.05566,
    31.5777], [120.07489, 31.59536], [120.07558, 31.60711], [120.10448, 31.62877], [120.11948, 31.6309], [
    120.12853, 31.68463], [120.14327, 31.67608], [120.15253, 31.68364], [120.143, 31.68833], [120.15684,
    31.70383], [120.15447, 31.75624], [120.16902, 31.76097], [120.18394, 31.74992], [120.20116, 31.75327], [
    120.17425, 31.80103], [120.17923, 31.81334], [120.16392, 31.83029], [120.18425, 31.85613], [120.17589,
    31.87021], [120.14423, 31.85893], [120.08547, 31.85306], [120.04498, 31.82175], [120.02877, 31.83212], [
    120.01981, 31.82276], [120.00492, 31.82582], [119.99007, 31.85461], [120.00327, 31.85915], [120.01478,
    31.88179], [119.99783, 31.89435], [120.00089, 31.90557], [120.02241, 31.91969], [120.0074, 31.93593], [
    120.00878, 31.9513], [120.0224, 31.96775], [120.13481, 31.93938], [120.2366, 31.93291], [120.3707, 31.99082]]
    ],
}

每一级部门数据及对应的区域code集合

下钻的每一级区域及对应数据是已知的,这里的数据已经存有区域code,
所以就可以很好的和经纬度数据做一个关联

区域数据结构如下
这个数据一般由接口返回

let areaList = [
    {
        "id": "2", 
        "name": "华东科", 
        "level": 2, 
        "levelTitle": "科室", 
        "count": 100,
        "areaIdList": [
            "310000", 
            "320000", 
            "330000", 
            "370000", 
            "420000", 
            "500000"
        ]
    }, 
    {
        "id": "3", 
        "name": "华南科", 
        "mapTier": 2, 
        "levelTitle": "科室", 
        "count": 100,
        "areaIdList": [
            "350000", 
            "360000", 
            "430000", 
            "450000", 
            "440000", 
            "420000", 
            "460000", 
            "520000", 
            "530000"
        ]
    }, 
    {
        "id": "4", 
        "name": "西北科", 
        "level": 2, 
        "levelTitle": "科室", 
        "count": 100,
        "areaIdList": [
            "610000", 
            "650000", 
            "500000", 
            "620000", 
        ]
    }, 
    {
        "id": "5", 
        "name": "华北科", 
        "level": 2, 
        "levelTitle": "科室", 
        "count": 100,
        "areaIdList": [
            "120000", 
            "130000", 
            "140000", 
            "370000", 
            "340000"
        ]
    }, 
    {
        "id": "7", 
        "name": "东北科", 
        "level": 2, 
        "levelTitle": "科室", 
        "count": 100,
        "areaIdList": [
            "110000", 
            "150000", 
            "230000", 
            "220000", 
            "210000"
        ]
    }
]

这里的id和level根据业务需要来定,
当前level是按照如下划分:
1:国家
2:科室
3:部门
4:经销商
5:区县

五、地图下钻

下钻其实就是获取到下一级的区域数据,并渲染到地图

在marker渲染的时候,添加了事件的处理,

// 通过点击实现地图下钻
marker.on('mousedown', (e) => {
    handleAreaClick(e);
});

就可以在事件回调中来根据当前层级来获取下一层级的数据,并完成地图的渲染

let handleAreaClick = function () {
    const data = e.target.De.extData;
    const {
        level,
        levelTitle,
        id,
        count,
    } = data;
    getMapData(id, level);
}


// 默认从科室层级开始
let getMapData = function (id, level = 2) {
 
    // 接口获取
    getUnderData({
        id,
        level: level + 1,
    }).then(res => {
        console.log(res);
        let areaList = res;
        areaList.map(item => {
            let position = [];
            item.areaList.map(areaId => {
                let paths = areaPath[areaId];
                paths.map(path => {
                    position = [...position, ...path];
                    addPolygon(path);
                });
            });
            // 获取中心点坐标,并渲染区域名称及数据marker
            let center = getPointsCenter(position);
             renderMarker({
                ...item,
                center,
                color: '#ccc',
             });
        });
    })
}

至此,下钻功能完成,源码请移步 https://github.com/wforguo/daily-code/blob/master/vue-app/vue2-app/src/views/AmapDrop/index.vue

Logo

前往低代码交流专区

更多推荐