【React Native（Expo）】MAP表示と現在地表示

## 本記事を作成したきっかけ
職業訓練の学校での卒業制作で、React Native（Expo）アプリを作成しました。
アプリでMap画面を作成し、振り返りの意味を込めて
本記事を作成しようと思った次第です。

本記事で使用しているライブラリは
- react-native-maps（MAP表示用）
- expo-location（現在地表示用）

です。

expo-mapsなるライブラリもあるのですが、
まだ正式版ではないのと、
Expo Goアプリでは利用できないと書いてあったので、
上記ライブラリを使用しております。

## MAP画面作成準備
### react-native-maps
react-native-mapsとは、
React NativeでMAP表示させるために使用するライブラリです。

https://docs.expo.dev/versions/latest/sdk/map-view/

１．ライブラリのインストール
```
npx expo install expo-maps
```
２．app.json修正（パッケージフィールド追加）
``` app.json
"android": {
    "config": {
        "googleMaps": {
            "apiKey": "process.env.GOOGLE_MAPS_API_KEY",
        },
    },
}

"ios": {
    "config": {
        "googleMapsApiKey":  "process.env.GOOGLE_MAPS_API_KEY",
    },
}
```
※react-native-mapsを使用するには、
　Google CloudのAPIキーが必須となりますので、
　使用前にAPIキーの作成をお願いします。
　具体的な手順は上記の公式ドキュメントを御確認ください。

### expo-location
expo-locationとは、
React Nativeアプリで位置情報に関する機能を
簡単に扱えるようにするライブラリです。
ユーザーの現在地を取得したり、
位置情報の変化を監視したり、
ジオコーディング（住所⇔座標の相互変換）などが出来ます。

１．ライブラリのインストール
```
npx expo install expo-location
```

２．Androidエミュレータの現在地設定
　サイドバーの一番下にある設定メニュー「...」を押下。
　設定画面の右側にある「Location」メニューを押下。
　Single pointsタブのMap画面で任意の場所をクリックし、
　「SAVE POINT」を押下して現在地情報として保存。
 保存すると、右側に保存した現在地情報が表示されるので、
 クリックして「SET LOCATION」を押下。

３．iOSシミュレーターの現在地設定
　Simulator の上部メニューから「Features」を選択。
「Location」を選択。
「Custom Location」を選択。
「Latitude（緯度）」と「Longitude（経度）」を入力。
「OK」ボタンを押下。

```
npx expo install expo-maps
```
４．app.json修正（権限追加）
``` app.json
"android": {
    "permissions": [
        "ACCESS_FINE_LOCATION",
        "ACCESS_COARSE_LOCATION"
      ]
}

"ios": {
    "infoPlist": {
        "NSLocationWhenInUseUsageDescription": "このアプリでは、地図上で現在位置を表示するために位置情報を使用します。"
      }
}

"plugins": [
      [
        "expo-location",
        {
          "locationAlwaysAndWhenInUsePermission": "Allow jNavi to use your location."
        }
      ]
    ]
```

### 実装例
```tsx:mapsample.tsx
import { useEffect, useState } from "react"
import { StyleSheet, View, Text, Dimensions } from "react-native"
import MapView, { Marker, Region } from "react-native-maps"
import * as Location from "expo-location"

// マーカーのデータ型
// 経度・緯度の情報と一意キー用のidで構成
type Marker = {
    id: number;
    latitude: number;
    longitude: number;
}

// マーカー表示用データ
const markersData: Marker[] =
    [
        {
            id: 1,
            latitude: 35.662346819815305,
            longitude: 139.6991631860567
        },
        {
            id: 2,
            latitude: 35.6640117157159,
            longitude: 139.6993884915927
        },
        {
            id: 3,
            latitude: 35.663218497391604,
            longitude: 139.69760750497448
        },
        {
            id: 4,
            latitude: 35.661762790515354,
            longitude: 139.69710324972715
        },
        {
            id: 5,
            latitude: 35.66078649319146,
            longitude: 139.69840143876814
        }

    ]


export default function MapSample() {

    // 現在地
    const [initRegion, setInitRegion] = useState<Region | null>(null)
    // マーカー
    const [markers, setMarkers] = useState<Marker[]>([])
    // エラーメッセージ
    const [errorMsg, setErrorMsg] = useState<string | null>(null)

    useEffect(() => {
        // 位置情報のアクセス許可を取り、現在地情報を取得する
        const getCurrentLocation = async () => {
            const { status } = await Location.requestForegroundPermissionsAsync()
            if (status !== "granted") {
                setErrorMsg("位置情報へのアクセスが拒否されました")
                return
            }

            try {
                const location = await Location.getCurrentPositionAsync({})
                // 緯度・経度はgetCurrentPositionAsyncで取得した緯度・経度
                // 緯度・経度の表示範囲の縮尺は固定値にしてます
                setInitRegion({
                    latitude: location.coords.latitude,
                    longitude: location.coords.longitude,
                    latitudeDelta: 0.05,
                    longitudeDelta: 0.05
                })
            } catch (error) {
                console.error("現在地情報取得エラー：", error)
            }
        }
        getCurrentLocation()
        // 固定で設定したマーカー情報を設定する
        setMarkers(markersData)
    }, [])

    return (
        <>
            <View style={styles.container}>
                {errorMsg ? (
                    <Text>{errorMsg}</Text>
                )
                    :
                    // MapViewではstyle設定は必須です。
                    // （設定しないとMAP表示されません）
                    // regionはinitRegion(Region型)を設定
                    // showsUserLocationはtrueにすると、現在地が青い点で表示します。
                    // providerをgoogleにするとiOSでもGoogleMapで表示してくれます。
                    // （デフォルトはapple map）
                    (<MapView
                        style={styles.mapContainer}
                        region={initRegion || undefined}
                        showsUserLocation={true}
                        provider="google"
                    >
                        {markers.map((marker) => (
                            // key指定は必須。
                            // coordinateは緯度・経度を設定する。
                            // pinColorでマーカーの色指定可能
                            <Marker
                                key={marker.id}
                                coordinate={{
                                    latitude: marker.latitude,
                                    longitude: marker.longitude
                                }}
                                pinColor="blue"
                            >
                            </Marker>
                        ))}
                    </MapView>
                    )}
            </View>
        </>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center"
    },
    mapContainer: {
        width: "100%",
        height: "100%"
    }
})
```

### シミュレーターでの動作確認
現在地はスクランブル交差点
マーカーの情報は、渋谷にあるラーメン屋の
緯度・経度を設定して表示しております。

#### iOS
![スクリーンショット 2025-04-09 16.14.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/39252453-b4c1-4e29-9869-3351c271b6bb.png)

### Android
![スクリーンショット 2025-04-09 16.17.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/ab6ef32c-0d1b-44dd-8107-6219819db317.png)

#### 注意点
初めての現在地表示の際は、
位置情報取得可否の確認が出てきますので、
許可するようにしてください。