微信小程序 - 腾讯地图使用和选点连线计算距离

2022-04-25  本文已影响0人  西半球_

GitHub Demo 地址: jh-weapp-demo 实现一些常用效果、封装通用组件和工具类

最近练习了下腾讯地图的使用,包含地图展示、定位、自定义标注和气泡、点聚合、拖动地图选点、逆地址解析、地图连线计算距离,标注编辑和删除,具体可扫码或在demo查看。

效果图

map1.png
map2.gif
map3.gif
map4.gif

实现方式

地图展示

地图使用的是腾讯地图Map组件。官方文档

逆地址解析

逆地址解析使用的是腾讯位置服务,可以通过拿到的经纬度获取到位置的一些信息

地图选点

地图选点有两种实现方式
一种是使用小程序API wx.chooseLocation
另一种通过小程序地图选点插件

根据经纬度计算距离

计算距离有两种实现方式
一种是使用腾讯位置服务的距离算法
一种根据经纬度计算地球两个经纬度的距离(haversine公式)

注:

腾讯位置服务需要注册,使用时需要在后台配置,需要下载对应js库文件,有调用频率限制
小程序插件需要在小程序后台和小程序代码配置

经纬度计算距离

//给定的经度1,纬度1;经度2,纬度2. 计算2个经纬度之间的距离。
//<returns>距离 (单位:米)</returns>
//util的方法是通过读取类文件,然后通过映射获取的,所以需要在使用的page中加入
//var util = require('../../../utils/util.js');相当于一个导入的过程。
function distance(p1, p2) {
  //用haversine公式计算球面两点间的距离。
  //经纬度转换成弧度
  var lat1 = p1.latitude * Math.PI / 180;
  var lon1 = p1.longitude * Math.PI / 180;
  var lat2 = p2.latitude * Math.PI / 180;
  var lon2 = p2.longitude * Math.PI / 180;
  //差值
  var vLon = Math.abs(lon1 - lon2);
  var vLat = Math.abs(lat1 - lat2);
  //h is the great circle distance in radians, great circle就是一个球体上的切面,它的圆心即是球心的一个周长最大的圆。
  var v = Math.sin(vLat / 2);
  var v1 = Math.sin(vLon / 2);
  var h = v * v + Math.cos(lat1) * Math.cos(lat2) * v1 * v1;
  // 地球半径 平均值,米
  var distance = 2 * 6371000 * Math.asin(Math.sqrt(h));
  return distance;
}
module.exports = {//用于映射函数
  distance: distance,
}

地图展示和逆地址解析实现代码

wxml
<!-- <map id="myMap" style="width: 100%; height: 70vh;" latitude="40.040415" longitude="116.273511" scale="17"></map> -->


<view class="map-container">
  <map id="myMap" class="map" latitude="{{latitude}}" longitude="{{longitude}}" show-location="{{isShowPosition}}" show-compass="{{isShowCompass}}" show-scale="{{isShowScale}}" markers="{{markers}}" scale="{{scale}}" bindregionchange="onMapChange" bindtap="onMapTap" bindmarkertap="onMapMarkTap" bindcallouttap="onMapCalloutTap">

    <!-- <cover-image class="centerImg" src="./images/ic_mark2.png" /> -->

    <!-- 带动画 -->
    <cover-image class="centerImg {{animation ? 'locationpicker-animation' : ''}}" src="./images/ic_mark2.png" bindanimationend="onMarkerAnimationend" />

    <cover-view class="locationBg" catchtap='onClickLocation'>
      <cover-image class="locationIcon" src="./images/ic_location.png" />
    </cover-view>

  </map>
</view>

<van-cell-group title="拖动地图选中的位置">
  <van-cell title="地址描述" label="{{address}}" />
  <van-cell title="位置描述" label="{{recommend}}" />
  <van-cell title="大致位置" label="{{rough}}" />
</van-cell-group>

<view style="padding-top: 50px;"></view>
js
var QQMapWX = require('./lib/qqmap-wx-jssdk1.2/qqmap-wx-jssdk.min.js');
var qqmapsdk;

const INIT_CALLOUT = {
  content: '天安门广场',
  padding: 10,
  display: 'ALWAYS',
  anchorY: -30,
  bgColor: '#ffffff',
  anchorY: -30,
  fontSize: 14,
  textAlign: 'center',
  // borderWidth: 2,
};

const INIT_MARKER = {
  id: 2,
  latitude: 39.903734,
  longitude: 116.397845,
  iconPath: './images/Marker3_Activated@3x.png',
  width: '34px',
  height: '34px',
  rotate: 0,
  alpha: 1,
  callout: INIT_CALLOUT,
};

Page({

  /**
   * 页面的初始数据
   */
  data: {
    latitude: 40.040415, // 纬度
    longitude: 116.273511, // 经度
    scale: 3, //默认16
    isShowScale: true,
    isShowCompass: true,
    isShowPosition: true,
    markers: [
      // 我的位置
      // {
      //   id: 0,
      //   iconPath: "./images/ic_mark1.png",
      //   latitude: 40.040415,
      //   longitude: 116.273511,
      //   width: 30,
      //   height: 30,
      //   title: '我的位置' //点击时显示,callout存在时将被忽略
      // },

      // 其它
      {
        ...INIT_MARKER
      },
      {
        id: 3,
        latitude: 40.040415,
        longitude: 116.273511,
        iconPath: './images/Marker1_Activated@3x.png',
        width: 30,
        height: 30,
        callout: {
          content: '腾讯总部大楼',
          padding: 10,
          borderRadius: 5,
          display: 'ALWAYS',
        },
        // 地图标记文字
        label: {
          content: '标记文字',
          color: '#f00',
          textAlign: 'center'
        }
      }
    ],
    animation: false,
    address: '',
    recommend: '',
    rough: '',
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

    // 实例化API核心类
    // 开发文档:https://lbs.qq.com/miniProgram/jsSdk/jsSdkGuide/jsSdkOverview
    qqmapsdk = new QQMapWX({
      key: '4M5BZ-ALKLU-BVXVL-B2HCC-DJSUS-WGFBH'
    });

    this.mapCtx = wx.createMapContext('myMap')
    // this.mapCtx.moveToLocation()
    // this.getCurrentLocation()
    // this.getCenterLngLat()

    // //更换定位图标。这个图标支持网络图片。
    // this.mapCtx.setLocMarkerIcon({
    //   iconPath: "./images/ic_mark1.png",
    // })

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },
  // 获取当前位置
  getCurrentLocation() {
    var that = this
    const lat = "markers[0].latitude";
    const log = "markers[0].longitude";
    // wgs84 返回 gps 坐标,gcj02 返回可用于 wx.openLocation 的坐标
    wx.getLocation({
      type: 'gcj02',
      success: (res) => {
        console.log('当前位置');
        console.log(res);
        that.setData({
          latitude: res.latitude,
          longitude: res.longitude,
          [lat]: res.latitude,
          [log]: res.longitude
        });
      },
      fail: (err) => {
        // wx.showToast({
        //   title: '为了不影响您的使用,请授权定位',
        //   icon: 'none'
        // })
      }
    });
  },
  // 点击定位按钮
  onClickLocation() {
    var that = this
    this.setData({
      scale: 16,
    })
    setTimeout(() => {
      that.mapCtx.moveToLocation()
    }, 500);
  },
  // 视野发生变化时触发
  // 监听拖动地图,拖动结束根据中心点更新页面
  onMapChange(e) {
    if (e.type == 'end' && (e.causedBy == 'scale' || e.causedBy == 'drag')) {
      this.getCenterLngLat()
      this.setData({
        animation: true,
      });
    }
  },
  // 点击地图
  onMapTap(e) {
    console.log('点击地图');
    console.log(e);
    wx.showToast({
      title: '点击地图',
      icon: 'none'
    })
  },
  // 点击标记点时触发,e.detail = {markerId}
  onMapMarkTap(e) {
    console.log('点击标记点');
    console.log(e);
    console.log(e.detail.markerId);
    wx.showToast({
      title: '点击标记点',
      icon: 'none'
    })
  },
  // 点击标记点对应的气泡时触发e.detail = {markerId}
  onMapCalloutTap(e) {
    console.log('点击气泡');
    console.log(e);
    console.log(e.detail.markerId);
    wx.showToast({
      title: '点击标记点对应的气泡',
      icon: 'none'
    })
  },
  onMarkerAnimationend() {
    this.setData({
      animation: false
    });
  },

  // 获取地图中心点的经纬度
  getCenterLngLat: function () {
    var that = this
    this.mapCtx.getCenterLocation({
      success: function (res) {
        console.log('获取中间点');
        console.log(res);
        that.reverseGeocoder(res.latitude, res.longitude)
      }
    })
  },
  // 逆地址解析
  reverseGeocoder(latitude, longitude) {
    var that = this
    qqmapsdk.reverseGeocoder({
      location: {
        latitude: latitude,
        longitude: longitude
      },
      success: function (res) {
        console.log('逆地址解析结果');
        console.log(res)
        var result = res.result
        console.log(result)
        // 地址描述
        let address = result.address
        // 位置描述
        let formatted_addresses = result.formatted_addresses
        // 经过腾讯地图优化过的描述方式, 更具人性化特点
        let recommend = formatted_addresses.recommend
        // 大致位置, 可用于对位置的粗略描述
        let rough = formatted_addresses.rough
        console.log(address)
        console.log(recommend)
        console.log(rough)

        that.setData({
          address: address,
          recommend: recommend,
          rough: rough,
        })
      },
    });
  }
})

地图标点连线计算距离实现代码

wxml
<view class="map-container">
  <map id="myMap" class="map" latitude="{{latitude}}" longitude="{{longitude}}" show-location="{{isShowPosition}}" show-compass="{{isShowCompass}}" show-scale="{{isShowScale}}" markers="{{markers}}" scale="{{scale}}" polygons="{{polygons}}" polyline="{{polyline}}" include-points="{{includePoints}}" enable-zoom bindregionchange="onMapChange" bindtap="onMapTap" bindmarkertap="onMapMarkTap" bindcallouttap="onMapCalloutTap">

    <!-- <cover-image class="centerImg" src="./images/ic_mark2.png" /> -->

    <cover-view class="center-bg" catchtap="onClickAddMarkerBtn">
      <cover-view class="center-top">确定打标</cover-view>
      <cover-view class="center-bottom"></cover-view>
    </cover-view>

    <!-- 定位按钮 -->
    <cover-view class="locationBg" catchtap='onClickLocation'>
      <cover-image class="locationIcon" src="./images/ic_location.png" />
    </cover-view>

    <!-- 大头针中间文字 -->
    <cover-view slot="callout">
      <cover-view wx:for="{{markers}}" wx:key='index' marker-id="{{item.id}}">
        <cover-view> {{ (item.id + 1) }}</cover-view>
      </cover-view>
    </cover-view>

  </map>

</view>
js
const util = require('./util')

Page({

  /**
   * 页面的初始数据
   */
  data: {
    latitude: 40.040415,
    longitude: 116.273511,
    scale: 16, //默认16
    isShowScale: true,
    isShowCompass: true,
    isShowPosition: true,
    markers: [
      // 我的位置
      // {
      //   id: 0,
      //   iconPath: "./images/ic_mark1.png",
      //   latitude: 40.040415,
      //   longitude: 116.273511,
      //   width: 30,
      //   height: 30,
      //   title: '我的位置' //点击时显示,callout存在时将被忽略
      // },
      // 其它
      // {
      //   id: 3,
      //   latitude: 40.040415,
      //   longitude: 116.273511,
      //   iconPath: './images/Marker1_Activated@3x.png',
      //   width: 30,
      //   height: 30,
      //   callout: {
      //     content: '腾讯总部大楼',
      //     padding: 10,
      //     borderRadius: 5,
      //     display: 'ALWAYS',
      //   },
      //   label: {
      //     content: '标记文字',
      //     color: '#f00',
      //     textAlign: 'center'
      //   }
      // }
    ],

    curPoints: [],
    polyline: '', // 路线
    polygons: '', // 多边形
    includePoints: '', // 缩放视野以包含所有给定的坐标点
    isSelectMarker: false,
    selectMarkerId: '',
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

    this.mapCtx = wx.createMapContext('myMap')
    // this.mapCtx.moveToLocation()
    // this.getCurrentLocation()
    // this.getCenterLngLat()

    // //更换定位图标。这个图标支持网络图片。
    // this.mapCtx.setLocMarkerIcon({
    //   iconPath: "./images/ic_mark1.png",
    // })

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },
  // 获取当前位置
  getCurrentLocation() {
    var that = this
    const lat = "markers[0].latitude";
    const log = "markers[0].longitude";
    // wgs84 返回 gps 坐标,gcj02 返回可用于 wx.openLocation 的坐标
    wx.getLocation({
      type: 'gcj02',
      success: (res) => {
        console.log('当前位置');
        console.log(res);
        that.setData({
          latitude: res.latitude,
          longitude: res.longitude,
          [lat]: res.latitude,
          [log]: res.longitude
        });
      },
      fail: (err) => {
        // wx.showToast({
        //   title: '为了不影响您的使用,请授权定位',
        //   icon: 'none'
        // })
      }
    });
  },
  // 点击定位按钮
  onClickLocation() {
    this.setData({
      scale: 17
    });
    setTimeout(() => {
      this.mapCtx.moveToLocation()
    }, 200);
  },
  // 视野发生变化时触发
  // 监听拖动地图,拖动结束根据中心点更新页面
  onMapChange(e) {
    if (e.type == 'end' && (e.causedBy == 'scale' || e.causedBy == 'drag')) {
      this.getCenterLngLat()
      if (this.data.isSelectMarker) {
        setTimeout(() => {
          this.moveMarker(this.data.selectMarkerId, this.data.latitude, this.data.longitude)
        }, 500);
      }
    }
  },
  // 点击地图
  onMapTap(e) {
    console.log('点击地图');
    console.log(e);
  },
  // 点击标记点时触发,e.detail = {markerId}
  onMapMarkTap(e) {
    console.log('点击标记点');
    console.log(e.detail.markerId);
    this.selectMarker(e.detail.markerId)
  },
  // 点击标记点对应的气泡时触发e.detail = {markerId}
  onMapCalloutTap(e) {
    console.log('点击气泡');
    console.log(e.detail.markerId);
    this.selectMarker(e.detail.markerId)
  },
  onClickAddMarkerBtn() {
    this.addMarker(this.data.latitude, this.data.longitude)
  },
  // 获取地图中心点的经纬度
  getCenterLngLat: function () {
    var that = this
    this.mapCtx.getCenterLocation({
      success: function (res) {
        console.log('获取中间点');
        console.log(res);
        that.setData({
          latitude: res.latitude,
          longitude: res.longitude,
        });
      }
    })
  },
  // 编辑或删除选中标记
  selectMarker(markerId) {
    let tempMarkers = this.data.markers
    tempMarkers.forEach(function (item, index) {
      item.iconPath = "./images/ic_mark1.png"
      item.isSelect = false
      if (index == markerId) {
        item.isSelect = !item.isSelect
        item.iconPath = item.isSelect ? './images/Marker1_Activated@3x.png' : "./images/ic_mark1.png"
      }
    });
    let marker = this.data.markers[markerId]
    this.setData({
      latitude: marker.latitude,
      longitude: marker.longitude,
      markers: tempMarkers,
      isSelectMarker: true,
      selectMarkerId: markerId,
    });
    var that = this
    wx.showModal({
      title: '是否删除该标记点?',
      content: '确认将删除,取消可拖动地图更新标记点位置',
      success(res) {
        if (res.confirm) {
          console.log('用户点击确定')
          that.deleteMarker(markerId)
        } else if (res.cancel) {
          console.log('用户点击取消')
        }
      }
    })
  },
  cancelMarker() {
    let tempMarkers = this.data.markers
    tempMarkers.forEach(function (item, index) {
      item.iconPath = "./images/ic_mark1.png"
      item.isSelect = false
    });
    this.setData({
      markers: tempMarkers,
      isSelectMarker: false,
      selectMarkerId: "",
    });
  },
  // 添加一个标记点
  addMarker(latitude, longitude) {
    // 判断是否存在,相同经纬度不再添加
    let isExist = false
    this.data.markers.forEach(item => {
      if (item.latitude == latitude && item.longitude == longitude) {
        console.log('已存在,相同经纬度不再添加');
        isExist = true
        return
      }
    });
    if (isExist == true) {
      return
    }
    // 不存在。新增
    var mark = new Object(); //声明一个mark对象
    mark.id = this.data.markers.length;
    mark.longitude = longitude; //经度
    mark.latitude = latitude;
    mark.iconPath = "./images/ic_mark1.png";
    mark.width = 40;
    mark.height = 40;
    // mark.label = {
    //   fontSize: 14,
    //   anchorX: 0,
    //   anchorY: -35,
    //   content: mark.id + '',
    //   textAlign: 'center',
    //   color: '#000000',
    // }
    // mark.callout = {
    //   content: '腾讯总部大楼',
    //   padding: 10,
    //   borderRadius: 5,
    //   display: 'ALWAYS',
    // }

    // 自定义气泡窗口
    mark.customCallout = {
      anchorX: 0,
      anchorY: 25,
      display: 'ALWAYS',
    }
    mark.isSelect = false

    this.data.markers.push(mark)
    this.setData({
      markers: this.data.markers
    })

    // 在data中声明一个curPoints 来记录点击所有的点,在完成绘制的时候清空点。
    this.data.curPoints.push({
      longitude: longitude,
      latitude: latitude
    })
    // 添加线上的超过一个的点,每次把距离叠加上去
    if (this.data.curPoints.length > 1) {
      // console.log(this.data.curPoints)
      // 地图上用的polyline是一个线集合对象,如果只放一条线是无法看见的。
      var pl = [{
        points: this.data.curPoints,
        color: "#0066FF",
        width: 2,
        dottedLine: false,
      }];
      //更改界面数据
      this.setData({
        polyline: pl
      })
    }
    // 计算距离
    this.calculateDistance()
  },
  // 编辑标记点(移动位置)
  moveMarker(markerId, latitude, longitude) {
    console.log('编辑标记点(移动位置)');
    this.mapCtx.translateMarker({
      // 要平移的marker的id
      markerId: markerId,
      // 移动过程中是否自动旋转 marker
      autoRotate: false,
      // 动画持续时长,平移与旋转分别计算
      duration: 10,
      // 平移到的目的地,参数为经纬度
      destination: {
        latitude: latitude,
        longitude: longitude,
      },
      //平移动画后的回调函数
      animationEnd() {}
    })

    let tempMarkers = this.data.markers
    let marker = this.data.markers[markerId]

    let curPoints = this.data.curPoints
    curPoints.forEach(function (item, index) {
      if (item.latitude == marker.latitude && item.longitude == marker.longitude) {
        curPoints.splice(index, 1, {
          longitude: longitude,
          latitude: latitude
        })
      }
    });

    tempMarkers[markerId].latitude = latitude
    tempMarkers[markerId].longitude = longitude

    // 地图上用的polyline是一个线集合对象,如果只放一条线是无法看见的。
    var pl = [{
      points: curPoints,
      color: "#0066FF",
      width: 2,
      dottedLine: false,
    }];
    this.setData({
      polyline: curPoints.length > 1 ? pl : '',
      curPoints: curPoints,
      markers: tempMarkers,
    })

    // 计算距离
    this.calculateDistance()
    // 移动结束取消选中
    this.cancelMarker()
  },
  // 删除一个标记点
  deleteMarker(markerId) {
    let tempMarkers = this.data.markers
    let delMarker = tempMarkers[markerId]
    // console.log(delMarker);
    if (tempMarkers.length > markerId) {
      // 删除
      tempMarkers.splice(markerId, 1)
      // 重新排序,设置顺序
      tempMarkers.forEach(function (item, index) {
        item.id = index
      });
      let curPoints = this.data.curPoints
      curPoints.forEach(function (item, index) {
        if (item.latitude == delMarker.latitude && item.longitude == delMarker.longitude) {
          curPoints.splice(index, 1)
        }
      });
      // 地图上用的polyline是一个线集合对象,如果只放一条线是无法看见的。
      var pl = [{
        points: curPoints,
        color: "#0066FF",
        width: 2,
        dottedLine: false,
      }];
      this.setData({
        polyline: curPoints.length > 1 ? pl : '',
        curPoints: curPoints,
        markers: tempMarkers
      })
      // 计算距离
      this.calculateDistance()
    }

  },
  // 每次更新markers之后重新计算距离
  calculateDistance() {
    // this.calculateEachDistance()
    this.calculateAllDistance()
  },
  // 计算两个点之间的距离,个数大于1每个点下都显示距离,不显示总距离
  calculateEachDistance() {
    let curPoints = this.data.curPoints
    if (curPoints.length > 1) {
      var p2 = curPoints[curPoints.length - 1]
      var p1 = curPoints[curPoints.length - 2]
      // console.log(p1);
      // console.log(p2);
      let dis = 0
      dis += util.distance(p1, p2);
      let datas = Number(dis); //转为字符串
      let datas2 = datas.toFixed(2) + "米"; //保留两位小数
      var x = -(datas2.length * 1) //设置文字向左偏移
      let label = {
        fontSize: 14,
        anchorX: x,
        anchorY: 0,
        content: datas2,
        textAlign: 'center',
        color: '#000000',
      }
      let last = "markers[" + (this.data.markers.length - 1) + "].label";
      this.setData({
        [last]: label
      })
    } else if (curPoints.length == 1) {
      let label = {
        fontSize: 14,
        anchorX: x,
        anchorY: 0,
        content: '',
        textAlign: 'center',
        color: '#000000',
      }
      this.setData({
        ['markers[0].label']: label
      })
    }
  },
  // 计算所有点之间的总距离,按添加顺序依次计算两点之间距离,并在最后一个点上显示总距离
  calculateAllDistance() {
    // 添加线上的超过一个的点,每次把距离叠加上去
    let curPoints = this.data.curPoints
    if (curPoints.length > 1) {
      let dis = 0
      curPoints.forEach(function (item, index) {
        if (index < (curPoints.length - 1)) {
          var p1 = curPoints[index]
          var p2 = curPoints[index + 1]
          dis += util.distance(p1, p2);
        }
      });
      let datas = Number(dis); //转为字符串
      let datas2 = datas.toFixed(2) + "米"; //保留两位小数
      var x = -(datas2.length * 1) //设置文字向左偏移
      let label = {
        fontSize: 14,
        anchorX: x,
        anchorY: 0,
        content: datas2,
        textAlign: 'center',
        color: '#000000',
      }
      let last2 = "markers[" + (this.data.markers.length - 2) + "].label";
      let last = "markers[" + (this.data.markers.length - 1) + "].label";
      this.setData({
        [last2]: {},
        [last]: label
      })
    } else if (curPoints.length == 1) {
      let label = {
        fontSize: 14,
        anchorX: x,
        anchorY: 0,
        content: '',
        textAlign: 'center',
        color: '#000000',
      }
      this.setData({
        ['markers[0].label']: label
      })
    }
  }

})
上一篇下一篇

猜你喜欢

热点阅读