高德地图-区域地图-实现对地图区域板块进行颜色标注与地图主题风格切换

哈喽,各位高贵的倔友们你们好呀(✪ω✪),今天小编给各位分享一篇关于高德地图内容的文章,如果各位倔友觉得还不错,期望点个赞呗,那我们话不多说,直接进入正题。

写在开头

申请Web端开发者Key和安全密钥

要使用高德地图,我们必须先去高德地图官网注册成为开发者,并创建应用申请得到一个开发者 Key 与安全密钥。

注册/登录 -> 右上角头像 -> 应用管理 -> 点击"创建新应用"按钮 -> 添加 Key

上面我们简单给个过程路径就不再多说啦,很简单的,更加详细的过程可以看看这里。传送门

安装

然后我们需要安装高德地图的依赖包:

javascript 复制代码
npm install @amap/amap-jsapi-loader --save

初始化地图

装好依赖后,就能初始化地图了。

新建 AreaMap.vue 文件:

javascript 复制代码
<template>
  <div id="container" />
</template>

<script>
import AMapLoader from '@amap/amap-jsapi-loader';

export default {
  data() {
    return {
      // map:null, // 此处不声明 map 对象,可以直接使用 this.map 赋值或者采用非响应式的普通对象来存储。
    }
  },
  async mounted() {
    await this.initMap();
  },
  methods: {
    /** @name 初始化地图 **/
    initMap() {
      return new Promise(resolve => {
        window._AMapSecurityConfig = {
          securityJsCode: '你的安全密钥', // 自2021年12月02日升级后, key与安全密钥必须一起使用, 否则可能会出现一些API无法使用,如 DistrictSearch
        }
        AMapLoader.load({
          key: '你的Key', // 首次调用 load 时必填
          version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
          plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
        }).then((AMap)=>{
          this.map = new AMap.Map('container', {
            center: [110.17293, 35.20173],
            zoom: 4.8,
          });
          this.map.on('complete', () => {
            resolve();
          });
        }).catch(e=>{
          console.log(e);
        })
      })
    }
  }
}
</script>

<style scoped>
#container {
  width: 740px;
  height: 500px;
}
</style>

这里小编使用的是 Vue,对于其他技术栈初始化地图,可以看看这里传送门

数据准备

这里小编先准备了一些测试数据,下面会以这种数据结构来编写逻辑,如果你真实的数据结构与此不同,可以自行再转换一下或者适当调整一下代码逻辑。

创建 data.js 文件:

javascript 复制代码
const data = [
  {
    "province": "北京市",
    "city": "北京城区",
    "area": "西城区",
    "append": "北京市北京城区西城区3",
    "lng": "116.365868",
    "lat": "39.912289",
  },
  {
    "province": "上海市",
    "city": "上海城区",
    "area": "静安区",
    "append": "上海市上海城区静安区5",
    "lng": "121.459384",
    "lat": "31.247105",
  },
  {
    "province": "广东省",
    "city": "广州市",
    "area": "黄埔区",
    "append": "广东省广州市黄埔区4",
    "lng": "113.459749",
    "lat": "23.106402",
  },
  {
    "province": "四川省",
    "city": "成都市",
    "area": "青羊区",
    "append": "四川省成都市青羊区6",
    "lng": "104.062499",
    "lat": "30.674406",
  },
  {
    "province": "河北省",
    "city": "石家庄市",
    "area": "桥西区",
    "append": "河北省石家庄市桥西区2",
    "lng": "114.461154",
    "lat": "38.004043",
  },
  {
    "province": "重庆市",
    "city": "重庆城区",
    "area": "涪陵区",
    "append": "重庆市重庆城区涪陵区1",
    "lng": "107.389298",
    "lat": "29.703113",
  },
];

export default data;

需要注意,上面小编虽然只列举了几条数据,但像这种以后可能会增大的数据量,一定不要直接挂载到 Vue 身上,要不消耗的依赖收集行为会特别影响性能!!!

不要问我为什么知道的(•́へ•́╬)。

地图板块颜色标注

完成准备工作后,我们就要进入本章的主题对"地图区域板块"进行自定义颜色标注,该功能主要使用到高德地图的 AMap.DistrictLayer 图层对象。

该对象使用方式很简单,我们主要关注它的 adcode 参数,它表示区域板块的行政编码,下面小编写了一个简单案例,请观看:

adcode 参数官方文档写得是 String 类型,但实际还支持 Array 类型。

javascript 复制代码
<script>
export default {
  ...
  async mounted() {
    await this.initMap();
    // 初始完地图后,开始绘制
    this.drawAreaMap();
  },
  methods: {
    ...,
    /** @name 绘制区域地图 **/
    drawAreaMap() {
      const instance = new window.AMap.DistrictLayer.Province({
        adcode: ["310000", "110000", "510000", "440000", "130000", "500000", "500000"],
        depth: 0,
        styles: {
          'fill': 'red',
        },
      });
      instance.setMap(this.map);
    },
  }
}
</script>

具体效果:

是不是很简单(✪ω✪),然后你以为这就完了?当然还没,接下来我们需要解决两个问题。

  1. 开头我们提前准备的数据里面是没有行政编码的,这个行政编码只能从高德那边获取回来。
  2. 我们需要实现对不同区域板块标注不同的颜色。

获取行政编码

行政编码的获取,高德提供了一个 AMap.DistrictSearch 对象供我们查询,它属于额外的插件服务,需要在地图初始化的时候进行配置。

具体配置如下:

javascript 复制代码
/** @name 初始化地图 **/
initMap() {
  return new Promise(resolve => {
    ...
    AMapLoader.load({
      key: '你的Key',
      version: '2.0',
      plugins: [ // 配置行政区查询服务
        'AMap.DistrictSearch'
      ],
    })
    ...
  })
}

后续如果使用到其他一些额外服务都是在 plugins 这里进行配置。

接下来我们就把数据的地址信息转换成具体的行政编码:

javascript 复制代码
<script>
import AMapLoader from '@amap/amap-jsapi-loader';
import data from './data.js';

export default {
  ...
  async mounted() {
    await this.initMap();
    // 初始完地图后,开始绘制
    this.drawAreaMap();
  },
  methods: {
    ...,
    /** @name 绘制区域地图 **/
    drawAreaMap() {
      const allAdCode = await this.getAllAdCode();
      console.log('allAdCode', allAdCode);
      // 获取数据对应的"省级"行政编码
      const adcode = [];
      data.forEach(address => {
        allAdCode.districtList.forEach(province => {
          if(address.province === province.name) {
            adcode.push(province.adcode)
          }
        })
      })
    
      const instance = new window.AMap.DistrictLayer.Province({
        adcode,
        depth: 0,
        styles: {
          'fill': 'red',
        },
      });
      instance.setMap(this.map);
    },
    /** @name 获取所有区域板块的行政编码 **/
    getAllAdCode() {
      return new Promise(resolve => {
        const searchInstance = new window.AMap.DistrictSearch({
          subdistrict: 3, // 获取的下级行政区"级数"
          showbiz: false, // 是否显示商圈, 这里用不上直接false
        });
        searchInstance.search('中国', (status, result) => {
          if (status === 'complete') {
            resolve(result.districtList[0]);
          }
        });
      });
    }
  }
}
</script>

subdistrict 参数有四个值:0、1、2、3。默认值为 1, 其中 0 表示不返回下级行政区,1 表示返回下一级行政区,2 表示返回下两级行政区,3 表示返回下三级行政区。

getAllAdCode 方法中,小编直接把全国与省市区三级的行政编码一次性全部获取回来了,然后再通过循环去找到对应的数据,高德返回全国区域的行政编码其实是非常快的。

记得初次写得时候,小编是根据数据中需要哪个区域的数据就去查询那个区域,少量数据的话这样子是没有问题的,但是一旦数据量开始变多,并且查询的不是省级,是市级或者区级,那性能就嘎嘎往下掉。。。-.-

不同颜色标注

那么,现在地图中红色板块的标注就是根据"数据"来控制的了,但是全部都是红色的略显单调,小编这就来给它们分别上上色(✪ω✪)。

官方文档中对于板块的样式调整有六个参数可以使用,应该是足够满足我们实际中复杂的业务需求了。

具体过程如下:

javascript 复制代码
<script>
export default {
  ...
  methods: {
    ...,
    /** @name 绘制区域地图 **/
    drawAreaMap() {
      ...
      const instance = new window.AMap.DistrictLayer.Province({
        adcode,
        depth: 0,
        styles: {
          // 填充色
          'fill': fillParams => {
             console.log(fillParams)
             const r = Math.floor(Math.random() * 255);
             const g = Math.floor(Math.random() * 255);
             const b = Math.floor(Math.random() * 255);
             const color = `rgba(${r}, ${g}, ${b}, 0.8)`;
             return adcode.includes(`${fillParams.adcode}`) ? color : undefined;
          },
        },
      });
      instance.setMap(this.map);
    },
  }
}
</script>

以上小编通过填充色 fill 支持的 Function 类型来实现不同颜色的标注,在回调函数中我们能获取到每个区域板块的行政编码。

省市区板块标注切换

上面我们标注的区域都是以省级为单位的,那么如何实现省级、市级、区级之间的标注切换呢?接下来请听小编细细道来(✪ω✪)。

前面我们知道了标注一块区域就需要知道这块区域的行政编码才行,那么问题转换成:

  • 省级:获取需要的省级行政编码
  • 市级:获取需要的市级行政编码
  • 区域:获取需要的区级行政编码

而且我们在上面的 allAdCode 变量中存储了全国所有区域板块的行政编码,它是一棵树形结构。

我们先看效果,再具体看编码过程:

javascript 复制代码
<template>
  <div class="map">
    <div id="container" />
    <select v-model="range" @change="rangeChange">
      <option :value="'province'">省级</option>
      <option :value="'city'">市级</option>
      <option :value="'area'">区级</option>
    </select>
  </div>
</template>

<script>
import AMapLoader from '@amap/amap-jsapi-loader';
import data from './data.js';

let instance = null;

export default {
  data() {
    return {
      // map:null,
      range: 'province'
    }
  },
  ...
  methods: {
    ...,
    /** @name 显示范围改变 **/
    rangeChange() {
      this.drawAreaMap();
    },
    /** @name 绘制区域地图 **/
    drawAreaMap() {
      const allAdCode = await this.getAllAdCode();
      // 将树形转换成对象形式
      const allAdCodeMap = this.transformTree(allAdCode);
      // 获取行政编码
      const adcode = [];
      data.forEach(address => {
        if (this.range === 'province') {
          adcode.push(allAdCodeMap[address.province]?.adcode || '');
        }
        if (this.range === 'city') {
           // 处理直辖市
           const directCity = [
             { label: '北京市', value: '110000' },
             { label: '天津市', value: '120000' },
             { label: '重庆市', value: '500000' },
             { label: '上海市', value: '310000' },
             { label: '香港特别行政区', value: '810000' },
           ];
           const target = directCity.find(item => item.label === address.province);
           if (target) {
             adcode.push(target.value);
           } else {
             adcode.push(allAdCodeMap[address.city]?.adcode || '');
           }
        }
        if (this.range === 'area') {
          adcode.push(allAdCodeMap[address.area]?.adcode || '');
        }
      });
      // 重新绘制需要清空图层
      instance && instance.setMap(null);
      const depth = {
        province: 0,
        city: 1,
        area: 2,
      };
      instance = new window.AMap.DistrictLayer.Province({
        adcode,
        depth: depth[this.range], // 设定数据的层级深度: https://lbs.amap.com/api/javascript-api-v2/documentation#districtlayer
        styles: {
          'fill': fillParams => {
             const r = Math.floor(Math.random() * 255);
             const g = Math.floor(Math.random() * 255);
             const b = Math.floor(Math.random() * 255);
             const color = `rgba(${r}, ${g}, ${b}, 0.8)`;
             return adcode.includes(`${fillParams.adcode}`) ? color : undefined;
          },
        },
      });
      instance.setMap(this.map);
    },
    /** @name 将树形的行政编码转成对象形式 **/
    transformTree(allAdCode) {
      let allAdCodeMap = {};
      if (this.range === 'province') {
        allAdCodeMap = allAdCode.districtList.reduce((result, province) => {
          result[province.name] = province;
          return result;
        }, {});
      }
      if (this.range === 'city') {
        data.forEach(address => {
          const province = allAdCode.districtList.find(
            item => item.name === address.province,
          );
          if (province && province.districtList) { // 注意台湾省数据没有districtList
            const cityMap = province.districtList.reduce((result, city) => {
              result[city.name] = city;
              return result;
            }, {});
            allAdCodeMap = {
              ...allAdCodeMap,
              ...cityMap,
            }
          }
        })
      }
      if (this.range === 'area') {
        data.forEach(address => {
          const province = allAdCode.districtList.find(
            item => item.name === address.province,
          );
          if (province && province.districtList) {
            const city = province.districtList.find(
              item => item.name === address.city,
            );
            if (city && city.districtList) {
              const areaMap = city.districtList.reduce((result, area) => {
                    result[area.name] = area;
                    return result;
              }, {});
              allAdCodeMap = {
                ...allAdCodeMap,
                ...areaMap,
              };
            };
          }
        });
      };
      return allAdCodeMap;
    },
  }
}
</script>

编码过程不难,主要需要注意两点问题,其一是特殊省份像直辖市、台湾省、澳门等等,其二是性能问题。

主题风格切换

最后,我们再来完成一个地图主题风格切换的功能,高德地图官方给我们提供了十种标准的主题,可以直接免费使用,如果还不能满足你的需求,你也可以创建自定义的主题地图,当然,自定义功能强大只是需要花点 Money (✪ω✪)。传送门

因为比较简单,小编就直接来贴代码啦。

javascript 复制代码
<template>
  <div class="map">
    ...
    <select v-model="style" @change="styleChange">
      <option v-for="item in styleOptions" :key="item.value"  :value="item.value">
        {{ item.label }}
      </option>
    </select>
  </div>
</template>

<script>
...
export default {
  data() {
    return {
      ...,
      style: 'normal',
      styleOptions: [
        { label: '标准', value: 'normal' },
        { label: '幻影黑', value: 'dark' },
        { label: '月光银', value: 'light' },
        { label: '远山黛', value: 'whitesmoke' },
        { label: '草色青', value: 'fresh' },
        { label: '雅士灰', value: 'grey' },
        { label: '涂鸦', value: 'graffiti' },
        { label: '马卡龙', value: 'macaron' },
        { label: '靛青蓝', value: 'blue' },
        { label: '极夜蓝', value: 'darkblue' },
        { label: '酱籽', value: 'wine' },
      ]
    }
  },
  ...
  methods: {
    ...,
    /** @name 初始化地图 **/
    initMap() {
      return new Promise(resolve => {
        window._AMapSecurityConfig = {
          securityJsCode: '你的安全密钥',
        }
        AMapLoader.load({
          key: '你的Key',
          version: '2.0',
          plugins: [
            'AMap.DistrictSearch'
          ],
        }).then((AMap)=>{
          this.map = new AMap.Map('container', {
            center: [110.17293, 35.20173],
            zoom: 4.8,
            mapStyle: 'amap://styles/' + this.style, // 加载主题
          });
          this.map.on('complete', () => {
            resolve();
          });
        }).catch(e=>{
          console.log(e);
        })
      })
    },
    /** @name 主题风格切换 **/
    async styleChange() {
      await this.initMap();
      this.drawAreaMap();
    },
  }
}
</script>

具体实现效果:


哎,写到这里突然发现文章好像又贼拉长了,还有两个功能没写呢("工具栏操作"、"地图板块交互事件"),呃......后面再写续集吧,溜了溜了。

完整源码

点我点我


至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。

老样子,点赞+评论=你会了,收藏=你精通了。

相关推荐
树叶会结冰18 分钟前
HTML语义化:当网页会说话
前端·html
冰万森23 分钟前
解决 React 项目初始化(npx create-react-app)速度慢的 7 个实用方案
前端·react.js·前端框架
牧羊人_myr36 分钟前
Ajax 技术详解
前端
浩男孩1 小时前
🍀封装个 Button 组件,使用 vitest 来测试一下
前端
蓝银草同学1 小时前
阿里 Iconfont 项目丢失?手把手教你将已引用的 SVG 图标下载到本地
前端·icon
布列瑟农的星空1 小时前
重学React —— React事件机制 vs 浏览器事件机制
前端
程序定小飞1 小时前
基于springboot的在线商城系统设计与开发
java·数据库·vue.js·spring boot·后端
一小池勺1 小时前
CommonJS
前端·面试
孙牛牛2 小时前
实战分享:一招解决嵌套依赖版本失控问题,以 undici 为例
前端