Uniapp实现微信小程序跑步运动轨迹、历史轨迹和轨迹回放等功能

小程序 0

一、先看效果

【跑跑步】微信小程序


二、实现功能

        【跑跑步】是一款基于Uniapp开发的微信小程序,主要实现了跑步轨迹记录、历史轨迹、轨迹纠偏、轨迹回放和轨迹排名等功能。室内跑步记录正在开发,还没有发布,主要利用手机加速度传感器实现计步功能。更多干货请关注公众号:小蝇工作室

1、轨迹记录

        轨迹记录主要用到微信小程序的startLocationUpdateBackground和onLocationChange两个API接口,该接口实现了前后台GPS定位,连续采集用户的跑步定位信息,然后通过map组件把轨迹绘制出来。

//前后台定位接口wx.startLocationUpdateBackground(callback) {    var that = this;    success(res) {        //开始监听GPS数据        that.onLocationChange();    },    fail(res) {        //授权失败后引导用户打开定位信息		...				    }})//监听GPS数据的变化onLocationChange() {	var that = this;	var points = [];	var paths = [];	wx.onLocationChange(function(res) {		res.time = Date.now();		points.push(res);		that.latitude = res.latitude;		that.longitude = res.longitude;		if (that.scale = 3) {			that.scale = 18;		}		paths = filterGPSPoints(points);		paths = douglasPeucker(paths, 3);		that.playRunAudio();		uni.setStorage({			key: 'gps',			data: JSON.stringify(paths),			success: function(e) {				//console.log(e);			},			fail: function(e) {				console.log(e)			}		});	});}//绘制跑步轨迹addLine(points) {	var that = this;	//计算距离	function calculationDistance(ps) {		var LC = 0;		ps.forEach(function(f, index) {			let lng1 = f.longitude;			let lat1 = f.latitude;			let lng2 = f.longitude;			let lat2 = f.latitude;			if (ps[index + 1]) {				lng2 = ps[index + 1].longitude;;				lat2 = ps[index + 1].latitude;			}			let radLat1 = lat1 * Math.PI / 180.0;			let radLat2 = lat2 * Math.PI / 180.0;			let a = radLat1 - radLat2;			let b = (lng1 * Math.PI / 180.0) - (lng2 * Math.PI / 180.0);			let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +				Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));			LC = LC + s * 6370996.81		})		that.LC = (LC / 1000).toFixed(3);		that.SD = (LC * 3.6 / (that.time)).toFixed(2);	};	calculationDistance(points);	const taskLine = { //路线		points: points, //经纬度数组		color: '#00ff00', //线的颜色		width: 5, //线的宽度		borderWidth: 1, //线的厚度		dottedLine: false, //是否虚线		arrowLine: true //带箭头的线 开发者工具暂不支持该属性	}	this.polyline[0] = taskLine;}

其中:filterGPSPoints方法用于过滤GPS飘逸点数据,douglasPeucker方法用于抽稀GPS数据,addLine方法绘制跑步轨迹。

2、轨迹回放

轨迹回放功能的实现主要通过moveAlong方法实现,设置的参数包括marker点的ID,轨迹线路和动画长度。本文实现了在轨迹回放时候切换三维场景,然后自动旋转等特效。

moveAlong() {	var that = this;	var duration = 10000;	var durationMove = 50;	var markerId = 10;	var js = TimeTotimestamp(that.JS);	var sd = that.SD;	var lc = that.LC;	that.startNum = 2;	that.isShowMyRunRecord = false;	that.isShowMyWeRun = false;	if (this.MapContext) {		this.MapContext.moveAlong({			markerId: markerId,			autoRotate: false,			path: this.polyline[0].points,			duration: duration, //假设动画执行5秒			success(res) { // ios是开始动画就会执行,安卓手机是在动画结束后执行success 				that.removeAlong();				that.startName = "开始";				that.startNum = 0;				that.isShowMyRunRecord = true;				that.isShowMyWeRun = true;				that.isShowShare = true;			}		});		var i = 0;		var tt = 10;		that.startName = tt;		that.endS = setInterval(function() {			tt--;			that.startName = tt;		}, 1000);		var durationZH = duration / durationMove;		that.rotateDH = setInterval(function() {			i++;			if (i < 40) {				that.skew = i;			}			that.rotate = (270 / durationZH) * i;			that.startName = tt;			that.LC = ((js / durationZH) * i * (sd / 3600)).toFixed(3);			that.JS = TimeToHFS((js / durationZH) * i);		}, durationMove);	}}
3、核心算法

filterGPSPoints算法过滤GPS飘逸点数据:

// 定义阈值const speedThreshold = 40; // 速度阈值,单位为m/sconst accelerationThreshold = 4; // 加速度阈值,单位为m/s^2// 过滤GPS飘逸点的函数function filterGPSPoints(points) {	// 如果点的数量小于等于2,直接返回原始点集合	if (points.length <= 2) {		return points;	}	// 过滤后的点集合	const filteredPoints = [points[0]];	// 遍历原始点集合	for (let i = 1; i < points.length - 1; i++) {		const prevPoint = points[i - 1];		const currentPoint = points[i];		const nextPoint = points[i + 1];		// 计算当前点的速度和加速度		const speed = calculateSpeed(prevPoint, currentPoint);		const acceleration = calculateAcceleration(prevPoint, currentPoint, nextPoint);		// 如果速度和加速度都低于阈值,认为是有效点,加入过滤后的点集合		if (speed <= speedThreshold && acceleration <= accelerationThreshold) {			filteredPoints.push(currentPoint);		}	}	// 加入最后一个点	filteredPoints.push(points[points.length - 1]);	return filteredPoints;}// 计算两个点之间的速度function calculateSpeed(prevPoint, currentPoint) {	const distance = calculateDistance(prevPoint, currentPoint);	const time = (currentPoint.time - prevPoint.time)/1000; // 假设timestamp是时间戳	return distance / time;}// 计算三个点之间的加速度function calculateAcceleration(prevPoint, currentPoint, nextPoint) {	const speed1 = calculateSpeed(prevPoint, currentPoint);	const speed2 = calculateSpeed(currentPoint, nextPoint);	const time = (nextPoint.time - prevPoint.time)/1000; // 假设timestamp是时间戳	return (speed2 - speed1) / time;}// 计算两个点之间的距离function calculateDistance(point1, point2) {	const lat1 = point1.latitude;	const lon1 = point1.longitude;	const lat2 = point2.latitude;	const lon2 = point2.longitude;	const R = 6371; // 地球半径,单位为km	const dLat = deg2rad(lat2 - lat1);	const dLon = deg2rad(lon2 - lon1);	const a =		Math.sin(dLat / 2) * Math.sin(dLat / 2) +		Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);	const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));	const distance = R * c * 1000; // 转换为米		return distance;}// 将角度转换为弧度function deg2rad(deg) {	return deg * (Math.PI / 180);}module.exports = filterGPSPoints;

抽稀GPS数据:

//计算距离function calculationDistance(point1, point2) {	let lat1 = point1.latitude;	let lat2 = point2.latitude;	let lng1 = point1.longitude;	let lng2 = point2.longitude;	let radLat1 = lat1 * Math.PI / 180.0;	let radLat2 = lat2 * Math.PI / 180.0;	let a = radLat1 - radLat2;	let b = (lng1 * Math.PI / 180.0) - (lng2 * Math.PI / 180.0);	let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +		Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));	return s * 6370996.81;};//计算垂距function distToSegment(start, end, center) {	//下面用海伦公式计算面积	let a = Math.abs(calculationDistance(start, end));	let b = Math.abs(calculationDistance(start, center));	let c = Math.abs(calculationDistance(end, center));	let p = (a + b + c) / 2.0;	let s = Math.sqrt(Math.abs(p * (p - a) * (p - b) * (p - c)));	return s * 2.0 / a;};//递归方式压缩轨迹function compressLine(coordinate, result, start, end, dMax) {	if (start < end) {		let maxDist = 0;		let currentIndex = 0;		let startPoint = coordinate[start];		let endPoint = coordinate[end];		for (let i = start + 1; i < end; i++) {			let currentDist = distToSegment(startPoint, endPoint, coordinate[i]);			if (currentDist > maxDist) {				maxDist = currentDist;				currentIndex = i;			}		}		if (maxDist >= dMax) {			//将当前点加入到过滤数组中			result.push(coordinate[currentIndex]);			//将原来的线段以当前点为中心拆成两段,分别进行递归处理			compressLine(coordinate, result, start, currentIndex, dMax);			compressLine(coordinate, result, currentIndex, end, dMax);		}	}	return result;};/** * *@param coordinate 原始轨迹Array<{latitude,longitude}> *@param dMax 允许最大距离误差 *@return douglasResult 抽稀后的轨迹 * */function douglasPeucker(coordinate, dMax) {	if (!coordinate || !(coordinate.length > 2)) {		return null;	}	coordinate.forEach((item, index) => {		item.key = index;	});	let result = compressLine(coordinate, [], 0, coordinate.length - 1, dMax);	result.push(coordinate[0]);	result.push(coordinate[coordinate.length - 1]);	let resultLatLng = result.sort((a, b) => {		if (a.key < b.key) {			return -1;		} else if (a.key > b.key)			return 1;		return 0;	});	resultLatLng.forEach((item) => {		item.key = undefined;	});	return resultLatLng;}module.exports = douglasPeucker;

也许您对下面的内容还感兴趣: