美文网首页
腾讯地图vue组件开发

腾讯地图vue组件开发

作者: 一只佛手 | 来源:发表于2020-12-29 10:24 被阅读0次

    背景

    百度地图插件vue-baidu-map拾取的经纬度在小程序上显示时存在偏差。

    小程序使用的是腾讯自家的地图。两个地图用的是两套坐标系,腾讯地图、高德地图用的是GCJ-02坐标,也就是国测局坐标系,而百度是自成一套,BD-09坐标系,所以相同地点在经纬度在两个坐标系是不一样的,或者说有偏移。

    解决方案如下:

    对于后台百度地图传过来的经纬度,要做一次转换,转换成腾讯地图的经纬度,再传给小程序显示。

    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
      }
    }
    

    由于不想做转换,所以基于腾讯地图封装了个vue组件。

    • 实现的功能:
      • 外部传入经纬度,地图上显示标记。
      • 根据关键词搜索地址。
      • 点击地图,标记点,并显示经纬度、地址详细信息。
      • 点击搜索结果的某一项,地图上显示标记。
    • 效果图:
    ff7a5900-497d-11eb-97b7-0dc4655d6e68.png

    组件的使用

    1. 申请mapKey

    • 打开腾讯位置服务系统,登录/注册账号->右上角进入控制台-> key管理。
    • 设置合法域名或IP。这一步很重要,否则后续的逆向解析将会提示“域名/ip未授权”。【应用产品】设置为WebServiceAPI,配置即将引入该组件的网站域名或ip,其他保存默认设置,单击【保存】。
      90a27910-498f-11eb-8a36-ebb87efcf8c0.png

    2. 内部实现逻辑

    • mapKey:需换成您上一步申请到的key。
    <template>
      <div class="well-map">
        <div class="selected-info">
          <div>
            <span class="label">经度:</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>
       * 参数:
       * 1. selectedValue:可不传、可传null、支持异步数据。若传入,格式如下:
       * {
       *       location: {
       *         lat: null,
       *         lng: null,
       *       },
       *       address: null,
       *       province: null,
       *       city: null,
       *       district: null
       *     }
       * 函数:
       * 1. selected-change:当选中的点发生变化时,触发
       * **/
      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: [],//搜索结果
            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,
              };
              //判断经纬度是否发生变化,如果变化,则需要重新画点
              if (_oldLoca.lat !== _newLoca.lat || _oldLoca.lng !== _newLoca.lng) {
                if (this.map && this.markerLayer) {
                  //1. 地图如果渲染完了,则把画点画上去
                  //2. 如果没有渲染完,也无需担心,在init会根据selfSelectedValue的最新值绘制
                  this._drawPoint(_newLoca.lat, _newLoca.lng, true);
                }
              }
              this.selfSelectedValue = mergeResult;
            },
            immediate: true
          }
        },
        directives: {
    
          /**
           * 封装指令,监听点击非目标元素之外的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;
                }
                // 判断指令中是否绑定了函数
                if (binding.expression) {
                  // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
                  binding.value(e);
                }
              }
    
              // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
              el.__vueClickOutside__ = documentHandler;
              document.addEventListener('click', documentHandler);
            },
            update() {
            },
            unbind(el, binding) {
              // 解除事件监听
              document.removeEventListener('click', el.__vueClickOutside__);
              delete el.__vueClickOutside__;
            }
          }
        },
        methods: {
          /**
           * 初始化地图
           * **/
          initMap() {
            //初始化地图
            this.map = new TMap.Map('well-container', {
              rotation: 20, //设置地图旋转角度
              pitch: 0, //设置俯仰角度(0~45)
              zoom: 16, //设置地图缩放级别
            });
    
            //初始化marker图层
            this.markerLayer = new TMap.MultiMarker({
              id: 'marker-layer',
              map: this.map
            });
    
            //监听点击事件添加marker
            this.map.on('click', this._clickMap);
    
            //初始化点(在_drawPoint中,做了经纬度是否存在的判断)
            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;
          },
          /**
           * 搜索框内容发生变化
           * 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":"辽宁省铁岭市银州区三眼井巷1",
           *   "category":"教育学校:幼儿园",
           *   "type":0,
           *   "location":{
           *       "lat":42.292420809,
           *       "lng":123.857382405
           *   },
           *   "adcode":211202,
           *   "province":"辽宁省",
           *   "city":"铁岭市",
           *   "district":"银州区"
           * }* **/
          handleSelect(row) {
    
            console.log(JSON.stringify(row));
    
            this.searchKey = row.title;
            //searchKey发生变化了,需触发搜索
            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发生变化了,需重新绘制点
            this._drawPoint(row.location.lat, row.location.lng, true);
    
            //选中之后需触发失去焦点
            this.handleBlur();
    
          },
    
          /**
           * 根据经纬度在地图上标注
           * @param lat 纬度
           * @param lng 经度
           * @param isUpdateCenter 是否更新地图中心点,有以下三种情况:
           * 1. 外部传入的点信息发生变化时,需更新中心点
           * 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)
            });
          },
          /**
           * 通知父组件,标记的点发生了变化
           * **/
          _notifyParent(value) {
            // this.$emit('update:value', selectedValue);
            // this.$emit('update:selectedValue', selectedValue);
            this.$emit('selected-change', value);
          },
    
          /**
           * 点击地图
           * 1. 根据经纬度画点
           * 2, 根据经纬度逆向查询到地址详细信息
           * 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 + '】逆地址解析结果:', 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>
    
    • 设置代理。在vue.config.js文件中,添加配置。
    devServer: {
        proxy: {
          "/wellTencentMap": {
            target: "https://apis.map.qq.com",
            changeOrigin: true,
            ws: true,
            pathRewrite: {
              '^/wellTencentMap': '' //重写接口
            }
          },
        }
      }
    
    • 替换mapKey,组件实现逻辑中的mapKey需换上面申请到的key。
    data() {
      return {
        mapKey: 'JDKBZ-****',
      };
    },
    
    • 使用组件。(此处我的组件名为well-tencent-map)
    <well-tencent-map :selectedValue="selectedValue" @selected-change="mapSelectedChange"></well-tencent-map>
    
    • 参数:
    selectedValue:可不传、可传null、支持异步获取回来的数据。若传入,格式如下:
    {
      location: {
        lat: null,
        lng: null,
      },
      address: null,
      province: null,
      city: null,
      district: null
    }
    
    • 事件:
    selected-change:当标记的点发生变化时,触发。
    

    相关文章

      网友评论

          本文标题:腾讯地图vue组件开发

          本文链接:https://www.haomeiwen.com/subject/cxqfoktx.html