整理了下日常地图开发过程中遇到的问题及解决方法,供大家参考,文章将会持续更新。

地图引入问题

网上搜索了一些资料,大部门都是index.html直接引入高德地图的js文件,个人感觉没有必要,毕竟地图只是部分页面需要使用,所以这种方法直接不考虑了。
然后又找到了一种地图懒加载的方法,需要的时候按需引入地图即可,
整理了下按需加载地图的js,我们可以新建一个js文件,如loadMap.js,位置可以随意,
这里为了引入方便直接放组件同级目录了,代码如下:

/**
 * 动态加载高德地图
 *
 * @export
 * @param {*} key 高德地图key
 * @param {*} plugins 高德地图插件
 * @param {string} [v='1.4.14'] 高德地图版本
 * @returns
 */export default function loadMap (key, plugins, v = '1.4.14') {
  return new Promise(function (resolve, reject) {
    if (typeof AMap !== 'undefined') {
      // eslint-disable-next-line no-undef
      resolve(AMap)
      return true
    }
    window.onCallback = function () {
      // eslint-disable-next-line no-undef
      resolve(AMap)
    }
    let script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = `https://webapi.amap.com/maps?v=${v}&key=${key}&plugin=${plugins}&callback=onCallback`
    script.onerror = reject
    document.head.appendChild(script)
  })
}

使用

需要用到的地方直接引用这个文件,再mounted的时候执行

loadMap(this.key, this.plugins, this.v)
  .then(AMap => {
    // 此处地图就加载成功了,然后就可以使用`new AMap.Map`来实例化地图了
    console.log('地图加载成功!')
  })
  .catch(() => {
    console.log('地图加载失败!')
  })

完整代码

为了更好的体验,这里加了一个loading动画,地图加载成功complete后取消loading效果。

<template>
  <div class="map">
    <div id="GDMap"
      v-loading="loading">
    </div>
  </div>
</template>

<script>
import loadMap from './loadMap'
export default {
  data () {
    return {
      // 地图实例
      GDMap: null,
      // 加载的一些插件
      // 更多参考:https://lbs.amap.com/api/javascript-api/guide/abc/plugins#plugins
      plugins: [
        'AMap.OverView',
        'AMap.MouseTool',
        'AMap.PolyEditor',
        'AMap.RectangleEditor',
        'AMap.PlaceSearch',
        'AMap.DistrictLayer',
        'AMap.CustomLayer'
      ],
      // key
      key: 'c5eac55551560531336988396dacbf53',
      // 地图版本
      v: '1.4.14',
      loading: true
    }
  },
  mounted () {
    loadMap(this.key, this.plugins, this.v)
      .then(AMap => {
        this.GDMap = new AMap.Map('GDMap', {
          zoom: 11,
          center: [116.397428, 39.90923]
        })
        this.GDMap.on('complete', () => {
          this.loading = false
        })
      })
      .catch(() => {
        this.loading = false
        console.log('地图加载失败!')
      })
  }
}
</script>

<style>
#GDMap {
  width: 1200px;
  height: 500px;
    position: relative;
}
</style>

效果图

demo18.gif

获取多边形编辑的点的问题

说的直白点,就是多边形在拖动点编辑过程中知道是哪个点
看了下官网的相关文档:https://lbs.amap.com/api/javascript-api/reference/plugin#AMap.PolyEditor
没找到相关的api,无奈只能自己写了。
大致思路就是多边形在编辑过程中其他的点的坐标是不变了,唯一变化的就是被编辑的点,在编辑过程中做个判断即可找到这个点的索引。
多边形在被编辑过程中是会触发change事件,所以可以利用这个事件写些判断,
在拿到这个点的索引后我们就可以干点其他事情了。

// 多边形的path
let polygonPath = polygon.getPath()
// 索引
let index
// change事件
polygon.on('change', (ev) => {
  const curPath = ev.target.getPath()
  for (let i = 0; i < path.length; i++) {
    // 判断一直在变化的点
    if (polygonPath[i].lng !== curPath[i].lng || polygonPath[i].lat !== curPath[i].lat) {
      index = i
      break
    }
  }
  polygonPath = JSON.parse(JSON.stringify(curPath))
  console.log('编辑点索引:', index)
})

完整代码

<template>
  <div class="map">
    <div id="GDMap"
      v-loading="loading">
    </div>
  </div>
</template>

<script>
import loadMap from './loadMap'
export default {
  data () {
    return {
      // 地图实例
      GDMap: null,
      // 加载的插件
      plugins: [
        'AMap.OverView',
        'AMap.MouseTool',
        'AMap.PolyEditor',
        'AMap.RectangleEditor',
        'AMap.PlaceSearch',
        'AMap.DistrictLayer',
        'AMap.CustomLayer'
      ],
      // key
      key: 'c5eac55551560531336988396dacbf53',
      // 地图版本
      v: '1.4.14',
      loading: true
    }
  },
  mounted () {
    loadMap(this.key, this.plugins, this.v)
      .then(AMap => {
        this.GDMap = new AMap.Map('GDMap', {
          zoom: 11,
          center: [116.397428, 39.90923]
        })
        this.GDMap.on('complete', () => {
          this.loading = false
        })

        const path = [
          [116.403322, 39.920255],
          [116.410703, 39.897555],
          [116.402292, 39.892353],
          [116.389846, 39.891365]
        ]
        const polygon = new AMap.Polygon({
          path: path,
          strokeColor: '#FF33FF',
          strokeWeight: 6,
          strokeOpacity: 0.2,
          fillOpacity: 0.4,
          fillColor: '#1791fc',
          zIndex: 50
        })
        // 地图添加多边形
        this.GDMap.add(polygon)
        // 缩放地图到合适的视野级别
        this.GDMap.setFitView([ polygon ])

        // 多边形编辑实例
        const polyEditor = new AMap.PolyEditor(this.GDMap, polygon)
        // 开启编辑
        polyEditor.open()

        // 多边形的path
        let polygonPath = polygon.getPath()
        // 索引
        let index
        // change事件
        polygon.on('change', (ev) => {
          const curPath = ev.target.getPath()
          for (let i = 0; i < path.length; i++) {
            // 判断一直在变化的点
            if (polygonPath[i].lng !== curPath[i].lng || polygonPath[i].lat !== curPath[i].lat) {
              index = i
              break
            }
          }
          polygonPath = JSON.parse(JSON.stringify(curPath))
          console.log('编辑点索引:', index)
        })
      })
      .catch(() => {
        this.loading = false
        console.log('地图加载失败!')
      })
  }
}
</script>

<style>
#GDMap {
  width: 1200px;
  height: 500px;
}
</style>

效果图

demo19.gif

信息窗口使用问题

看了下官网信息窗口的相关例子,发现写法都不太友好,例如以下几种形式的写法:
image.png

image.png
要是按照这种写法,vue中相关事件及数据传递怎么整,后期维护还很困难,所以这种形式直接被我pass掉了,自己写一个类似的组件,放在地图上不就行了吧,自由程度高,可定制化也高。

简单的信息窗口组件

自定义了一个简单的信息窗口组件InfoWindow,显示出来后大致就长这个样子,UI样式可以自己定义,所以很方便。
image.png

组件代码

<div id="GDMap">
  <info-window></info-window>
</div>
<template>
  <div class="info-window">
    <div class="top">
      <span class="title">标题</span>
      <span class="close">x</span>
    </div>
    <div class="content">
      我是窗口的内容
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>
.info-window {
  position: absolute;
  top: 50%;
  left: 50%;
  padding: 10px;
  min-width: 300px;
  border-radius: 5px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  background-color: #ffffff;
  z-index: 10;
  transform: translate(-50%, -50%);
}
.info-window::after {
  content: "◆";
  font-size: 36px;
  height: 24px;
  color: #ffffff;
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}

.info-window .top {
  padding-bottom: 5px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.info-window .top .title {
  font-size: 12px;
}

.info-window .top .close {
  width: 24px;
  font-size: 12px;
  cursor: pointer;
}
</style>

窗口位置

这是官网信息窗口在地图拖拽过程中的表现,会发现一直固定在某一个点相对位置
demo20.gif

再看看我们写的信息窗口组件,看到差异了吧,信息窗口被固定死了。
demo21.gif

通过审核元素你会发现官方的信息窗口的位置随着地图的拖拽在实时变化。
demo22.gif

所以加下来我们要做的就是这个。
看了下文档,找到了地图的一个方法lngLatToContainer
官方给出的描述是:地图经纬度坐标转为地图容器像素坐标
实际上窗口信息在地图上显示靠的是一个坐标点,有了这个坐标点信息窗口才知道需要哪个位置进行显示,
对于信息窗口组件来说这个坐标点就是absolute定位中的topleft,对于地图来讲就是lnglat经纬度,
所以先定一个地图的坐标点,pos: [116.397428, 39.90923],该坐标也是地图初始化时候的中心点坐标(center)
地图拖动过程中会触发mapmove事件,在该事件中,通过lngLatToContainer方法获取到pos点在地图容器的实时像素坐标,然后实时改变组件的topleft即可达到效果

相关代码

this.GDMap.on('mapmove', () => {
  let position = this.GDMap.lngLatToContainer(this.pos)
  this.$refs.infoWindow.$el.style.left = position.x + 'px'
  this.$refs.infoWindow.$el.style.top = position.y + 'px'
})

预览

这样就差不多和官方的信息窗口差不多了。
demo23.gif

代码完善

接下来我们将代码稍微完善以下,增加一个新功能,点击地图上的热点的时候,出现信息窗口,显示相应热点的信息。

代码

<template>
  <div class="map">
    <div id="GDMap"
      v-loading="loading">
      <info-window v-if="visible"
        :pos="position"
        :info="info"
        @close="visible = false">
      </info-window>
    </div>
  </div>
</template>

<script>
import loadMap from './loadMap'
import InfoWindow from './InfoWindow'
export default {
  components: {
    InfoWindow
  },
  data () {
    return {
      // 地图实例
      GDMap: null,
      // 加载的插件
      plugins: [
        'AMap.OverView',
        'AMap.MouseTool',
        'AMap.PolyEditor',
        'AMap.RectangleEditor',
        'AMap.PlaceSearch',
        'AMap.DistrictLayer',
        'AMap.CustomLayer'
      ],
      // key
      key: 'c5eac55551560531336988396dacbf53',
      // 地图版本
      v: '1.4.14',
      loading: true,
      pos: [116.397428, 39.90923],
      position: {},
      visible: false,
      info: {}
    }
  },
  mounted () {
    loadMap(this.key, this.plugins, this.v)
      .then(AMap => {
        this.GDMap = new AMap.Map('GDMap', {
          zoom: 11,
          center: [116.397428, 39.90923]
        })

        // 地图加载完成事件
        this.GDMap.on('complete', () => {
          this.loading = false
        })

        // 热点点击事件
        this.GDMap.on('hotspotclick', ev => {
          console.log(ev)
          this.visible = true
          this.pos = [ev.lnglat.lng, ev.lnglat.lat]
          this.info = ev
        })
      })
      .catch(() => {
        this.loading = false
        console.log('地图加载失败!')
      })
  },
  methods: {
    mapmove () {
      this.position = this.GDMap.lngLatToContainer(this.pos)
    }
  },
  watch: {
    pos (newPos) {
      this.position = this.GDMap.lngLatToContainer(newPos)
    },
    visible (newVisible) {
      if (this.GDMap) {
        if (newVisible) {
          // 绑定地图平移事件
          this.GDMap.on('mapmove', this.mapmove)
        } else {
          // 移除事件绑定
          this.GDMap.off('mapmove', this.mapmove)
        }
      }
    }
  }
}
</script>

<style>
#GDMap {
  width: 1200px;
  height: 500px;
  position: relative;
}
</style>

信息窗口组件

<template>
  <div class="info-window"
    ref="infoWindow">
    <div class="top">
      <span class="title">{{ info.name }}</span>
      <span class="close"
        @click="handleClose">x</span>
    </div>
    <div class="content">
      id:{{ info.id }}<br>
      type:{{ info.type }}<br>
      lnglat:{{ info.lnglat.lng }},{{ info.lnglat.lat }}<br>
    </div>
    <div class="footer">
      by: liubing.me
    </div>
  </div>
</template>

<script>
export default {
  props: {
    // 像素坐标
    pos: Object,
    // 窗口信息
    info: Object,
    // 信息窗口偏移
    offset: {
      type: Object,
      default: () => {
        return {
          x: 0,
          y: -20
        }
      }
    }
  },
  methods: {
    handleClose () {
      this.$emit('close')
    }
  },
  watch: {
    pos: {
      handler (newPos) {
        if (newPos && newPos.x && newPos.y) {
          this.$nextTick(() => {
            const infoHeight = document.querySelector('.info-window').clientHeight
            this.$refs.infoWindow.style.left = newPos.x + this.offset.x + 'px'
            this.$refs.infoWindow.style.top = newPos.y - infoHeight / 2 + this.offset.y + 'px'
          })
        }
      },
      immediate: true
    }
  }
}

</script>

<style scoped>
.info-window {
  position: absolute;
  top: 50%;
  left: 50%;
  padding: 10px;
  min-width: 300px;
  border-radius: 5px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  background-color: #ffffff;
  z-index: 10;
  transform: translate(-50%, -50%);
}
.info-window::after {
  content: "◆";
  font-size: 36px;
  height: 24px;
  color: #ffffff;
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}

.info-window .top {
  padding-bottom: 5px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.info-window .top .title {
  font-size: 12px;
}

.info-window .top .close {
  width: 24px;
  font-size: 12px;
  cursor: pointer;
}
.info-window .content {
  text-align: left;
}
.info-window .footer {
  font-size: 12px;
  color: #cccccc;
  text-align: right;
}
</style>

最终预览

最后,大家可以根据这个法子自定义出自己的信息窗口组件,而不用受高德地图信息窗口的约束了。
demo24.gif