业务场景
小程序打卡签到功能实现
效果预览
业务实现
前言
1、实现实时动态更新用户定位;
2、计算当前定位是否在多边形打卡区域内,判断是否可以打卡;
3、根据当前定位和目标打卡位置计算中心点位,并调整合适的比例进行缩放,使两个点位在用户首次进入应用时可同时显示,提升交互体验;
4、用户点击更新定位时,视角聚焦于当前用户定位;
业务代码
1、微信公众平台配置隐私协议
地址:微信公众平台
(1)申请接口
(2)隐私协议配置
需要配置位置隐私信息,需要审核 0.5-2 天左右
(3)小程序代码 config 接口配置
在 src/app.config.ts 页面中配置申请的三个接口
requiredPrivateInfos: [
"getLocation",
"onLocationChange",
"startLocationUpdate",
];
(4)request域名添加
在微信公众平台配置-开发管理-开发设置-服务器域名-request合法域名中添加【https://apis.map.qq.com】
2、业务代码
(1)绘制我的定位
a、获取用户当前的定位:通过getLocation获取用户当前的定位,由于用户可能会拒绝微信授权或者未开启定位功能,可以通过Taro.getSystemInfo和wx.getLocation判断是否拒绝,如果拒绝可以通过wx.getSetting引导开启
b、监听用户位置:通过wx.startLocationUpdate监听当前用户移动位置,并通过wx.onLocationChange事件获取移动位置信息,wx.stopLocationUpdate在页面关闭时停止监控
c、如果你的业务需要将经纬度转成详细位置信息,需要找包萍钢给应用和小程序申请key值,将经纬度值转成详细位置信息,具体方法参考signin函数
// 获取我的当前经纬度
const getLongitude = (type) => {
Taro.getSystemInfo({
success: (res) => {
if (!res.locationEnabled || !res.locationAuthorized) {
clearMarkers();
positionPermission.value = false;
Taro.showToast({
title: "请开启手机定位功能",
});
} else {
wx.getLocation({
isHighAccuracy: true, // 开启地图精准定位
// 高德 gcj02, 百度 wgs84
type: "gcj02", // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: (res) => {
refreshFlag.value = true;
console.log("我的位置", res);
positionPermission.value = true;
setTimeout(() => {
refreshFlag.value = false;
}, 10000);
// 添加标记点
addMarkers(res);
if (type === "refresh") {
centerLongitude.value = res.longitude;
centerLatitude.value = res.latitude;
tMap.moveToLocation({
longitude: centerLongitude.value,
latitude: centerLatitude.value,
});
scale.value = 18;
}
if (type === "once") {
getInitCenter();
}
},
fail: (err) => {
positionPermission.value = false;
wx.getSetting({
success: (res) => {
if (!res.authSetting["scope.userLocation"]) {
Taro.showModal({
content:
"检测到您未打开地理位置授权,无法使用该功能,是否前往开启",
confirmText: "去开启",
success: (res) => {
if (res.confirm) {
Taro.openSetting();
} else {
wx.showToast({
title: "您未授权位置信息,暂无法使用打卡功能",
icon: "none",
});
}
},
});
}
},
fail: (res) => {
console.log("res拒绝", res);
},
});
console.log("拒绝", err);
},
});
}
},
});
};
// 监听当前用户位置移动
onLoad: (options) => {
// 先停止监听
wx.stopLocationUpdate({
success(res) {
console.log("停止定位更新", res);
},
});
// 创建新的监听
wx.startLocationUpdate({
success() {
// locationChangeFn为绘制marker等业务
wx.onLocationChange(_this.locationChangeFn);
},
fail(err) {
console.error("定位更新失败", err);
},
});
},
onUnload() {
// 页面卸载后停止监听
wx.stopLocationUpdate({
success(res) {
console.log("停止定位更新", res);
},
});
},
(2)绘制打卡范围和打卡中心点,并计算用户当前位置是否在当前区域范围内
a、绘制打卡范围和打卡中心点:可参考代码中getAreaLine方法和addMarkers方法
b、计算当前定位是否在打卡区域内算法,通过isPointInPolygon函数返回,如果在范围内,修改我的定位图标,提示已进入范围
// point 经纬度对象,polygon多边形数组
const isPointInPolygon = (point, polygon) => {
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i].longitude,
yi = polygon[i].latitude;
const xj = polygon[j].longitude,
yj = polygon[j].latitude;
const intersect =
yi > point.latitude !== yj > point.latitude &&
point.longitude < ((xj - xi) * (point.latitude - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
};
(3)将视野调整为我的定位与打卡签到位置中间,并视野缩放为合适的比例
a、计算我的定位和打卡定位直线的中心位置,通过tMap.moveToLocation方法,将视角移动到该两点的中间点
b、计算我的定位和打卡定位中心位置的距离distance,通过算法算出合适的缩放比例,调整地图的缩放比例scale,使得我的定位和打卡定位中心位置同时在页面中合适位置
// 计算两个点之间的中心位置
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371000; // 地球半径,单位为米
const toRadians = (angle) => angle * (Math.PI / 180);
const dLat = toRadians(lat2 - lat1);
const dLon = toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRadians(lat1)) *
Math.cos(toRadians(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
// 计算缩放比例,可以自行调整
function estimateZoomLevel(distance) {
let zoomLevel;
if (distance > 1000000) {
zoomLevel = 5 - ((distance - 1000000) / 3000000) * 10;
} else if (distance > 100000 && distance <= 1000000) {
zoomLevel = 7 + ((distance - 100000) / 900000) * 6;
} else if (distance > 50000 && distance <= 100000) {
zoomLevel = 10 + (distance / 1000000) * 9;
} else if (distance > 10000 && distance <= 50000) {
zoomLevel = 11 + (distance / 1000000) * 6.5;
} else if (distance > 5000 && distance <= 10000) {
zoomLevel = 13 + (distance / 100000) * 6;
} else if (distance > 900 && distance <= 5000) {
zoomLevel = 14 + (distance / 100000) * 8;
} else if (distance > 500