React NativeでMapboxを使ってみる

メモです。

これまでReact Nativeでの地図として、react-native-mapsを使ってきた。これを使うとiOSではAppleのMap、AndroidではGoogle Mapsで地図が表示される。

地図そのものの表現はこれでも特に問題はなかったものの、ビジュアライゼーションとかナビゲーション周りを実装したくMapboxを検討することにした。ちなみにナビゲーションに関してMapbox公式からはiOSAndroid向けの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向けのやつがそのまま使えないかと思ったがそうでもないようだ。

github.com

なお、今回は冒頭で言ったようにすでに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はないのでShapeSourceLineLayerを使って描画する。このとき線を引くための座標情報は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>

ついでに、地図をこの現在値に追従させることも可能。それには先ほどのCamerafollowUserLocationを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と比べてかなりもっさりする。最初、それが既存の実装との兼ね合いなのかライブラリの問題なのかわからなかったけど。ライブラリのサンプルを動かしてみても同様に遅かったので実機で動かしてみたところサクサク動いたので一安心しつつそういうもんなんだなと思っている。今の所は。

©Keisuke Nishitani, 2023   プライバシーポリシー