お問い合わせ

ブログ

これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。

Vue3でMapboxを使用する - カメラ、API編 -

okuda Okuda 9 months

カメラ

アニメーションでランダムに移動

flyTo を使用する

トップレベルに以下を追加

// アニメーションでランダムに移動
const flyToRandom = () => {
    // 最初に位置に戻る
    map.flyTo({
        center: [135 + Math.random(), 34 + Math.random()],
        essential: true,
    });
};

テンプレート内にボタンを追加

    <button @click="flyToRandom">fly to random</button>

シーンを変えてランダムに移動

jumpTo を使用する

トップレベルに以下を追加

// シーンを変えてランダムに移動
const jumpToRandom = () => {
    map.jumpTo({
        center: [135 + Math.random(), 34 + Math.random()],
        zoom: Math.random() * 22,
        pitch: Math.random() * 85,
        bearing: Math.random() * 360,
    });
};

テンプレート内にボタンを追加

    <button @click="jumpToRandom">Jump to random</button>

マップを移動

ピクセル単位で座標を増減する

panBy を使用する

トップレベルに以下を追加

// ピクセル単位で移動
const panByPixels = (key) => {
    let point = null
    switch (key) {
        case 'up':
            point = [0, -100]
            break;
        case 'down':
            point = [0, 100]
            break;
        case 'left':
            point = [-100, 0]
            break;
        case 'right':
            point = [100, 0]
            break;
        default:
            break;
    }
    map.panBy(point, {duration: 1000});
};

テンプレート内にボタンを追加

    <!-- マップを移動 -->
    <button @click="panByPixels('up')">pan by up</button>
    <button @click="panByPixels('down')">pan by down</button>
    <button @click="panByPixels('left')">pan by left</button>
    <button @click="panByPixels('right')">pan by right</button>

マップを回転

度数でマップを回転

rotateTo を使用する

トップレベルに以下を追加

// 度数でマップを回転
const rotateByBearing = (key) => {
    const bearing = 30;
    switch (key) {
        case "left":
            map.rotateTo(map.getBearing() + bearing);
            break;
        case "right":
            map.rotateTo(map.getBearing() - bearing);
            break;
        default:
            break;
    }
};

テンプレート内にボタンを追加

    <!-- マップを回転 -->
    <button @click="rotateByBearing('left')">rotate by left</button>
    <button @click="rotateByBearing('right')">rotate by right</button>

ファンクション

2点間の距離を測る

トップレベルに距離を宣言

// from marker
let fromMarker = null;
// to marker
let toMarker = null;
// 距離
const distance = ref(0);

onMounted のなかにfromとtoのマーカを追加

// from marker
    var markerElm = createTextMarker("from")
    const fromMarker = new mapboxgl.Marker({
        element: markerElm,
        draggable: true,
    })
        .setLngLat([135.4250, 34.8187])
        .addTo(map)

    // to marker
    var markerElm = createTextMarker("to")
    const toMarker = new mapboxgl.Marker({
        element: markerElm,
        draggable: true,
    })
        .setLngLat([135.4270, 34.8187])
        .addTo(map)

onMounted のなかにマーカイベントを追加

    // 距離をクリア
    fromMarker.on("dragend", () => {
        distance.value = 0;
    });

    // 距離を取得
    toMarker.on("dragend", () => {
        const from = fromMarker.getLngLat();
        const to = toMarker.getLngLat();
        distance.value = from.distanceTo(to).toFixed(to); // m
    });

テンプレートに表示する

<!-- 2点間の距離 -->
    <div>[distance from to] {{ distance }} m</div>

パスを書いて距離を測る

右クリックでポイントを追加してパスを書く
地理空間データ分析ライブラリ「turfjs」でパスの距離を測る

https://turfjs.org/docs/#length https://github.com/Turfjs/turf https://www.npmjs.com/package/@turf/turf

// turfjs をインストール

npm i --save @turf/turf

トップレベルでインポート

import { length } from "@turf/turf"; 

トップレベルで距離を測るための geojson とポイント間に線を引くために使用する linestring を作成

// ラインの距離
const lineDistance = ref(0);

// 距離を測るためのGeoJSON
const geojson = {
    type: "FeatureCollection",
    features: [],
};

// ポイント間に線を引くために使用
const linestring = {
    type: "Feature",
    geometry: {
        type: "LineString",
        coordinates: [],
    },
};

onMounted のなかの map on load イベントでソースとレイヤを追加

    // map on load
    map.on("load", () => {
        // geojsonをソースとして追加
        map.addSource("geojson", {
            type: "geojson",
            data: geojson,
        });

        // ポイントのレイヤを追加
        map.addLayer({
            id: "measure-points",
            type: "circle",
            source: "geojson",
            paint: {
                "circle-radius": 5,
                "circle-color": "#000",
            },
            filter: ["in", "$type", "Point"],
        });

        // ラインのレイヤを追加
        map.addLayer({
            id: "measure-lines",
            type: "line",
            source: "geojson",
            layout: {
                "line-cap": "round",
                "line-join": "round",
            },
            paint: {
                "line-color": "#000",
                "line-width": 2.5,
            },
            filter: ["in", "$type", "LineString"],
        });
    });

onMounted のなかに右クリックイベント map on contextmenu を追加し、右クリックでポイントとラインを追加するようにする

// 右クリックイベント
    map.on("contextmenu", (event) => {
        // Feature objects を取得
        const features = map.queryRenderedFeatures(event.point, {
            layers: ["measure-points"],
        });

        // 線を新しく書くためにラインを消す
        // geojson.featuresの最後の配列が`type: "LineString"`なので削除する
        if (geojson.features.length > 1) {
            geojson.features.pop();
        }

        // ポイントをクリックした場合は削除
        if (features.length) {
            const id = features[0].properties.id;
            geojson.features = geojson.features.filter(
                (point) => point.properties.id !== id
            );
        }
        // それ以外はポイントを追加
        else {
            const point = {
                type: "Feature",
                geometry: {
                    type: "Point",
                    coordinates: [event.lngLat.lng, event.lngLat.lat],
                },
                properties: {
                    id: String(new Date().getTime()),
                },
            };

            geojson.features.push(point);
        }

        // ポイントが2つ以上のとき
        if (geojson.features.length > 1) {
            // linestringに座標を追加
            linestring.geometry.coordinates = geojson.features.map(
                (point) => point.geometry.coordinates
            );
            geojson.features.push(linestring);

            // 距離を計算
            lineDistance.value = turfLength(linestring);
        } else {
            // 距離をリセット
            lineDistance.value = 0;
        }

        // geojsonを更新
        map.getSource("geojson").setData(geojson);
    });

テンプレートに表示する

    <!-- ラインの距離 -->
    <div>[distance line] {{ lineDistance }} m</div>

ラインをバウンズにセット

さっき引いたラインをバウンズにセットする

トップレベルに追加

const setBounds = () =>
    {
        // LineStringの地理座標を取得
        const coordinates = linestring.geometry.coordinates

        if (coordinates.length < 2) {return }

        // 最初の'LngLatBounds'を作成
        const bounds = new mapboxgl.LngLatBounds(
            coordinates[0],
            coordinates[0]
        )
        console.log(bounds)

        // 'LngLatBounds'に`extend`を使ってぅべての座標を含める
        for (const coord of coordinates) {
            bounds.extend(coord)
        }

        map.fitBounds(bounds, {
            padding: 10
        })
    }

テンプレートにボタンを追加

    <!-- ラインをバウンズにセット -->
    <button @click="setBounds">set bounds</button>

jsonからfeaturesをアップデート

geojsonをapiで取得して、 setInterval で追加していく
同時に panTo でマップも追従させる

トップレベルに追加

// jsonからfeaturesをアップデート
const updateFeaturesByJson = async () =>
{
    // jsonの取得
    const response = await fetch(
        "https://docs.mapbox.com/mapbox-gl-js/assets/hike.geojson"
    );
    const data = await response.json();
    console.log(data);

    // 座標データを保存
    const coordinates = data.features[0].geometry.coordinates;

    // 最初の座標だけをセット
    data.features[0].geometry.coordinates = [coordinates[0]];

    // ソースを追加
    map.addSource("trace", {type: "geojson", data: data});

    // レイヤを追加
    map.addLayer({
        id: "trace",
        type: "line",
        source: "trace",
        paint: {
            "line-color": "yellow",
            "line-opacity": 0.75,
            "line-width": 5,
        },
    });

    // start位置に移動して準備
    map.jumpTo({center: coordinates[0], zoom: 14});
    map.setPitch(60);

    // 保存した座標リストから座標を追加しマップを更新
    let i = 0;
    const timer = setInterval(() =>
    {
        if (i < coordinates.length) {
            data.features[0].geometry.coordinates.push(coordinates[i]);
            map.getSource("trace").setData(data);
            map.panTo(coordinates[i]);
            i++;
        } else {
            window.clearInterval(timer);
        }
    }, 10);
}

テンプレートにボタンを追加

    <!-- jsonでアップデート -->
    <button @click="updateFeaturesByJson">update features by json</button>

ポイントからリバースロケーション

geocoding pai を使用して、近くにある施設を検索

トップレベルに追加

// ポイントからリバースロケーション
const getPoint = async () => {
    // geocoding api を使用する
    const domain = "https://api.mapbox.com/geocoding/v5";
    const endpoint = "mapbox.places";
    // マップの中心座標
    const lng = mapInfo.lng;
    const lat = mapInfo.lat;
    // 候補の数
    const limit = 3;
    // タイプの選択
    const types = "poi";
    // 言語の選択
    const lang = selectedLanguage.value ?? "ja";
    // urlの作成
    const api = `${domain}/${endpoint}/${lng},${lat}.json?limit=${limit}&types=${types}&language=${lang}&access_token=${accessToken}` ;

    // jsonとして取得
    const res = await fetch(api);
    const json = await res.json();
    console.log(json);

        // ポップアップで表示
    json.features.forEach(feature =>
    {
        new mapboxgl.Popup()
            .setLngLat(feature.center)
            .setHTML(feature.text)
            .addTo(map)
    });
}

テンプレートにボタンを追加

        <!-- ポイントからリバースロケーション -->
        <button @click="getPoint">get point form center</button>

経路を取得

directions pai を使用して、2点間の経路を取得
作成済みの fromMarkertoMarker を使用する

トップレベルに追加

// 経路を取得する
const mainRouteGeoJson = ref(null)
const mainRoute = reactive({
    distance: 0,
    duration: 0,
    durationTypical: 0,
});

const getDirection = async () => {
    // 経由するポイント
    let coordinates = [];

    // スタート位置としてfromMarkerの座標を入れる
    var lngLat = fromMarker.value.getLngLat();
    coordinates.push(`${lngLat.lng},${lngLat.lat}`);

    // ゴール位置としてtoMarkerの座標を入れる
    var lngLat = toMarker.value.getLngLat();
    coordinates.push(`${lngLat.lng},${lngLat.lat}`);

    // geocoding api を使用する
    const domain = "https://api.mapbox.com/directions/v5/mapbox";
    const profile = "driving-traffic"; // ["driving-traffic", "driving", "walking", "cycling"];
    const coordinatesString = coordinates.join(";");
    // 言語の選択
    const lang = selectedLanguage.value ?? "ja";

    // build url
    const api = `${domain}/${profile}/${coordinatesString}`;

    // クエリストリングで渡すパラメータ
    const params = {
        geometries: "geojson", // geojson, polyline, polyline6
        access_token: accessToken,
        alternatives: false, // 代替ルートを返す: false
        annotations: "duration,distance,speed,congestion", // ルートに沿って追加のメタデータを返す
        overview: "full", // きれいに表示
        steps: true, // 道順
        banner_instructions: true,
        language: lang,
    };

    // 文字列に変換
    const paramsString = new URLSearchParams(params).toString();

    // api request
    const response = await fetch(`${api}?${paramsString}`);
    const json = await response.json();
    const routes = json.routes;

    // 1つ目のルートをメインとして取得
    const main = routes[0];

    // `main`から座標リストを取得してgeojsonを作成
    const geojson = {
        type: "Feature",
        properties: {},
        geometry: {
            type: "LineString",
            coordinates: main.geometry.coordinates,
        },
    }

    // `direction-source`として登録したソースがあるかチェック
    if (map.getSource("direction-source")) {
        // ソースをアップデート
        map.getSource("direction-source").setData(geojson);
    } else {
        // ない場合はソースを追加
        map.addSource("direction-source", {
            type: "geojson",
            data: geojson,
        });

        // 経路をラインで表示
        map.addLayer({
            id: "direction-line",
            type: "line",
            source: "direction-source",
            layout: {
                // デフォルトでレイヤーを表示
                visibility: "visible",
            },
            paint: {
                "line-color": "blue",
                "line-width": 3,
                "line-opacity": 1.0,
            },
        });
    }

    mainRoute.distance = main.distance,
    mainRoute.duration = (main.duration / 60).toFixed(2), // 秒を分に変更
    mainRoute.durationTypical = (main.duration_typical / 1000 / 60).toFixed(2), // 秒を分に変更
}

テンプレートにボタンと表示を追加

        <!-- 経路を表示 -->
        <button @click="getDirection">get direction</button>
        <div>約{{ mainRoute.distance }}m</div>
        <div>約{{ mainRoute.duration }}分</div>
Vue3でMapboxを使用する - カメラ、API編 - 2022-03-05 18:25:54

コメントはありません。

4488

お気軽に
お問い合わせください。

お問い合わせ