Haversine式を使用して2点間の緯度経度から距離を計算する方法を紹介します。
Haversine式は、球面上の2点間の距離を計算するための公式です。この式は、地球のように球形の天体上での距離計算(いわゆる「大円距離」や「球面上の直線距離」)に使われます。特に、経度と緯度を使って地球上の2地点間の最短距離を求める場合に非常に便利です。
vueのleafletを使用してマップを作成しています。
1.距離の計算するファイルを作成
const getRadian = (value) => {
return value * Math.PI / 180;
};
const getDistance = (location1, location2) => {
const latitude1 = location1.latitude;
const longitude1 = location1.longitude;
const latitude2 = location2.latitude;
const longitude2 = location2.longitude;
const R = 6371; // km
const diffLatitudeRadian = getRadian(latitude2 - latitude1);
const diffLongitudeRadian = getRadian(longitude2 - longitude1);
const latitudeRadian = getRadian(latitude1);
const longitudeRadian = getRadian(latitude2);
const a = Math.sin(diffLatitudeRadian / 2) * Math.sin(diffLatitudeRadian / 2) +
Math.sin(diffLongitudeRadian / 2) * Math.sin(diffLongitudeRadian / 2) *
Math.cos(latitudeRadian) * Math.cos(longitudeRadian);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
};
export { getDistance };
2. 地図表示
<l-map :zoom="zoom" :center="center" @update:zoom="updateZoom" @update:center="updateCenter" @ready="onMapReady" ref="mapContainer">
<l-tile-layer url="https://mt1.google.com/vt/lyrs=r&x={x}&y={y}&z={z}" layer-type="base" name="GoogleMaps"></l-tile-layer>
<l-marker v-for="marker in markers" :key="marker.id" :lat-lng="[marker.lat, marker.lng]">
<l-popup>{{ marker.name }}</l-popup>
</l-marker>
<l-marker :lat-lng="userLocation" v-if="userMarker"></l-marker>
</l-map>
まず、(Vue-Leafletのコンポーネント)で地図を表示し、ズームやセンターを管理するための状態(zoom、center)を定義しています。
-
ユーザーの位置をマーカーで表示するために下記が使われています
<l-marker :lat-lng="userLocation" v-if="userMarker"></l-marker>
-
ここでは、markers配列を使って、マーカーをループ処理して表示しています
3.ユーザー位置の取得
const locateUser = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
userLocation.value = [latitude, longitude]; // ユーザー位置を更新
userMarker.value = true; // ユーザーマーカーを表示
center.value = userLocation.value; // 地図の中心をユーザー位置に設定
if (mapVisible.value) {
mapRef.value.setView(center.value, zoom.value); // 地図の中心を設定
}
getMyMaps(); // 自分のマップ情報を取得
},
(error) => {
console.error('Could not get current position:', error);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
} else {
console.error('Browser does not support geolocation API.');
getMyMaps(); // ユーザー位置が取得できない場合でもマップを表示
}
};
locateUserメソッドでは、ブラウザの位置情報を使用して、ユーザーの(緯度・経度)を取得します。
4.マップデータの取得
const getMyMaps = async () => {
const url = 'DBからマップのデータを取得するためのエンドポイント';
try {
const response = await axios.get(url, { params: { lat: center.value[0], lng: center.value[1] } });
if (response.data.result === true) {
myMaps.value = response.data.myMap; // 取得したマップデータを格納
setMarkers(); // マーカーをセット
calculateDistances(); // ユーザーとマーカー間の距離を計算
} else {
console.error('Unexpected response structure:', response.data);
}
} catch (error) {
console.error('Error fetching maps:', error);
}
};
getMyMapsメソッドは、サーバーからマップ情報(myMap)を取得します。この際、地図の現在位置(緯度・経度)をサーバーに送信し、その位置にベースってマーカーを設定します。
4.マーカーの設定
const setMarkers = () => {
markers.value = myMaps.value.map((myMap) => ({
id: myMap.id,
lat: myMap.latitude,
lng: myMap.longitude,
name: myMap.name,
}));
};
setMarkersメソッドでは、取得したマップデータ(myMaps)からマーカー情報を取り出して、markers配列に格納しています。
controller
public function hoge(Request $request)
{
$latitude = $request->latitude;
$longitude = $request->longitude;
$loggedInUserId = Auth::id();
$myMap = [];
try {
$myMap = UserMap::selectDistance($longitude, $latitude, $loggedInUserId)
->orderBy('distance', 'asc')
->take(10)
->get();
} catch (\Exception $e) {
}
return ([
'result' => true,
'myMap' => $myMap,
]);
}
public function scopeSelectDistance($query, $longitude, $latitude, $loggedInUserId) // 現在地との距離を取得できるようにしてます
{
$query->selectRaw(
'id,user_id, name, address,content'.
'ST_Y(location) AS longitude, ST_X(location) AS latitude, '.
'st_Distance_Sphere(POINT(?, ?), POINT(ST_Y(location), ST_X(location))) AS distance',
[ $longitude, $latitude,]
)
->where('user_id', $loggedInUserId);
}
5. 2点間の距離を計算する
import { getDistance } from '@/Haversine.js';
const calculateDistances = () => {
const userLoc = { latitude: userLocation.value[0], longitude: userLocation.value[1] };
myMaps.value = myMaps.value.map((myMap) => {
const mapLoc = { latitude: myMap.latitude, longitude: myMap.longitude };
return {
...myMap,
distance: getDistance(userLoc, mapLoc).toFixed(2) // ユーザーとマーカー間の距離を計算
};
});
myMaps.value.sort((a, b) => parseFloat(a.distance) - parseFloat(b.distance)); // 距離が近い順にソート
};
calculateDistancesメソッドは、ユーザーの位置とマーカーの位置と距離を計算する処理です。計算には、getDistance関数(Haversine式)を使用します。
- getDistance関数を使って、ユーザーとマーカーの距離(キロメートル)を計算しています
- 計算した距離をmyMapsの各オブジェクトに追加し、その後、距離が近い順にソートします
6.距離をマーカーに表示
<l-marker v-for="marker in markers" :key="marker.id" :lat-lng="[marker.lat, marker.lng]">
<l-popup>{{ marker.name }} - {{ marker.distance }} km</l-popup>
</l-marker>
最後に、markers配列に折りたたまれたマーカーを地図に表示します。 マーカーのポップアップには、marker.nameとともに距離を表示できます。
ここでは、各マーカーの距離をポップアップに表示しておりますので、ユーザー{{ marker.distance }} kmとそのマーカーの距離を表示します。
最後に
2点間距離を計算するために公式を使用しましたが、2点間の直線距離なのであくまで目安でしかありません。
有料でGoogleMapAPIを使用すればより正確な距離を取得することはできます。
参考になれば幸いです。