路径记录demo

背景

最近使用了一个 iPhone 上的应用 anydistance，可以记录比如跑步或者骑车的路径，更惊喜的是它会记录海拔高度，并且支持生成 3d 的路径。

这时上次去湖州一段路上所记录，看起来挺酷的。

所以想着自己做个 demo。

体验链接是 http://geo-path.pages.dev/

技术细节

技术上，navigator.geolocation支持获取用户当前的地理信息，然后我们用three.js画出路径。

geoloation

Copy
getGeoData() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition((position) => {
const data = {
altitude: position.coords.altitude ?? 0,
latitude: position.coords.latitude,
longitude: position.coords.longitude
};
resolve(data);
}, () => {

}, {
enableHighAccuracy: true
});
})
}

这里有两点需要注意的。

首先是有些浏览器不支持 altitude 属性也就是海拔高度，比如安卓上的 chrome。

其次是 enableHighAccuracy 这个属性，设为 true 表示支持高准确性，最好设置成 true。我在小区跑了一圈测试比对了下，没有设置高准确性时，路径会显得很没有杂乱。

three.js

首先是需要数据的转换，还是以上述提到的绕小区跑一圈为例子，经度的范围是 120.11420594721491 到 120.11537834321953，相差非常非常小，如果用这个数据显示的话，在画布上很难看清形状，所以需要做相应的数据转换

Copy
const latitude = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER];
const longitude = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER];

rawData.forEach((data) => {
latitude[0] = Math.min(latitude[0], data.latitude)
latitude[1] = Math.max(latitude[1], data.latitude)
longitude[0] = Math.min(longitude[0], data.longitude)
longitude[1] = Math.max(longitude[1], data.longitude)
});

const points = rawData.map((data) => {
const latitudeValue = latitude[1] === latitude[0] ? 300 : (data.latitude - latitude[0]) / (latitude[1] - latitude[0]) * 300;
const longitudeValue = longitude[1] === longitude[0] ? 300 : (data.longitude - longitude[0]) / (longitude[1] - longitude[0]) * 300;
return new THREE.Vector3( longitudeValue, latitudeValue, data.altitude )
});

路径

Copy
const geometry = new THREE.BufferGeometry().setFromPoints( points );
const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
const line = new THREE.Line( geometry, material );
this.scene.add( line );

line.geometry.center();

使用 Line 来表示路径，并且我们把这个 line 的中心放到原点。

表示当前位置的点

Copy
const dotGeometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3( 0, 0, 0)]);
const dotMaterial = new THREE.PointsMaterial( { size: 5, sizeAttenuation: false, color: 0x888888 } );
this.dot = new THREE.Points( dotGeometry, dotMaterial );
this.scene.add( this.dot );

使用 Points 来表示当前位置的点。

动画

Copy
// 旋转路径
this.line.rotateOnAxis(new THREE.Vector3(0, 0, 1), 1 * Math.PI * 2 / frames);

// 更新当前位置
this.dotIndex++;
const length = this.line.geometry.attributes.position.array.length / 3;
this.dotIndex = this.dotIndex % length;
const vector = this.line.geometry.attributes.position.array.slice(this.dotIndex * 3, this.dotIndex * 3 + 3)
this.dot.position.set(vector[0], vector[1], vector[2]);

效果

http://geo-path.pages.dev/

其他

上面只是 demo，要做好这个产品其实还有些情况需要考虑，现在是 1 秒记录 20 次，如果用户的路径很长，则数据就会很多，数据的存储，还有展示时选择合适的数据颗粒度，都是需要考虑的。