騰訊地圖vue組件開發(fā)

背景

百度地圖插件vue-baidu-map拾取的經(jīng)緯度在小程序上顯示時存在偏差。

小程序使用的是騰訊自家的地圖。兩個地圖用的是兩套坐標系,騰訊地圖、高德地圖用的是GCJ-02坐標,也就是國測局坐標系,而百度是自成一套,BD-09坐標系,所以相同地點在經(jīng)緯度在兩個坐標系是不一樣的,或者說有偏移。

解決方案如下:

對于后臺百度地圖傳過來的經(jīng)緯度,要做一次轉(zhuǎn)換,轉(zhuǎn)換成騰訊地圖的經(jīng)緯度,再傳給小程序顯示。

function convert2TecentMap(lng, lat) {
  if (lng == '' && lat == '') {
    return {
      lng: '',
      lat: ''
    }
  }
  var x_pi = 3.14159265358979324 * 3000.0 / 180.0
  var x = lng - 0.0065
  var y = lat - 0.006
  var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi)
  var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi)
  var qqlng = z * Math.cos(theta)
  var qqlat = z * Math.sin(theta)
  return {
    lng: qqlng,
    lat: qqlat
  }
}

由于不想做轉(zhuǎn)換,所以基于騰訊地圖封裝了個vue組件。

  • 實現(xiàn)的功能:
    • 外部傳入經(jīng)緯度,地圖上顯示標記。
    • 根據(jù)關(guān)鍵詞搜索地址。
    • 點擊地圖,標記點,并顯示經(jīng)緯度、地址詳細信息。
    • 點擊搜索結(jié)果的某一項,地圖上顯示標記。
  • 效果圖:
ff7a5900-497d-11eb-97b7-0dc4655d6e68.png

組件的使用

1. 申請mapKey

  • 打開騰訊位置服務(wù)系統(tǒng),登錄/注冊賬號->右上角進入控制臺-> key管理。
  • 設(shè)置合法域名或IP。這一步很重要,否則后續(xù)的逆向解析將會提示“域名/ip未授權(quán)”?!緫?yīng)用產(chǎn)品】設(shè)置為WebServiceAPI,配置即將引入該組件的網(wǎng)站域名或ip,其他保存默認設(shè)置,單擊【保存】。
    90a27910-498f-11eb-8a36-ebb87efcf8c0.png

2. 內(nèi)部實現(xiàn)邏輯

  • mapKey:需換成您上一步申請到的key。
<template>
  <div class="well-map">
    <div class="selected-info">
      <div>
        <span class="label">經(jīng)度:</span>
        <span>{{selfSelectedValue.location.lng}}</span>
      </div>
      <div>
        <span class="label">緯度:</span>
        <span>{{selfSelectedValue.location.lat}}</span>
      </div>
      <div>
        <span class="label">地址:</span>
        <span>{{selfSelectedValue.address}}</span>
      </div>
    </div>
    <div class="search-row" v-clickoutside="handleBlur">
      <div class="input-wrap">
        <input type="text" v-model="searchKey" @input="handleSearch()" @click="handleFoucus" placeholder="請輸入要搜索的地址"/>
        <button type="button" @click="handleSearch()">搜索</button>
      </div>

      <ul v-show="showAddressList && addressList.length">
        <li v-for="(item,index) in addressList" :key="index" @click.stop="handleSelect(item)">
          <span class="title">{{item.title}}</span>
          <span class="other-info">{{item.address}}</span>
        </li>
      </ul>
    </div>
    <div id="well-container" class="well-map-container"></div>

  </div>
</template>

<script>
  /**
   * 使用:<well-tencent-map :selectedValue="selectedValue" @selected-change="mapSelectedChange"></well-tencent-map>
   * 參數(shù):
   * 1. selectedValue:可不傳、可傳null、支持異步數(shù)據(jù)。若傳入,格式如下:
   * {
   *       location: {
   *         lat: null,
   *         lng: null,
   *       },
   *       address: null,
   *       province: null,
   *       city: null,
   *       district: null
   *     }
   * 函數(shù):
   * 1. selected-change:當(dāng)選中的點發(fā)生變化時,觸發(fā)
   * **/
  import axios from 'axios';
  export default {
    props: {
      selectedValue: {
        type: Object,
        required: false
      }
    },
    data() {
      return {
        timeout: null, //搜索防抖

        selfSelectedValue: {
          location: {
            lat: null,
            lng: null,
          },
          address: null,
          province: null,
          city: null,
          district: null
        },
        searchKey: '',//搜索Key
        addressList: [],//搜索結(jié)果
        mapKey: 'JDKBZ-****',//騰訊地圖mapKey,需到https://lbs.qq.com/上申請
        map: null,
        markerLayer: null,
        showAddressList: false
      };
    },
    mounted() {

      //初始化地圖
      this.initMap();
    },
    watch: {
      selectedValue: {
        handler(newValue, oldName) {
          let value = newValue || {};
          let _newLoca = value.location || {};
          let _oldLoca = this.selfSelectedValue.location || {};
          let mergeResult = {
            location: {
              lat: _newLoca.lat,
              lng: _newLoca.lng,
            },
            address: value.address,
            province: value.province,
            city: value.city,
            district: value.district,
          };
          //判斷經(jīng)緯度是否發(fā)生變化,如果變化,則需要重新畫點
          if (_oldLoca.lat !== _newLoca.lat || _oldLoca.lng !== _newLoca.lng) {
            if (this.map && this.markerLayer) {
              //1. 地圖如果渲染完了,則把畫點畫上去
              //2. 如果沒有渲染完,也無需擔(dān)心,在init會根據(jù)selfSelectedValue的最新值繪制
              this._drawPoint(_newLoca.lat, _newLoca.lng, true);
            }
          }
          this.selfSelectedValue = mergeResult;
        },
        immediate: true
      }
    },
    directives: {

      /**
       * 封裝指令,監(jiān)聽點擊非目標元素之外的dom
       * ***/
      clickoutside: {
        bind(el, binding, vnode) {
          function documentHandler(e) {

            console.log('documentHandler');
            // 這里判斷點擊的元素是否是本身,是本身,則返回
            if (el.contains(e.target)) {
              console.log('el.contains(e.target)');
              return false;
            }
            // 判斷指令中是否綁定了函數(shù)
            if (binding.expression) {
              // 如果綁定了函數(shù) 則調(diào)用那個函數(shù),此處binding.value就是handleClose方法
              binding.value(e);
            }
          }

          // 給當(dāng)前元素綁定個私有變量,方便在unbind中可以解除事件監(jiān)聽
          el.__vueClickOutside__ = documentHandler;
          document.addEventListener('click', documentHandler);
        },
        update() {
        },
        unbind(el, binding) {
          // 解除事件監(jiān)聽
          document.removeEventListener('click', el.__vueClickOutside__);
          delete el.__vueClickOutside__;
        }
      }
    },
    methods: {
      /**
       * 初始化地圖
       * **/
      initMap() {
        //初始化地圖
        this.map = new TMap.Map('well-container', {
          rotation: 20, //設(shè)置地圖旋轉(zhuǎn)角度
          pitch: 0, //設(shè)置俯仰角度(0~45)
          zoom: 16, //設(shè)置地圖縮放級別
        });

        //初始化marker圖層
        this.markerLayer = new TMap.MultiMarker({
          id: 'marker-layer',
          map: this.map
        });

        //監(jiān)聽點擊事件添加marker
        this.map.on('click', this._clickMap);

        //初始化點(在_drawPoint中,做了經(jīng)緯度是否存在的判斷)
        let location = this.selfSelectedValue.location || {};
        this._drawPoint(location.lat, location.lng, true);

      },

      /**
       * 搜索框聚焦
       * **/
      handleFoucus(e) {
        console.log('handleFoucus');
        this.showAddressList = true;

      },
      /**
       * 搜索框失焦
       * **/
      handleBlur() {
        console.log('handleBlur');
        this.showAddressList = false;
      },
      /**
       * 搜索框內(nèi)容發(fā)生變化
       * timeout是為了防抖
       * **/
      handleSearch() {
        this.showAddressList = true;

        console.log('handleSearch');
        clearTimeout(this.timeout);

        if (!this.searchKey) {
          this.addressList = [];
        } else {
          this.timeout = setTimeout(() => {
            axios({
              url: `/wellTencentMap/ws/place/v1/suggestion?keyword=${this.searchKey}&key=${this.mapKey}`,
              method: 'GET'
            }).then(res => {
              this.addressList = (res.data && res.data.data) || [];
            }).catch(err => {
              console.log(err);
            });
          }, 300);
        }

      },

      /**
       * 選中搜索列表中的某一項,row格式如下:
       * {
       *   "id":"1594670327289385140",
       *   "title":"人才好娃幼兒園",
       *   "address":"遼寧省鐵嶺市銀州區(qū)三眼井巷1",
       *   "category":"教育學(xué)校:幼兒園",
       *   "type":0,
       *   "location":{
       *       "lat":42.292420809,
       *       "lng":123.857382405
       *   },
       *   "adcode":211202,
       *   "province":"遼寧省",
       *   "city":"鐵嶺市",
       *   "district":"銀州區(qū)"
       * }* **/
      handleSelect(row) {

        console.log(JSON.stringify(row));

        this.searchKey = row.title;
        //searchKey發(fā)生變化了,需觸發(fā)搜索
        this.handleSearch();

        let value = {
          location: row.location,
          address: row.address,
          province: row.province,
          city: row.city,
          district: row.district
        };

        this.selfSelectedValue = value;
        this._notifyParent(value);
        //selectedValue發(fā)生變化了,需重新繪制點
        this._drawPoint(row.location.lat, row.location.lng, true);

        //選中之后需觸發(fā)失去焦點
        this.handleBlur();

      },

      /**
       * 根據(jù)經(jīng)緯度在地圖上標注
       * @param lat 緯度
       * @param lng 經(jīng)度
       * @param isUpdateCenter 是否更新地圖中心點,有以下三種情況:
       * 1. 外部傳入的點信息發(fā)生變化時,需更新中心點
       * 2. 選中搜索列表的某一項時,需更新中心點
       * 3. 點擊地圖標記點時,不需要更新中心點
       * **/
      _drawPoint(lat, lng, isUpdateCenter) {

        //先清空點
        this.markerLayer.setGeometries([]);
        if (!lat || !lng) {
          return;
        }
        //更新地圖中心位置
        if (isUpdateCenter) {
          this.map.setCenter(
            new TMap.LatLng(lat, lng)
          );
        }
        this.markerLayer.add({
          position: new TMap.LatLng(lat, lng)
        });
      },
      /**
       * 通知父組件,標記的點發(fā)生了變化
       * **/
      _notifyParent(value) {
        // this.$emit('update:value', selectedValue);
        // this.$emit('update:selectedValue', selectedValue);
        this.$emit('selected-change', value);
      },

      /**
       * 點擊地圖
       * 1. 根據(jù)經(jīng)緯度畫點
       * 2, 根據(jù)經(jīng)緯度逆向查詢到地址詳細信息
       * 3. 通知父組件
       * **/
      _clickMap(evt){
        console.log('點擊地圖:', evt);
        this._drawPoint(evt.latLng.lat, evt.latLng.lng, false);

        let locationParam = evt.latLng.lat + ',' + evt.latLng.lng;
        axios({
          url: '/wellTencentMap/ws/geocoder/v1/?location=' + locationParam + '&key=' + this.mapKey + '&get_poi=0',
          method: 'GET'
        }).then(res => {
          console.log('【' + locationParam + '】逆地址解析結(jié)果:', res);


          let result = (res.data && res.data.result);
          if (!result) {
            //            this.$message.error(res.data.message);
            alert(res.data.message);
            return;
          }
          let ad_info = result.ad_info || {};

          let value = {
            location: result.location,
            address: result.address,
            province: ad_info.province,
            city: ad_info.city,
            district: ad_info.district
          };
          this.selfSelectedValue = value;

          this._notifyParent(value);

        }).catch(err => {
          console.log('逆地址解析失敗', err);
        });
      },

    }
  };
</script>

<style lang="less" scoped>
  .well-map {
    /*position: relative;*/

    line-height: normal;

    .search-row {
      position: relative;
      /*line-height: normal;*/

      width: 100%;
      margin: 12px 0 4px 0;
      /*left: 20px;*/
      /*top: 20px;*/
      z-index: 99009;

      .input-wrap {
        height: 32px;
        line-height: 32px;
        display: flex;

        > input {
          box-sizing: border-box;
          margin: 0;

          position: relative;
          width: 100%;
          height: 100%;
          padding: 4px 11px;
          color: rgba(0, 0, 0, 0.65);
          font-size: 14px;
          background-color: #fff;
          /*background-image: none;*/
          border: 1px solid #d9d9d9;
          border-radius: 4px 0 0 4px;
          transition: all 0.3s;
          border-right: none;

          &:hover {
            border-color: #40a9ff;
            border-right-width: 1px !important;
          }

          &:focus {
            border-color: #40a9ff;
            border-right-width: 1px !important;
            outline: 0;
            box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
          }
        }

        > button {

          padding: 0 15px;
          border-radius: 0 4px 4px 0;
          color: #fff;
          background: #1890ff;
          border-color: #1890ff;
          box-shadow: none;
          border: none;
          width: 82px;
          height: 100%;
          cursor: pointer;

          &:hover, &:focus {
            color: #fff;
            background: #40a9ff;
            border-color: #40a9ff;
            outline: none;
          }

          &:active {
            color: #fff;
            background: #096dd9;
            border-color: #096dd9;
            outline: none;
          }
        }
      }

      > ul {
        position: absolute;
        top: 100%;
        left: 0;
        width: 100%;
        background: rgba(252, 250, 250, 0.918);
        border: 1px solid #f1f1f1;
        font-size: 13px;
        color: #5a5a5a;
        max-height: 280px;
        overflow-y: auto;
        list-style: none;
        padding: 0;
        margin: 0;

        > li {
          text-overflow: ellipsis;
          white-space: nowrap;
          overflow: hidden;
          width: 100%;
          border-bottom: 1px solid #f1f1f1;
          padding: 10px;
          margin: 0;
          cursor: pointer;

          &:hover {
            background: #eff6fd;
          }

          .title {
            display: block;
            line-height: normal;
            margin-bottom: 4px;

          }

          .other-info {
            font-size: 12px;
            color: #b9b9b9;
            display: block;
            line-height: normal;
          }
        }
      }
    }

    .well-map-container {
      width: 100%;
      height: 300px;
    }

    .selected-info {
      background: #ecf5ff;
      padding: 10px 14px;
      color: #565656;
      font-size: 13px;

      > div {
        margin-bottom: 4px;

        .label {
          color: #757575;
        }

        &:last-child {
          margin-bottom: 0;
        }
      }
    }
  }


</style>

3. 使用

  • 引入騰訊APIjs文件。在vue項目的index.html中引入,其中key需換成第一步申請到的key。
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=JDKBZ-****"></script>
  • 設(shè)置代理。在vue.config.js文件中,添加配置。
devServer: {
    proxy: {
      "/wellTencentMap": {
        target: "https://apis.map.qq.com",
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          '^/wellTencentMap': '' //重寫接口
        }
      },
    }
  }
  • 替換mapKey,組件實現(xiàn)邏輯中的mapKey需換上面申請到的key。
data() {
  return {
    mapKey: 'JDKBZ-****',
  };
},
  • 使用組件。(此處我的組件名為well-tencent-map)
<well-tencent-map :selectedValue="selectedValue" @selected-change="mapSelectedChange"></well-tencent-map>
  • 參數(shù):
selectedValue:可不傳、可傳null、支持異步獲取回來的數(shù)據(jù)。若傳入,格式如下:
{
  location: {
    lat: null,
    lng: null,
  },
  address: null,
  province: null,
  city: null,
  district: null
}
  • 事件:
selected-change:當(dāng)標記的點發(fā)生變化時,觸發(fā)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 使用地圖選點組件引發(fā)的一系列問題: 1、選擇地址的回調(diào)路徑不兼容哈希路由 2、回調(diào)后騰訊地圖返回了完整的地址...
    Mr船長大人閱讀 1,301評論 0 2
  • Vue+ts集成騰訊地圖 背景:使用騰訊定位和展示坐標地點 ps:由于使用vue+ts開發(fā)的項目嵌入到微信公眾號選...
    嬌氣小奶奶閱讀 1,442評論 3 0
  • 中國互聯(lián)網(wǎng)地圖提供商我們估計就熟知三家:百度地圖、高德地圖、騰訊地圖。三家提供的服務(wù)其實也感覺不出來有啥各自的特色...
    CondorHero閱讀 1,801評論 0 2
  • 【 申請密鑰AndroidSDK:應(yīng)用程序包名+數(shù)字簽名 As查看數(shù)字簽名:cmd-----cd .android...
    征程_Journey閱讀 2,475評論 0 5
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學(xué)已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,868評論 16 22

友情鏈接更多精彩內(nèi)容