緯度経度から2点間距離の計算方法

Haversine式を使用して2点間の緯度経度から距離を計算する方法を紹介します。

Haversine式は、球面上の2点間の距離を計算するための公式です。この式は、地球のように球形の天体上での距離計算（いわゆる「大円距離」や「球面上の直線距離」）に使われます。特に、経度と緯度を使って地球上の2地点間の最短距離を求める場合に非常に便利です。

vueのleafletを使用してマップを作成しています。

## 1.距離の計算するファイルを作成
```js:Haversine.js
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. 地図表示
```vue:map.vue
<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>
```
まず、<l-map>（Vue-Leafletのコンポーネント）で地図を表示し、ズームやセンターを管理するための状態（zoom、center）を定義しています。
- ユーザーの位置をマーカーで表示するために下記が使われています
```<l-marker :lat-lng="userLocation" v-if="userMarker"></l-marker>```

- ここでは、markers配列を使って、マーカーをループ処理して表示しています
### 3.ユーザー位置の取得
```javascript::map.vue
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.マップデータの取得
```javascript:
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.マーカーの設定
```javascript:
const setMarkers = () => {
    markers.value = myMaps.value.map((myMap) => ({
        id: myMap.id,
        lat: myMap.latitude,
        lng: myMap.longitude,
        name: myMap.name,
    }));
};
```
setMarkersメソッドでは、取得したマップデータ（myMaps）からマーカー情報を取り出して、markers配列に格納しています。

### controller
```php:controller.php
    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,
        ]);
    }
```
```php:model.php
    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点間の距離を計算する
```javascript:
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.距離をマーカーに表示
```vue:
<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を使用すればより正確な距離を取得することはできます。
参考になれば幸いです。