vue+高德地圖開發採坑持續記錄[2019-06-25]

原文地址: https://liubing.me/vue-gaode-...

整理了下平常地圖開發過程當中遇到的問題及解決方法,供你們參考,文章將會持續更新。javascript

一、地圖引入問題

網上搜索了一些資料,大部分都是index.html直接引入高德地圖的js文件,我的感受沒有必要,畢竟地圖只是部分頁面須要使用,因此這種方法直接不考慮了。
而後又找到了一種地圖懶加載的方法,須要的時候按需引入地圖便可,
整理了下按需加載地圖的js,咱們能夠新建一個js文件,如loadMap.js,位置能夠隨意,
這裏爲了引入方便直接放組件同級目錄了,代碼以下:html

/**
 * 動態加載高德地圖
 *
 * @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的時候執行vue

loadMap(this.key, this.plugins, this.v)
  .then(AMap => {
      // 此處地圖就加載成功了,而後就可使用`new AMap.Map`來實例化地圖了
    console.log('地圖加載成功!')
  })
  .catch(() => {
    console.log('地圖加載失敗!')
  })

完整代碼

爲了更好的體驗,這裏加了一個loading動畫,地圖加載成功complete後取消loading效果。java

<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事件,因此能夠利用這個事件寫些判斷,
在拿到這個點的索引後咱們就能夠乾點其餘事情了。web

// 多邊形的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.pngapi

image.png
要是按照這種寫法,vue中相關事件及數據傳遞怎麼整,後期維護還很困難,因此這種形式直接被我pass掉了,本身寫一個相似的組件,放在地圖上不就好了吧,自由程度高,可定製化也高。app

簡單的信息窗口組件

自定義了一個簡單的信息窗口組件InfoWindow,顯示出來後大體就長這個樣子,UI樣式能夠本身定義,因此很方便。
image.pngless

組件代碼

<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.gifssh

再看看咱們寫的信息窗口組件,看到差別了吧,信息窗口被固定死了。
demo21.gifide

經過審覈元素你會發現官方的信息窗口的位置隨着地圖的拖拽在實時變化。
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

四、多邊形繪製邊緣自動吸附

因爲高德自帶的繪製工具提供的事件太少,目前就一個draw事件,
徹底不知足個人需求,因此自力更生本身實現一個多邊形繪製,大體思路以下:
一、點擊繪製按鈕,將鼠標變成十字架
二、經過地圖的click事件,每點擊一次用new AMap.CircleMarker生成一個圓點
三、根據所生成的圓點座標生成一個多邊形

自定義多邊形繪製

定義數據

先在data中定義一些咱們須要的變量數據

isDrawing: false, // 是否正在繪製
polygons: [], // 多邊形集合數據
polygonsGroup: null, // 多邊形OverlayGroup集合實例,方便管理
drawingPolygon: {
  polyline: null, // 繪製過程當中的折線實例
  polylinePath: [], // 折線的path
  polygon: null, // 根據折線路徑自動生成的多邊形的實例
  pointsGroup: null, // 繪製過程當中點的集合的實例
  pointOnLine: null, // 多邊形邊上的點的實例
  isOnPolygon: null // 點是否在多邊形上
},
// 一些樣式 
styles: {
  // 圓點樣式option
  circleMarker: {
    radius: 4,
    strokeColor: '#010301',
    strokeWeight: 2,
    strokeOpacity: 1,
    fillColor: '#FFFFFF',
    fillOpacity: 1,
    bubble: true,
    cursor: 'pointer',
    clickable: true,
    zIndex: 999999
  },
  // 繪製過程當中折線樣式的option
  drawingPolyline: {
    strokeColor: '#dd9ab0',
    strokeWeight: 5,
    strokeStyle: 'dashed',
    strokeDasharray: [5, 20],
    bubble: true
  }
}

點擊地圖生成圓點

點擊相應的繪製按鈕後,點擊地圖生成一個個圓點,到時候利用這些圓點生成多邊形。
先增長個地圖點擊事件及相應的處理方法:

// 地圖點擊事件
mapOnClick(ev) {
  if (!this.isDrawing) return let position = [ev.lnglat.lng, ev.lnglat.lat] // 鼠標點擊的座標
  this.addPolygonPoint(position) // 地圖上增長相應的點
},
// 添加圍成多邊形的點
addPolygonPoint(position) {
  // 樣式及座標option
  const option = {
    ...this.styles.circleMarker,
    center: position
  }
  const circlePointMarker = new AMap.CircleMarker(option) // 單個圓點實例
  // 先判斷是否存在圓點集合實例,不存在話就建立一個
  // 全部的圓點這裏經過高德提供的OverlayGroup統一管理
  if (!this.drawingPolygon.pointsGroup) { // 若是不存在
    this.drawingPolygon.pointsGroup = new AMap.OverlayGroup() // 建立繪製過程當中點的集合的實例
    this.GDMap.add(this.drawingPolygon.pointsGroup) // 將集合添加到地圖上顯示
  }
  this.drawingPolygon.pointsGroup.addOverlay(circlePointMarker) // 將點添加到集合裏面
}

效果圖:
demo26.gif

生成繪製過程當中的鼠標移動位置的折線

繪製點擊生成圓點的同時新增繪製過程當中的折線

// 新增繪製中的折線
addDrawingPolyline(path) {
  // 判斷有無折線
  if (this.drawingPolygon.polyline) {
    this.drawingPolygon.polyline.setPath(path) // 有的話直接設置折線路徑
  } else { // 沒有的話須要建立折線
    // 折線樣式及路徑
    const option = {...this.styles.drawingPolyline,
      path: path
    }
    this.drawingPolygon.polyline = new AMap.Polyline(option) // 生成折線
    this.GDMap.add(this.drawingPolygon.polyline) // 地圖上添加折線
  }
  this.drawingPolygon.polylinePath = path // 存一下折線的路徑
},

鼠標移動過程當中實時改變繪製過程當中的折線路徑

// 地圖鼠標移動事件
mapOnMouseMove(ev) {
  if (!this.isDrawing) return
  const position = [ev.lnglat.lng, ev.lnglat.lat]
  this.setDrawingPolyline(position)
}
// 設置繪製中的折線的路徑
setDrawingPolyline (position) {
  if (this.drawingPolygon.polyline) {
    // 新增的折線的路徑,由上次記錄折線的開始點+鼠標座標位置點
    const linePath = [
      this.drawingPolygon.polylinePath[0], // 上次記錄折線的開始點
      position // 鼠標座標位置點
    ]
    this.drawingPolygon.polyline.setPath(linePath)
    this.drawingPolygon.polylinePath = linePath
  }
},

效果圖:
demo27.gif

鏈接圓點造成實時多邊形

根據上面的畫點咱們就能夠組成一個多邊形了,每次點擊的都執行這個方法生成或者修改多邊形的path

mapOnClick (ev) {
  if (!this.isDrawing) return
  let position = [ev.lnglat.lng, ev.lnglat.lat] // 鼠標點擊的座標
  this.addPolygonPoint(position) // 地圖上增長相應的點
  this.addDrawingPolyline([position, position]) // 添加繪製過程當中的鼠標移動位置的折線
  this.drawPolygonByPoints() // 經過點圍成多邊形
}
// 根據點畫區塊多邊形
drawPolygonByPoints () {
  const pointsGroup = this.drawingPolygon.pointsGroup
  const pointsLength = pointsGroup ? pointsGroup.getOverlays().length : 0
  if (pointsLength > 1) {
    const paths = pointsGroup.getOverlays().map(item => {
      const path = item.getCenter()
      return [path.lng, path.lat]
    })
    if (this.drawingPolygon.polygon) {
      this.drawingPolygon.polygon.setPath(paths)
    } else {
      const option = {
        ...this.styles.polygon,
        path: paths
      }
      this.drawingPolygon.polygon = new AMap.Polygon(option)
      this.drawingPolygon.polygon.setMap(this.GDMap)
    }
  }
}

效果圖:
demo28.gif

雙擊繪製完成

經過高德提供的dblclick雙擊事件來執行complateDraw完成繪製,
繪製完成的時候獲取到drawingPolygonpolygon的path,將該path存起來,
經過該path在地圖上添加新繪製的多邊形
最後將以前的一些數據清理掉,準備下次的繪製。

// 繪製完成
complateDraw () {
  const paths = this.drawingPolygon.polygon.getPath().map(item => [item.lng, item.lat]) // 繪製完成的多邊形path
  this.isDrawing = false // 取消繪製
  this.polygons.push(paths) // 添加繪製的多邊形
  this.addPolygon(paths) // 地圖上添加繪製的多邊形
  this.clearUselessOverlays() // 清理數據
},
// 清理無用的圖層
clearUselessOverlays () {
  if (!this.GDMap) return
  this.GDMap.remove(this.drawingPolygon.polyline)
  this.GDMap.remove(this.drawingPolygon.polygon)
  this.GDMap.remove(this.drawingPolygon.pointsGroup)
  this.drawingPolygon.polyline = null
  this.drawingPolygon.polygon = null
  this.drawingPolygon.pointsGroup = null
  this.drawingPolygon.polylinePath = []
},
// 添加多邊形
addPolygon (paths) {
  if (!this.GDMap) return
  const option = {
    ...this.styles.polygon,
    path: paths
  }
  const polygon = new AMap.Polygon(option)
  if (!this.polygonsGroup) {
    this.polygonsGroup = new AMap.OverlayGroup()
    this.GDMap.add(this.polygonsGroup)
  }
  this.polygonsGroup.addOverlay(polygon)
}

效果圖:
demo29.gif

現有問題

上面實現了自定義的多邊形繪製,可是存在一個問題,畫圖的時候雖然是對準這邊緣畫的,
可是放大後會發現有縫隙或者重疊現象,以下圖所示:
demo31.gif

線邊緣自動吸附

提及來有點繁瑣,有時間再寫,先直接上完整代碼,代碼有待優化,先實現功能:

<template>
  <div class="map">
    {{this.polygons}}
    <button type="primary"
      @click="drawPolygon">畫多邊形</button>
    <div id="GDMap">
    </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,
      // 多邊形集合
      polygons: [
        [
          [ 116.402921, 39.984507 ],
          [ 116.531324, 39.938719 ],
          [ 116.525144, 39.858648 ],
          [ 116.423521, 39.821742 ]
        ]
      ],
      polygonsGroup: null, // 多邊形OverlayGroup集合實例,方便管理
      isDrawing: false, // 是否正在繪製
      drawingPolygon: {
        polyline: null, // 繪製過程當中的折線實例
        polylinePath: [], // 折線的path
        polygon: null, // 根據折線路徑自動生成的多邊形的實例
        pointsGroup: null, // 繪製過程當中點的集合的實例
        pointOnLine: null, // 多邊形邊上的點的實例
        isOnPolygon: null // 點是否在多邊形上
      },
      styles: {
        // 圓點option
        circleMarker: {
          radius: 4,
          strokeColor: '#010301',
          strokeWeight: 2,
          strokeOpacity: 1,
          fillColor: '#FFFFFF',
          fillOpacity: 1,
          bubble: true,
          cursor: 'pointer',
          clickable: true,
          zIndex: 999999
        },
        // 繪製過程當中折線的option
        drawingPolyline: {
          strokeColor: '#dd9ab0',
          strokeWeight: 5,
          strokeStyle: 'dashed',
          strokeDasharray: [5, 20],
          bubble: true
        },
        polygon: {
          fillColor: '#DC3021', // 填充色
          fillOpacity: 0.2, // 填充透明度
          strokeColor: '#DC3021', // 輪廓顏色
          strokeWeight: 1, // 輪廓寬度
          strokeOpacity: 0.9 // 輪廓透明度
        }
      }
    }
  },
  mounted () {
    loadMap(this.key, this.plugins, this.v)
      .then(AMap => {
        this.GDMap = new AMap.Map('GDMap', {
          zoom: 11,
          center: [116.397428, 39.90923],
          isHotspot: false
        })
        // 綁定地圖單擊事件
        this.GDMap.on('click', this.mapOnClick)
        // 地圖雙擊事件
        this.GDMap.on('dblclick', this.mapOnDblclick)
        // 綁定地圖鼠標移動事件
        this.GDMap.on('mousemove', this.mapOnMouseMove)

        this.GDMap.on('complete', () => {
          // 地圖加載完成後初始化已有的多邊形
          this.polygons.forEach(polygon => {
            this.addPolygon(polygon)
          })
        })
      })
      .catch(() => {
        console.log('地圖加載失敗!')
      })
  },
  methods: {
    drawPolygon () {
      this.isDrawing = true
    },
    // 地圖點擊事件
    mapOnClick (ev) {
      if (!this.isDrawing) return
      let position = [ev.lnglat.lng, ev.lnglat.lat] // 鼠標點擊的座標
      // 判斷是否存在存在線上圓點實例及是否在線上
      if (this.drawingPolygon.isOnPolygon && this.drawingPolygon.pointOnLine) {
        const center = this.drawingPolygon.pointOnLine.getCenter() // 獲取線上圓點的中心
        position = [center.lng, center.lat]
      }
      this.addPolygonPoint(position) // 地圖上增長相應的點
      this.addDrawingPolyline([position, position]) // 添加繪製過程當中的鼠標移動位置的折線
      this.drawPolygonByPoints() // 經過點圍成多邊形
    },
    // 地圖雙擊事件
    mapOnDblclick (ev) {
      if (!this.isDrawing) return
      this.complateDraw()
    },
    // 地圖鼠標移動事件
    mapOnMouseMove (ev) {
      if (!this.isDrawing) return
      const position = [ev.lnglat.lng, ev.lnglat.lat]
      const linePath = this.getPointLine(position) // 獲取點所在的線
      let recentPoint = null // 定義最近的點
      if (this.drawingPolygon.isOnPolygon) {
        // 若是點在線上(這個點存在偏移,因此還得經過getRecentPoint獲取到正在的最近的一個點)
        // 獲取該點到線的最近的一個點
        recentPoint = this.getRecentPoint(linePath, position)
        this.addDrawingOnLinePoint(recentPoint) // 在線上添加圓點,也就是自動吸附的時候建立的點
      } else {
        this.removeDrawingOnLinePoint()
      }
      this.setDrawingPolyline(position)
    },
    // 添加圍成多邊形的點
    addPolygonPoint (position) {
      // 樣式及座標option
      const option = {
        ...this.styles.circleMarker,
        center: position
      }
      const circlePointMarker = new AMap.CircleMarker(option) // 單個圓點實例
      // 先判斷是否存在圓點集合實例
      // 全部的圓點這裏經過高德提供的OverlayGroup統一管理
      if (!this.drawingPolygon.pointsGroup) { // 若是不存在
        this.drawingPolygon.pointsGroup = new AMap.OverlayGroup() // 建立繪製過程當中點的集合的實例
        this.GDMap.add(this.drawingPolygon.pointsGroup) // 將集合添加到地圖上顯示
      }
      this.drawingPolygon.pointsGroup.addOverlay(circlePointMarker) // 將點添加到集合裏面
    },
    // 新增繪製中的折線
    addDrawingPolyline (paths) {
      // 判斷有無折線
      if (this.drawingPolygon.polyline) {
        this.drawingPolygon.polyline.setPath(paths) // 有的話直接設置折線路徑
      } else { // 沒有的話須要建立折線
        // 折線樣式及路徑
        const option = {
          ...this.styles.drawingPolyline,
          path: paths
        }
        this.drawingPolygon.polyline = new AMap.Polyline(option) // 生成折線
        this.GDMap.add(this.drawingPolygon.polyline) // 地圖上添加折線
      }
      this.drawingPolygon.polylinePath = paths // 存一下折線的路徑
    },
    // 設置繪製中的折線的路徑
    setDrawingPolyline (position) {
      if (this.drawingPolygon.polyline) {
        // 新增的折線的路徑,由上次記錄折線的開始點+鼠標座標位置點
        const linePath = [
          this.drawingPolygon.polylinePath[0], // 上次記錄折線的開始點
          position // 鼠標座標位置點
        ]
        this.drawingPolygon.polyline.setPath(linePath)
        this.drawingPolygon.polylinePath = linePath
      }
    },
    // 根據點畫區塊多邊形
    drawPolygonByPoints () {
      const pointsGroup = this.drawingPolygon.pointsGroup // 點及的集合實例
      const pointsLength = pointsGroup ? pointsGroup.getOverlays().length : 0 // 點的長度
      if (pointsLength > 2) {
        // 獲取每一個點的中心點組成path
        const paths = pointsGroup.getOverlays().map(item => {
          const path = item.getCenter()
          return [path.lng, path.lat]
        })
        // 地圖上繪製多邊形
        if (this.drawingPolygon.polygon) {
          this.drawingPolygon.polygon.setPath(paths)
        } else {
          const option = {
            ...this.styles.polygon,
            path: paths
          }
          this.drawingPolygon.polygon = new AMap.Polygon(option)
          this.drawingPolygon.polygon.setMap(this.GDMap)
        }
      }
    },
    // 繪製完成
    complateDraw () {
      const paths = this.drawingPolygon.polygon.getPath().map(item => [item.lng, item.lat]) // 繪製完成的多邊形path
      this.isDrawing = false // 取消繪製
      this.polygons.push(paths) // 添加繪製的多邊形
      this.addPolygon(paths) // 地圖上添加繪製的多邊形
      this.clearUselessOverlays() // 清理數據
    },
    // 清理無用的圖層
    clearUselessOverlays () {
      if (!this.GDMap) return
      this.GDMap.remove(this.drawingPolygon.polyline)
      this.GDMap.remove(this.drawingPolygon.polygon)
      this.GDMap.remove(this.drawingPolygon.pointsGroup)
      if (this.drawingPolygon.pointOnLine) {
        this.GDMap.remove(this.drawingPolygon.pointOnLine)
      }
      this.drawingPolygon.polyline = null
      this.drawingPolygon.polygon = null
      this.drawingPolygon.pointsGroup = null
      this.drawingPolygon.polylinePath = []
      this.drawingPolygon.isOnPolygon = false
    },
    // 添加多邊形
    addPolygon (paths) {
      if (!this.GDMap) return
      const option = {
        ...this.styles.polygon,
        path: [...paths]
      }
      const polygon = new AMap.Polygon(option)
      if (!this.polygonsGroup) {
        this.polygonsGroup = new AMap.OverlayGroup()
        this.GDMap.add(this.polygonsGroup)
      }
      this.polygonsGroup.addOverlay(polygon)
    },
    // 添加多邊形邊線上的點
    addDrawingOnLinePoint (center) {
      if (this.drawingPolygon.pointOnLine) {
        this.drawingPolygon.pointOnLine.setCenter(center)
        return
      }
      const option = {
        map: this.GDMap,
        center: center,
        ...this.styles.circleMarker
      }
      this.drawingPolygon.pointOnLine = new AMap.CircleMarker(option)
    },
    // 移除多邊形線上的點
    removeDrawingOnLinePoint () {
      if (this.drawingPolygon.pointOnLine) {
        this.GDMap.remove(this.drawingPolygon.pointOnLine)
        this.drawingPolygon.pointOnLine = null
      }
    },
    // 獲取點所在的線
    getPointLine (position) {
      const resolution = this.GDMap.getResolution()// 獲取指定位置的地圖分辨率,單位:米/像素
      const pointWidth = 6 * resolution// 線段上圓點的寬度,也就是偏差
      let linePath = []
      this.drawingPolygon.isOnPolygon = false // 默認點不在線上
      // 循環全部的多邊形,取到全部的線一一比較,存在性能問題
      for (let i = 0; i < this.polygons.length; i++) {
        const itemPath = this.polygons[i]
        let hasFind = false
        for (let n = 0; n < itemPath.length; n++) {
          const path = itemPath[n]
          const nextPath = itemPath[n + 1]
          const line = nextPath ? [path, nextPath] : [path, itemPath[0]]
          // 高德地圖提供的isPointOnSegment判斷點是否在線段上
          const isPointOnSegment = AMap.GeometryUtil.isPointOnSegment(position, line[0], line[1], pointWidth)
          if (isPointOnSegment) {
            linePath = line
            this.drawingPolygon.isOnPolygon = true
            hasFind = true
            break
          }
        }
        if (hasFind) break
      }
      return linePath
    },
    // 獲取最近的點的座標
    getRecentPoint (paths, curPointPosition) {
      const recentPoint = AMap.GeometryUtil.closestOnLine(curPointPosition, paths)
      return recentPoint
    }

  },
  watch: {
    isDrawing (newState) {
      if (newState) {
        this.GDMap.setDefaultCursor('crosshair')
      } else {
        this.GDMap.setDefaultCursor('')
      }
      this.GDMap.setStatus({
        doubleClickZoom: !newState
      })
    }
  }
}
</script>

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

最終效果圖

這樣畫出的來的多邊形邊緣就不會存在縫隙或者重疊的現象了。
demo32.gif
demo33.gif

在線預覽

https://jsrun.net/jdyKp/edit

相關文章
相關標籤/搜索