ブログ
これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。
Vue3でMapboxを使用する - カメラ、API編 -
カメラ
アニメーションでランダムに移動
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点間の経路を取得
作成済みの fromMarker
と toMarker
を使用する
トップレベルに追加
// 経路を取得する
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>
コメントはありません。