最近写了一个类似校园跑小程序,分享一下
效果如下:
先附上完整代码:
<template>
<view class="text-area">
<!-- <button @tap="getwz">点我获取当前位置</button> -->
<map class="map" :longitude="longitude" :latitude="latitude" :markers="marker" :polyline="polyline"></map>
<view class="block">
<view class="but" @click="startRun" :class="{ 'run-flag-active': runFlag }">
{{ content }}
</view>
<view class="msg">
<view class="speed">
平均速度:{{speed}} m/s
</view>
<view class="kilo">
公里:{{kilo}} km
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
longitude: 107.701594,
latitude: 29.615453,
marker: [],
polyline: [{ //指定一系列坐标点,从数组第一项连线至最后一项
points: [{
longitude: 107.701594,
latitude: 29.615453
}, {
longitude: 107.70048,
latitude: 29.617
}],
color: "#0000AA", //线的颜色
width: 1, //线的宽度
// dottedLine:true,//是否虚线
}],
// 跑步标致,为true时停止跑步
runFlag: true,
content: "开始运动",
speed: 0,
kilo: 0,
startTime: null, // 运动开始时间
totalElapsedTime: 0, // 累计行驶时间(单位:秒)
}
},
onLoad() {
var that = this
wx.authorize({
scope: 'scope.userLocation',
success(res) {
console.log(res)
if (res.errMsg == 'authorize:ok') {
wx.getLocation({
type: 'gcj02',
success(res) {
console.log(res) //此时里面有经纬度
// 设置中心点位
that.longitude = res.longitude;
that.latitude = res.latitude;
// 添加至标记点位
that.marker.push({
id: 0, // 保证ID唯一
longitude: res.longitude,
latitude: res.latitude,
iconPath: '../../static/img/map/marker_checked.png',
rotate: 0,
width: 10,
height: 20,
title: '当前位置',
});
}
})
}
},
fail(err) {
console.log(err)
}
})
},
methods: {
getwz() {
var that = this
wx.getLocation({
type: 'gcj02',
isHighAccuracy: true,
highAccuracyExpireTime: 3500,
success(res) {
console.log(res);
// 设置中心点位
that.longitude = res.longitude;
that.latitude = res.latitude;
// 修改标记点位
that.marker[0].longitude = res.longitude;
that.marker[0].latitude = res.latitude;
// 加入轨迹折线
that.polyline[0].points.push({
longitude: res.longitude,
latitude: res.latitude,
});
// 计算与上一点的距离并累加到总路程
// 计算距离和速度
const distance = that.calculateDistance(
that.polyline[0].points[that.polyline[0].points.length - 2], {
longitude: res.longitude,
latitude: res.latitude
}
);
// 更新总路程(保留三位有效数字)
that.kilo = (parseFloat(that.kilo) + distance).toFixed(3);
// 设置速度的实时显示(基于单次位置更新的瞬时速度)
// 判断速度是否有效(非Infinity和NaN),有效则更新速度显示
var nowTime = new Date().getTime(); // 获取当前时间
var timeSpace = nowTime - that.startTime; // 单位:毫秒
var speedRes = that.kilo * 1000 * 1000 / timeSpace; // 单位:m/s
if (!isNaN(speedRes) && isFinite(speedRes)) {
that.speed = speedRes.toFixed(3)
}
},
fail(info) {
console.log(info);
}
})
},
// 开始运动
startRun() {
// ... 原有startRun方法内容 ...
var that = this
if (that.runFlag) {
wx.showModal({
content: '是否开始运动',
success(res) {
if (res.confirm) {
// console.log('用户点击确定')
// 开始运动
that.content = "停止运动"
that.runFlag = !that.runFlag;
// 获取位置
wx.getLocation({
type: 'gcj02',
isHighAccuracy: true,
highAccuracyExpireTime: 3500,
success(res) {
// 设置中心点位
that.longitude = res.longitude;
that.latitude = res.latitude;
// 修改标记点位
that.marker[0].longitude = res.longitude;
that.marker[0].latitude = res.latitude;
// 修改初始轨迹折线起点
that.polyline[0].points[0].longitude = res.longitude,
that.polyline[0].points[0].latitude = res.latitude,
that.polyline[0].points[1].longitude = res.longitude,
that.polyline[0].points[1].latitude = res.latitude
},
fail(info) {
console.log(info);
}
});
// 或当前时间
that.startTime = new Date().getTime();
// 定时器跑步
that.locationTimer = setInterval(() => {
that.getwz();
}, 8000); // 开始跑步时重新开启定时器
} else if (res.cancel) {
// console.log('用户点击取消')
}
}
})
} else {
wx.showModal({
content: '是否停止运动',
success(res) {
if (res.confirm) {
// console.log('用户点击确定')
// 停止运动
that.content = "开始运动"
that.runFlag = !that.runFlag;
clearInterval(that.locationTimer); // 结束跑步时清除定时器
} else if (res.cancel) {
// console.log('用户点击取消')
}
}
})
}
},
// 计算路径长度
calculateDistance(prevPoint, currentPoint) {
const R = 6371; // 地球平均半径,单位为公里
const dLat = (currentPoint.latitude - prevPoint.latitude) * Math.PI / 180;
const dLon = (currentPoint.longitude - prevPoint.longitude) * Math.PI / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(prevPoint.latitude * Math.PI / 180) *
Math.cos(currentPoint.latitude * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
let distance = R * c;
// 将距离保留三位小数
distance = parseFloat(distance.toFixed(3));
return distance;
// 计算当前时间并累计行驶时间
const now = new Date().getTime();
// 计算平均速度(单位:m/s)
let speed = distance * 1000 / elapsedTime;
speed = parseFloat(speed.toFixed(3)); // 保留三位小数
},
},
// 不再使用时,记得清除定时器,防止内存泄漏
onUnload() {
clearInterval(this.locationTimer);
},
}
</script>
<style lang="scss">
.map {
width: 100%;
height: 650rpx;
}
.block {
width: 95%;
height: 300rpx;
// background-color: #3c9cff;
border: 2rpx solid #3c9cff;
border-radius: 20rpx;
margin: 20rpx 20rpx;
display: flex;
align-items: center;
.but {
width: 50%;
height: 100%;
background-color: red;
border-radius: 20rpx;
border-right: 2rpx solid white;
display: flex;
align-items: center;
justify-content: center;
text-align: left;
font-size: 50rpx;
font-weight: 700;
color: white;
}
/* 当runFlag为true时应用此样式 */
.but.run-flag-active {
background-color: #3c9cff;
}
.msg {
width: 50%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.speed {
width: 90%;
height: 50%;
background-color: white;
border-bottom: 2rpx solid #3c9cff;
padding-left: 30rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
text-align: left;
font-size: 40rpx;
font-weight: 550;
}
.kilo {
width: 90%;
height: 50%;
background-color: white;
padding-left: 30rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
text-align: left;
font-size: 40rpx;
font-weight: 550;
}
}
}
</style>
总体来说,代码还是很简单的。
使用到wx.getLocation接口(如果项目上线的话需要去官网申请,不上线可以随便用),获取精确的当前定位信息。
代码的简单讲解:
小程序官方的地图组件。
各个属性讲解:longitude是经度、latitude是纬度,这里的经纬度是当前地图定位的中心点位;markers是标记点位,也就是经过的点位;polyline是一系列的点位,用于绘制运动轨迹。
<map class="map" :longitude="longitude" :latitude="latitude" :markers="marker" :polyline="polyline"></map>
也就是说,运动轨迹是由很多标记点组成的,接口请求越快,点位就越密集,绘制出的轨迹就越准确。
具体实现分析:
第一次点击“开始运动”后,要将中心点位定位到当前位置,后续运动时,用户移动改变位置,需要一次次地调用接口获取当前位置信息,设置到中心地位、标记点、轨迹绘制中。
【注意】用户运动的当前位置在轨迹上表现就是轨迹的末端。
此外,关于定位信息的授权需要在manifest.json中进行说明,否则在运行时会闪退。下面进行代码详解:
data中要特别注意polyline起始数值必须包含两个起始的点位,否则在小程序中运行会报错。
data() {
return {
longitude: 107.701594,
latitude: 29.615453,
marker: [],
polyline: [{ //指定一系列坐标点,从数组第一项连线至最后一项
points: [{
longitude: 107.701594,
latitude: 29.615453
}, {
longitude: 107.70048,
latitude: 29.617
}],
color: "#0000AA", //线的颜色
width: 1, //线的宽度
// dottedLine:true,//是否虚线
}],
// 跑步标致,为true时停止跑步
runFlag: true,
content: "开始运动",
speed: 0,
kilo: 0,
startTime: null, // 运动开始时间
totalElapsedTime: 0, // 累计行驶时间(单位:秒)
}
},
在onLoad中提示用户获取定位权限,并获取用户当前初始的中心点位、加入标记点。
onLoad() {
var that = this
wx.authorize({
scope: 'scope.userLocation',
success(res) {
console.log(res)
if (res.errMsg == 'authorize:ok') {
wx.getLocation({
type: 'gcj02',
success(res) {
console.log(res) //此时里面有经纬度
// 设置中心点位
that.longitude = res.longitude;
that.latitude = res.latitude;
// 添加至标记点位
that.marker.push({
id: 0, // 保证ID唯一
longitude: res.longitude,
latitude: res.latitude,
iconPath: '../../static/img/map/marker_checked.png',
rotate: 0,
width: 10,
height: 20,
title: '当前位置',
});
}
})
}
},
fail(err) {
console.log(err)
}
})
},
mehods里的:开始运动和正式跑步的函数,开始跑步需要再定位一次,因为用户可能打开应用后不会立即进行跑步。开始跑步后也是发请求、对那三个数据赋值:中心点位、标记点、轨迹折线。
getwz() {
var that = this
wx.getLocation({
type: 'gcj02',
isHighAccuracy: true,
highAccuracyExpireTime: 3500,
success(res) {
console.log(res);
// 设置中心点位
that.longitude = res.longitude;
that.latitude = res.latitude;
// 修改标记点位
that.marker[0].longitude = res.longitude;
that.marker[0].latitude = res.latitude;
// 加入轨迹折线
that.polyline[0].points.push({
longitude: res.longitude,
latitude: res.latitude,
});
// 计算与上一点的距离并累加到总路程
// 计算距离和速度
const distance = that.calculateDistance(
that.polyline[0].points[that.polyline[0].points.length - 2], {
longitude: res.longitude,
latitude: res.latitude
}
);
// 更新总路程(保留三位有效数字)
that.kilo = (parseFloat(that.kilo) + distance).toFixed(3);
// 设置速度的实时显示(基于单次位置更新的瞬时速度)
// 判断速度是否有效(非Infinity和NaN),有效则更新速度显示
var nowTime = new Date().getTime(); // 获取当前时间
var timeSpace = nowTime - that.startTime; // 单位:毫秒
var speedRes = that.kilo * 1000 * 1000 / timeSpace; // 单位:m/s
if (!isNaN(speedRes) && isFinite(speedRes)) {
that.speed = speedRes.toFixed(3)
}
},
fail(info) {
console.log(info);
}
})
},
// 开始运动
startRun() {
// ... 原有startRun方法内容 ...
var that = this
if (that.runFlag) {
wx.showModal({
content: '是否开始运动',
success(res) {
if (res.confirm) {
// console.log('用户点击确定')
// 开始运动
that.content = "停止运动"
that.runFlag = !that.runFlag;
// 获取位置
wx.getLocation({
type: 'gcj02',
isHighAccuracy: true,
highAccuracyExpireTime: 3500,
success(res) {
// 设置中心点位
that.longitude = res.longitude;
that.latitude = res.latitude;
// 修改标记点位
that.marker[0].longitude = res.longitude;
that.marker[0].latitude = res.latitude;
// 修改初始轨迹折线起点
that.polyline[0].points[0].longitude = res.longitude,
that.polyline[0].points[0].latitude = res.latitude,
that.polyline[0].points[1].longitude = res.longitude,
that.polyline[0].points[1].latitude = res.latitude
},
fail(info) {
console.log(info);
}
});
// 或当前时间
that.startTime = new Date().getTime();
// 定时器跑步
that.locationTimer = setInterval(() => {
that.getwz();
}, 8000); // 开始跑步时重新开启定时器
} else if (res.cancel) {
// console.log('用户点击取消')
}
}
})
} else {
wx.showModal({
content: '是否停止运动',
success(res) {
if (res.confirm) {
// console.log('用户点击确定')
// 停止运动
that.content = "开始运动"
that.runFlag = !that.runFlag;
clearInterval(that.locationTimer); // 结束跑步时清除定时器
} else if (res.cancel) {
// console.log('用户点击取消')
}
}
})
}
},
在跑步中,我还加入了实时的计算路程和平均速度的函数,如下:
// 计算路径长度
calculateDistance(prevPoint, currentPoint) {
const R = 6371; // 地球平均半径,单位为公里
const dLat = (currentPoint.latitude - prevPoint.latitude) * Math.PI / 180;
const dLon = (currentPoint.longitude - prevPoint.longitude) * Math.PI / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(prevPoint.latitude * Math.PI / 180) *
Math.cos(currentPoint.latitude * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
let distance = R * c;
// 将距离保留三位小数
distance = parseFloat(distance.toFixed(3));
return distance;
// 计算当前时间并累计行驶时间
const now = new Date().getTime();
// 计算平均速度(单位:m/s)
let speed = distance * 1000 / elapsedTime;
speed = parseFloat(speed.toFixed(3)); // 保留三位小数
},
},
【特别注意】getLocation接口可能有一定的请求频率限制,为了展示的效果合理,我在调用包含该接口的getwz()时,设置了一个定时器,总之....效果就是差不多8秒更新一下最新的位置信息....哈哈,我的也不用于实际使用,只是比赛的作品,大佬们如果有其他解决方法可以评论一下。