前言

前端开发经常会在各种场合需要用到地图展示,网上很多实现都是基于vue2的,这里记录一下vue3实现地图展示和数据标注的几种方法。同时也会比较一下几种方法的优缺点。如果有新的发现的话也会随时更新到本章中。


提示:以下是本篇文章正文内容

一、使用echarts

1. 安装echarts

在项目中引入 Apache ECharts 参照官方文档进行安装和引入

npm install echarts --save

不建议使用vue-echarts, 因为echarts本身已经比较简单了,而且官方很多示例代码,用vue-echarts增加学习成本

2. echarts中使用百度地图

百度地图API3.0文档
这个方法的优点是简单快捷,将echarts和百度地图结合起来,缺点是百度地图不能单纯展示一个地区,如我只需要展示广东省的地图,这就不行。

2.1 引入百度地图

在public/index.html中引入,根据官网教程,注册百度地图,获取应用ak。

//public/index.html
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title>
    <%= htmlWebpackPlugin.options.title %>
  </title>
  <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=你的ak"></script>
</head>
2.2 全部代码

以下是全部代码,其中涉及到echarts的配置问题可以查看echarts的官方文档

//EchartsBaiduMap.vue
<template>
  <div ref="mapEcharts" class="baidu-map-echart"></div>
</template>

<script>
import * as echarts from 'echarts'
import 'echarts/extension/bmap/bmap' // bmap用来处理百度地图
import { ref, onMounted, reactive } from 'vue'
export default {
  setup() {
    //标点的数据格式,其中value是必须的[经度,纬度,值]
    const state = reactive({
      datas: [
        {
          name: '海门',
          value: [121.15, 31.89, 100],
        },
        {
          name: '鄂尔多斯',
          value: [109.781327, 39.608266, 154],
        },
        {
          name: '舟山',
          value: [122.207216, 29.985295, 80],
        },
        {
          name: '金昌',
          value: [102.188043, 38.520089, 130],
        },
        {
          name: '乳山',
          value: [121.52, 36.89, 200],
        },
      ],
    })
    //模板引用,引用上面的dom节点
    const mapEcharts = ref(null)
    //初始化echarts
    function initEcharts() {
      console.log(mapEcharts)
      const myMap = echarts.init(mapEcharts.value)
      const option = {
        title: {
          text: 'echart百度地图示例',
          left: 'center',
        },
        // 使用echarts配置地图
        bmap: {
          center: [104.114129, 37.550339],
          zoom: 5,
          roam: true, // scale允许缩放,move允许移动,true允许两者
        },
        tooltip: {
          trigger: 'item',
        },
        series: [
          {
            name: 'assets',
            type: 'effectScatter',
            coordinateSystem: 'bmap',
            data: state.datas,
            symbolSize: function (val) {
              return val[2] / 5
            },
            encode: {
              value: 2,
            },
            label: {
              position: 'top',
            },
            itemStyle: {
              color: '#ddb926',
            },
            emphasis: {
              label: {
                show: true,
              },
            },
          },
        ],
      }
      myMap.setOption(option)
      myMap.on('click', (params) => {
        console.log('click', params)
      })
      // 使用百度api来配置
      let bmap = myMap.getModel().getComponent('bmap').getBMap()
      bmap.disableDoubleClickZoom() //阻止双击放大
    }
    onMounted(() => {
      initEcharts()
    })

    return {
      mapEcharts,
    }
  },
}
</script>
<style scoped>
.baidu-map-echart {
  height: 100%;
}
</style>
2.3 结果

echarts-bmap

3 echarts使用geomap进行地图展示

这个方法的优点是可以单独展示某个地区的地图轮廓,缺点是轮廓图缺少地图的细节,如路线等等

3.1地图轮廓geojson获取

各个地区省份的地图轮廓json数据可以在这里获取DataV数据可视化平台
可以将需要的json数据下载到本地,然后将其进行修改如下,在json文件的开头加上变量,然后导出,然后将文件名改为js,这样就可以直接引用了。也可以用ajax直接获取json。

//china.js
export let data={"type":"FeatureCollection","fe..."
3.2 全部代码
//EchartsGeomap.vue
<template>
  <div ref="mapEcharts" class="map-echart"></div>
</template>

<script>
import * as echarts from 'echarts'
import { data as guangdong } from './guangdong'
import { ref, nextTick, onMounted } from 'vue'
export default {
  setup() {
    const mapEcharts = ref(null)
    function initEcharts() {
      echarts.registerMap('guangdong', guangdong)
      nextTick(() => {
        const map = echarts.init(mapEcharts.value, null, {
          renderer: 'canvas',
        })
        const option = {
          title: {
            text: '广东地图',
            left: 'center',
          },
          // 悬浮窗
          tooltip: {
            trigger: 'item',
          },
          geo: {
            map: 'guangdong',
            zoom: 1,
            roam: 'move',
            label: {
              show: true,
              color: 'black',
              position: 'inside',
              distance: 0,
              fontSize: 10,
              rotate: 45,
            },
            // 所有地图的区域颜色
            itemStyle: {
              areaColor: '#eee',
              borderColor: '#02c0ff',
            },
            emphasis: {
              itemStyle: {
                areaColor: '#aaa',
                shadowColor: 'rgba(0,0,0,0.8)',
              },
            },
          },
          series: [
            {
              type: 'scatter',
              coordinateSystem: 'geo',
              data: [
                { name: '肇庆市', value: [112.48461, 23.05196, 20] },
                { name: '佛山市', value: [113.130234, 23.018978, 30] },
                { name: '广州', value: [113.261081, 23.139856, 10] },
              ],
              symbolSize: (val) => {
                return val[2]
              },
              encode: {
                value: 2,
              },
              itemStyle: {
                color: '#ddb926',
              },
              emphasis: {
                label: {
                  show: true,
                },
              },
            },
          ],
        }
        map.setOption(option)
      })
    }
    onMounted(() => {
      initEcharts()
    })
    return {
      mapEcharts,
    }
  },
}
</script>

<style scoped>
.map-echart {
  height: 600px;
  width: 900px;
}
</style>
3.3 结果

在这里插入图片描述

二、直接使用百度地图

echarts 的优点是它可以很快的帮助我们将数据标注在地图上,如果仅仅是基础的数据展示来说,基本上已经够用了。但是当要实现地图复杂功能的展示的时候,就不够用了,比如说下面这种情况。
在这里插入图片描述
这个信息框展示的东西很多,还有按钮,这种效果echarts就做不了。直接用百度地图来做是比较好的

1. 引入百度地图

这个和2.1节是一样的.
不建议使用vue-baidu-map 这个库,一个原因是这个库已经很久没有维护了,还是使用的vue2.0版本,另一个原因是原生的百度地图更加灵活好用。

2. 全部代码

代码涉及一些参数配置,这些都可以参照百度地图官方文档

//baidumap.vue
<template >
  <div class="contain">
    <div id="baiduMap" ref="baiduMapRef" class="baidu-map"></div>
    <div ref="infoBoxRef" class="info-box" v-if="resource">
      <header>
        <div class="name">
          {{ resource.name }}
        </div>
        <button @click="onCloseInfoBox">close</button>
      </header>
      <main>这里是内容</main>
    </div>
  </div>
</template>
<script>
import { onMounted, ref, reactive, toRefs } from 'vue'
//自定义信息窗口
import MyInfoBox from './libs/myInfoBox'
//自定义标注
import CircleOverlay from './libs/circleOverlay'
export default {
  setup() {
    const state = reactive({
      resource: {},
      resources: [
        {
          name: 'name1',
          coordiate: {
            type: 'Point',
            coordinates: [113.261081, 23.139856],
          },
        },
        {
          name: 'name2',
          coordiate: {
            type: 'Point',
            coordinates: [116.703144, 39.540888],
          },
        },
      ],
    })
    const infoBoxRef = ref(null)
    const baiduMapRef = ref(null)
    let myInfoBox
    onMounted(() => {
      const map = new BMap.Map('baiduMap', {
        minZoom: 10,
        maxZoom: 18,
        enableMapClick: false,
      })
      map.centerAndZoom(new BMap.Point(113.261081, 23.139856), 10)
      map.disableDoubleClickZoom() //阻止双击放大
      //设置自定义地图样式
      map.setMapStyleV2({
        styleId: 'dbc55f135f1d3ce3ead90de962ed0a8e',
      })

      //新建自定义信息窗口
      myInfoBox = new MyInfoBox(infoBoxRef.value)  //将我们上面自定义的dom结构传入
      map.addOverlay(myInfoBox)
      myInfoBox.hide()  //先隐藏,当点击标注的时候再显示

      //渲染标注
      state.resources.forEach((val) => {
        const point = new BMap.Point(...val.coordiate.coordinates)
        //新建自定义标注
        const circleMarker = new CircleOverlay(point, 40, '#ffe866ce')
        map.addOverlay(circleMarker)
        circleMarker.addEventListener('click', () => {
          state.resource = val
          myInfoBox.open(point)  //显示自定义信息窗口
        })
      })

      // infoBox.open(map.getCenter())
    })

    function onCloseInfoBox() {
      myInfoBox.hide()
    }
    return {
      infoBoxRef,
      baiduMapRef,
      ...toRefs(state),
      onCloseInfoBox,
    }
  },
}
</script>
<style lang="less" scoped>
.contain {
  height: 100%;
}
.contain .baidu-map {
  height: 100%;
}

.info-box {
  background-color: #fff;
  box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);
  position: absolute;
  border-radius: 6px;
  width: 230px;
  padding: 16px 20px;
  box-sizing: border-box;
  color: #333;
}

.info-box header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 18px;
}

.border {
  height: 1px;
  background-color: #eee;
  margin: 10px 0 !important;
}
</style>

你可能已经注意到了,两个自定义文件,不慌,下面会详细讲到

3. 运行效果

在这里插入图片描述

4. 自定义标注以及自定义信息窗口

4.1 自定义标注

这个可以参考这个文档 自定义标注
效果就是上面的黄色圆形

//CircleOverlay.js
// 自定义实现圆形的标注
// 定义自定义覆盖物的构造函数
export default function CircleOverlay(center, length = 10, color) {
  this._center = center
  this._radius = length
  this._color = color
}
// 继承API的BMap.Overlay
CircleOverlay.prototype = new BMap.Overlay()
CircleOverlay.prototype.initialize = function (map) {
  // 保存map对象实例
  this._map = map
  // 创建div元素,作为自定义覆盖物的容器
  var div = document.createElement('div')
  div.style.position = 'absolute'
  // 可以根据参数设置元素外观
  div.style.borderRadius = '50%'
  div.style.width = this._radius + 'px'
  div.style.height = this._radius + 'px'
  div.style.cursor = 'pointer'
  div.style.background = this._color
  // 将div添加到覆盖物容器中
  map.getPanes().markerPane.appendChild(div)
  // 保存div实例
  this._div = div
  // 需要将div元素作为方法的返回值,当调用该覆盖物的show、
  // hide方法,或者对覆盖物进行移除时,API都将操作此元素。
  return div
}
// 实现绘制方法
CircleOverlay.prototype.draw = function () {
  // 根据地理坐标转换为像素坐标,并设置给容器
  var position = this._map.pointToOverlayPixel(this._center)
  this._div.style.left = position.x - this._radius / 2 + 'px'
  this._div.style.top = position.y - this._radius / 2 + 'px'
}

CircleOverlay.prototype.addEventListener = function (type, fn) {
  if (typeof type !== 'string' || typeof fn !== 'function') return
  this._div.addEventListener(type, fn)
}
4.2 自定义信息窗口

官方文档中自定义信息窗口 是使用 infoWindow 来实现自定义的,但是这有一些限制,如不能自定义边框,不能自定义关闭按钮等。而且样式也不怎么好看
在这里插入图片描述
文档中还提到了可以使用BMapLib.infoBox来实现更自由的信息窗口,但BMapLib.infoBox需要另外在index.html引入InfoBox.js ,而且个人觉得它还不够自由。
在这里插入图片描述
在这里插入图片描述

信息窗口也是覆盖物的一种,所以我们可以像自定义标注那样进行自定义信息窗口,同样只需要继承BMap.Overlay 就好。

//myInfoBox.js
export default function MyInfoBox(content) {
  this._content = content
}

MyInfoBox.prototype = new BMap.Overlay()
MyInfoBox.prototype.initialize = function (map) {
  this._map = map
  this._loc = map.getCenter()
  map.getPanes().floatPane.appendChild(this._content)
  return this._content
}
MyInfoBox.prototype.draw = function () {
  var position = this._map.pointToOverlayPixel(this._loc)
  console.log()
  this._content.style.left = position.x - this._content.clientWidth / 2 + 'px'
  this._content.style.top = position.y - this._content.clientHeight - 10 + 'px'
  // this._content.style.left = position.x + 'px'
  // this._content.style.top = position.y + 'px'
}
MyInfoBox.prototype.open = function (point) {
  this._loc = point
  this.draw()
  this.show()
}

总结

记录一下学习的过程

Logo

前往低代码交流专区

更多推荐