shuilong

shuilong的博客

パスの記録デモ

背景#

最近、iPhone のアプリ anydistance を使ってみました。ランニングや自転車のルートを記録できるだけでなく、驚くべきことに標高も記録しており、3D のルートを生成することもできます。

先日、湖州へ行った際に記録したルートがかっこよく見えます。

そこで、自分でデモを作ってみることにしました。

体験リンクはこちらです: http://geo-path.pages.dev/

技術の詳細#

技術的には、navigator.geolocationを使用してユーザーの現在の地理情報を取得し、それをもとにthree.jsでルートを描画します。

geoloation#

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
        });
    })
}

ここで注意するべき点が 2 つあります。

まず、一部のブラウザは altitude 属性(標高)をサポートしていないことがあります。たとえば、Android の Chrome です。

次に、enableHighAccuracy という属性です。これを true に設定すると、高い精度での位置情報をサポートします。できるだけ true に設定することが最適です。私は自分の近所を走ってテストしましたが、高い精度を設定しないと、ルートがごちゃごちゃして見えます。

three.js#

まず、データの変換が必要です。先ほど述べた近所を走る例を使います。経度の範囲は 120.11420594721491 から 120.11537834321953 までで、非常に小さな差です。このデータをそのまま表示すると、キャンバス上では形状が見えにくくなりますので、適切なデータ変換が必要です。

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 ) 
});

ルート#

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 を原点に配置します。

現在位置を表す点#

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 を使用します。

アニメーション#

// ルートを回転させる
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/

その他#

上記はデモに過ぎません。この製品を本格的に作るには、いくつかの事柄を考慮する必要があります。現在は 1 秒に 20 回の記録を行っていますが、ユーザーのルートが非常に長い場合、データが多くなります。データの保存や表示時に適切なデータの粒度を選択する必要があります。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。