メモです。
これまでReact Nativeでの地図として、react-native-mapsを使ってきた。これを使うとiOSではAppleのMap、AndroidではGoogle Mapsで地図が表示される。
地図そのものの表現はこれでも特に問題はなかったものの、ビジュアライゼーションとかナビゲーション周りを実装したくMapboxを検討することにした。ちなみにナビゲーションに関してMapbox公式からはiOS、Android向けのSDKしか出ておらず、React Native向けのものはHommeという会社がOSSで開発しているものくらいしかめぼしいものがない。というわけでそっちはまたあとで考えるとして、ひとまずReact NativeでMapboxを動かすところまでやってみる。
今回試すのはMapsと呼ばれているサービスで、WebでJSだとMapbox GL JSを利用し、iOS/Androidだとそれぞれのプラットフォーム向けに提供されているMaps SDKを使っていく必要がある。React Native向けには公式ではSDKは提供されておらず、コミュニティベースのOSSなライブラリが存在しているのでこれを使っていくこととする。React NativeなのでJS向けのやつがそのまま使えないかと思ったがそうでもないようだ。
なお、今回は冒頭で言ったようにすでにreact-native-mapsを使って実装されているのでこれを差し替えていく形になる。
ではやっていく。基本的には提供されているガイドに沿って進めていく。事前にMapboxへのサインアップをしておく必要がある。
インストール
yarn add @react-native-mapbox-gl/maps
iOS
ios/Podfile
でpre_installとpost_installのフックに以下のように追記する。
pre_install do |installer| $RNMBGL.pre_install(installer) end post_install do |installer| $RNMBGL.post_install(installer) end
そうしたらpod install
を実行する。
Android
最近のReact NativeのバージョンならAndroid向けの設定は特に必要ないです。
基本的な使い方
以降、iOSで確認してます。
地図の表示
基本的には地図を表示したい画面で以下のようにするだけ。このサンプルではApp.tsxに書いている。
まず、アクセストークンのセットをする。これはMapboxにサインアップしたらデフォルトのものがダッシュボード上に表示されているのでそれのコピペでOK。
MapboxGL.setAccessToken( 'アクセストークン' )
あとは<MapView />
を用意して、その中に<Camera />
をセットする。ここでいうカメラは写真を撮る機能のカメラではなくどちらかというと「視点」みたいなイメージの概念だ。地図をどう見るか、みたいな。
地図の中心をセットするにはこの<Camera />
のcenterCoortinate
というプロパティに配列で中心地の座標を指定する。指定の仕方は[経度, 緯度]
。以下のサンプルではニューヨークのとある場所が指定されている。
return ( <View style={styles.page}> <View style={styles.container}> <MapboxGL.MapView style={styles.map}> <MapboxGL.Camera zoomLevel={10} centerCoordinate={[-77.044211, 38.852924]} /> </MapboxGL.MapView> </View> </View> )
Polylineを引いてみる
あとはMapView
の中にいろんなものをセットしていくだけでいい。例えばPolylineを引きたいのであれば以下のような感じ。Mapboxではreact-native-mapsとは異なりPolylineそのもののAPIはないのでShapeSource
とLineLayer
を使って描画する。このとき線を引くための座標情報はGeoJson形式で<ShapeSource />
のshape
プロパティに渡す。なお、外部のURLを参照することも可能だそうだ。
export default function App(): JSX.Element { const geoJson = { type: 'Feature', geometry: { type: 'LineString', coordinates: [ [-77.044211, 38.852924], [-77.045659, 38.860158], [-77.044232, 38.862326], [-77.040879, 38.865454], [-77.039936, 38.867698], [-77.040338, 38.86943], [-77.04264, 38.872528], [-77.03696, 38.878424], [-77.032309, 38.87937], [-77.030056, 38.880945], [-77.027645, 38.881779], [-77.026946, 38.882645], [-77.026942, 38.885502], [-77.028054, 38.887449], [-77.02806, 38.892088], [-77.03364, 38.892108], [-77.033643, 38.899926], ], }, } return ( <View style={styles.page}> <View style={styles.container}> <MapboxGL.MapView style={styles.map}> <MapboxGL.Camera zoomLevel={10} centerCoordinate={[-77.044211, 38.852924]} /> <MapboxGL.ShapeSource id="source1" lineMetrics={true} shape={geoJson}, > <MapboxGL.LineLayer id="layer1" style={lineStyles.lineLayer} /> </MapboxGL.ShapeSource> </MapboxGL.MapView> </View> </View> ) }
なお、react-native-mapsではPolylineを引きたくないときは単に空の座標配列を渡せばよかったかけど、MapboxのSDKはエラーになってしまう。なので条件分岐で対応してみたけどこれであってるのかは不明。
現在地の表示
現在地を表示するには<MapboxGL.UserLocation></MapboxGL.UserLocation>
を使えばいい。これでデバイスの現在地が地図上に表示される。さらにデバイスの向きも表示できる。
<MapboxGL.UserLocation showsUserHeadingIndicator={true} ></MapboxGL.UserLocation>
ついでに、地図をこの現在値に追従させることも可能。それには先ほどのCamera
のfollowUserLocation
をtrueにセットしてあげるだけ。ただし、これを設定すると地図を任意の場所に移動して表示することができなくなるっぽい。
<MapboxGL.MapView style={styles.map}> <MapboxGL.Camera zoomLevel={10} centerCoordinate={[-77.044211, 38.852924]} followUserLocation={true} /> <MapboxGL.UserLocation showsUserHeadingIndicator={true} /> </MapboxGL.MapView>
サンプルコード
というわけで全体的にはこんな感じ。
import React from 'react' import MapboxGL from '@react-native-mapbox-gl/maps' import {StyleSheet, View } from 'react-native' MapboxGL.setAccessToken( 'XXXXXXXXXXX' ) const styles = StyleSheet.create({ page: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, container: { height: '100%', width: '100%', backgroundColor: 'tomato', }, map: { flex: 1, }, }) } export default function App(): JSX.Element { const geoJson = { type: 'Feature', geometry: { type: 'LineString', coordinates: [ [-77.044211, 38.852924], [-77.045659, 38.860158], [-77.044232, 38.862326], [-77.040879, 38.865454], [-77.039936, 38.867698], [-77.040338, 38.86943], [-77.04264, 38.872528], [-77.03696, 38.878424], [-77.032309, 38.87937], [-77.030056, 38.880945], [-77.027645, 38.881779], [-77.026946, 38.882645], [-77.026942, 38.885502], [-77.028054, 38.887449], [-77.02806, 38.892088], [-77.03364, 38.892108], [-77.033643, 38.899926], ], }, } return ( <View style={styles.page}> <View style={styles.container}> <MapboxGL.MapView style={styles.map}> <MapboxGL.Camera zoomLevel={10} centerCoordinate={[-77.044211, 38.852924]} /> <MapboxGL.UserLocation showsUserHeadingIndicator={true} ></MapboxGL.UserLocation> <MapboxGL.ShapeSource id="source1" lineMetrics={true} shape={geoJson}, > <MapboxGL.LineLayer id="layer1" style={{ lineColor: 'red', lineWidth: 5 }} /> </MapboxGL.ShapeSource> </MapboxGL.MapView> </View> </View> ) }
余談
今回試してわかったのだけどiOSのシミュレータでの実行がreact-native-mapsと比べてかなりもっさりする。最初、それが既存の実装との兼ね合いなのかライブラリの問題なのかわからなかったけど。ライブラリのサンプルを動かしてみても同様に遅かったので実機で動かしてみたところサクサク動いたので一安心しつつそういうもんなんだなと思っている。今の所は。