序言
最近练习了下腾讯地图的使用,包含地图展示、定位、自定义标注和气泡、点聚合、拖动地图选点、逆地址解析、地图连线计算距离,标注编辑和删除,具体可扫码或在demo查看。
效果图
实现方式
地图展示
地图使用的是腾讯地图
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
})
}
}
})
GitHub Demo 地址:
jh-weapp-demo
实现一些常用效果、封装通用组件和工具类