React-Nativeで地図を表示する(react-native-maps)

# 目的
Expoアプリケーションに地図を導入する
ライブラリの選定。導入手順を整理する

## 環境
- Expo 4.13.0
- react@17.0.2
- reactNative 4.9.0

## 目標
- ReactNativeアプリケーションに地図を導入する
- 地図は、現在位置の取得やカスタマイズ可能なラベルを配置できる
- GoogleMapは有償であるため、Open Street Mapを利用する   
　→断念。詳細はこの記事の最後「OpenStreetMapの導入について」をご確認ください。   
　もし、みなさんどうしているのか、知見があるようでしたら、共有いただけますと、　大変ありがたいです。

# 地図の導入方針
React-leafletがをそのままNativeでは使う方法はない。以下のライブラリを候補に検討した

- https://github.com/reggie3/react-native-webview-leaflet
 -  leafletの機能を使うこtができるが、OpenStreetMapが使えない
　選択できない
- react-native-maps
 - 公式のライブラリだが、leafletほどの多機能ではなさそう

> 【React Native】「react-native-maps」ライブラリを使ってOpenStreetMapを表示する
https://cpoint-lab.co.jp/article/202011/17919/

- ピンなどの応用
 - ピンの代わりにコンポーネントを渡すことも可能

ー＞ react-native-maps を利用することにした



# 導入手順 Expo
## 1) インストール

```
expo install react-native-maps
```

## 2) サンプル画面を設置
- この時点で、標準の地図は表示される

```javascript:App.js
import * as React from 'react';
import MapView from 'react-native-maps';
import { StyleSheet, Text, View, Dimensions } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <MapView style={styles.map} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  map: {
    width: Dimensions.get('window').width,
    height: Dimensions.get('window').height,
  },
});

```

## 3) APIキーを設定

- Open street mapを利用する場合
（要確認） APIキーはGoogle Mapを利用するために必要なものであり、OpenStreetMapを利用する場合は設定不要？

- Google Mapを利用する場合
> Expo公式
> https://docs.expo.dev/versions/latest/sdk/map-view/#deploying-google-maps-to-an-android-standalone
> Android Maps SDK for Android
> https://developers.google.com/maps/documentation/android-sdk/get-api-key

- app.jsonにapiキーを設置

```javascript:app.json
  "android": {
      "config": {
        "googleMaps" :{
          "apiKey" : "{APIKEYを設定する}"
        }
      }
```

※ ストアリリースしていないため、実際に本番で正しく動くか確認できていません。リリース時に詳細追記します


## 4) Mapのカスタマイズ
- MapView周りを作り込みます
 - インポートを追加し、UrlTile, Markerを追加します
 - 初期表示位置を `region` にステートで持たせます
 - urlTemplate に、Opem Street Mapのタイルを設定します（設定する場合）
 - MapViewの中に、Markerコンポーネントを配置することでマーカーを設置します
 - onRegionChangeは画面表示位置の変更を検知します。regionで受け取った値を、位置をもつstatenregionに設定することで、表示位置を更新します

```javascript:app.js
import MapView, { UrlTile, Marker } from 'react-native-maps';

class Map extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      // 初期表示位置を設定
      region: {
        latitude: 35.645736,
        longitude: 139.747575,
        latitudeDelta: 0.01,
        longitudeDelta: 0.01,
      },
      urlTemplate: 'http://c.tile.openstreetmap.org/{z}/{x}/{y}.jpg',
      markers: [
        {
          key: 'tamachiStation',
          latlng: {
            latitude: 35.645736,
            longitude: 139.747575,
          },
          title: '田町駅',
          description: '田町ニューデイズ',
        },
      ],
    };

  }

　　　onRegionChange(region) {
    console.log("onReasionChange");
    console.log(region);
    this.setState({ region });
  }

  

  render() {
    return (
      <View style={styles.container}>

        <MapView
        style={styles.map}
        region={this.state.region}
        onRegionChange={this.onRegionChange.bind(this)}
        >
        <UrlTile
            urlTemplate={this.state.urlTemplate}
            maximumZ={19}
            mapType={'none'}
          />

          {this.state.markers.map(marker => (
            <Marker
              key={marker.key}
              coordinate={marker.latlng}
              title={marker.title}
              description={marker.description}
            />
          ))}

        </MapView>
      </View>
    );
  }
}
```

## 5) 現在位置の取得
- パーミッションを追加

```javascript:app.json
"android": {
      "versionCode": 1,
      "package": "com.miraiyamaneko.samplepackagename",
      "googleServicesFile": "./google-services.json",
      "permissions": [
        "CAMERA",
        "WRITE_EXTERNAL_STORAGE",
        "CAMERA_ROLL"
      ],
```

- 以下のコードを追加

```
import * as Location from 'expo-location';
```

- 画面読み込み後に現在位置を取得するメソッド実行
- パーミッションを確認し、Grandなら位置情報を取得しStateに設定

```javascript:App.js
componentDidMount() {
    console.log("画面起動")
    // 現在位置を読み込む
    this._getLocationAsync();
  }

  // 現在位置の取得
  _getLocationAsync = async () => {
    let { status } = await Location.requestForegroundPermissionsAsync();
    console.log("Status");
    console.log(status);
    if (status !== 'granted') {
      this.setState({
      submitMessage: '位置情報の取得が許可されませんでした。',
      });
    }else if(status === 'granted'){
      console.log("getCurrentPositionAsync");
      await Location.getCurrentPositionAsync({}).then((location) => {
        let longitude = '経度:' + JSON.stringify(location.coords.longitude);
        let latitude = '緯度:' + JSON.stringify(location.coords.latitude);
        console.log(longitude);
        console.log(latitude);
        // 現在位置をMap Viewの中心に更新
        this.setState({
          region: {
            latitude: location.coords.latitude,
            longitude: location.coords.longitude,
            latitudeDelta: 0.01,
            longitudeDelta: 0.01,
          }
        })

      }).catch((e) => {
        console.log("現在位置取得失敗");
        console.log(e);
      });

    }
  };
```

- iOSSimulator利用の場合は、シミュレータに位置情報を設定する
Location.getCurrentPositionAsync({})実行時に、レスポンスがない、または次のエラーが発生する場合は、シミュレータに現在位置が設定されていない
`Cannot obtain current location: Error Domain=kCLErrorDomain Code=0 "(null)"`
シミュレータのfeatures>Location>CustomLocationを選択し、緯度・軽度を設定する

- コード全体


# OpenStreetMapの導入について
- 問題事象

```javascript:App.js
<UrlTile
  urlTemplate={this.state.urlTemplate}
  maximumZ={19}
/>

```

mapタイルを定義する上記を設定しても、OpenStreetMapの画面が表示されない
Androidでのみ発生する


https://stackoverflow.com/questions/66614673/react-native-maps-urltile-is-not-working-with-openstreetmap-url-on-android

- 原因・対策
https://github.com/react-native-maps/react-native-maps/issues/3941
OSMのポリシー変更
https://operations.osmfoundation.org/policies/tiles/
- OSMのデータを利用すること自体は問題ないが、タイルサーバの運用コストが嵩んでいるため規約が変更された
- 自身でタイルサーバを設置することが推奨されている
　https://switch2osm.org/serving-tiles/


# 参考資料
> react-native-mapsを使って地図を表示
https://honmushi.com/2019/12/13/expo-map/

> 【React Native】「react-native-maps」ライブラリを使ってOpenStreetMapを表示する
> https://cpoint-lab.co.jp/article/202011/17919/

> React Native と OpenStreetMap で散歩用のiOS地図アプリを作ってみた
> https://qiita.com/lfcd85/items/248d944592b93c2c009e

> Expo 公式ドキュメント
> https://docs.expo.dev/versions/latest/sdk/map-view/

> 現在位置の取得方法
> Expoを使ったGPS情報取得の実装
> https://honmushi.com/2019/12/10/expo-location/

### 通常のReactへのleafletの導入
> React-leafletの使い方メモ
https://qiita.com/studio_haneya/items/fbb52fa03ab4f212ced0

> Leaflet + OpenStreetMap で地図情報を扱うサンプルコード
https://qiita.com/TakeshiNickOsanai/items/783caa9f31bcf762da16