株式会社Infigate

Blog

ネイティブアプリにGoogleMapを表示し任意の住所をMap上にプロットする方法(iOS)


投稿日2024/1/18 更新日2024/1/18 システム

はじめに

今回はネイティブアプリ上で指定の住所の場所をマップ上で示してあげるといったよくある要件を実現させます。

静的なWebサイトでは決め打ちの住所に予めピンを指定した状態でコードの埋め込みができるため(会社住所など)時間がかかることは決してないのですが、動的にマップ上にプロットするという点、ネイティブアプリ上に組み込むという点でややポイントがありますので、そちらについて解説していきたいと思います。

今回のゴール

住所の形式を緯度経度に変換してマップ上にプロットする

事前準備

GoogleMapを利用するにはAPIの利用が必要なため、GoogleAccountの取得やGCPの登録、APIキーの取得までが済んでいる前提で進めていきます。

必要なライブラリ

以下2つのライブラリをプロジェクトに追加しておきます。

    yarn add react-native-geocoding
    yarn add react-native-maps

react-native-mapsの設定

react-native-mapsはパッケージの追加だけでは利用ができませんので、iOSディレクトリ配下にて以下の設定を続けて行っていきます。

podfileの編集

以下をtarget ‘*****’ doの前あたりに追加します。


  rn_maps_path = '../node_modules/react-native-maps'
  pod 'react-native-google-maps', :path => rn_maps_path

さらに、GoogleMapsSDKはiOS 13以上が必要になりますので、同ファイルの上部に定義があるplatform :iosのバージョンを13.4に書き換えます。

元々変数でmin_ios_versin_supportedとなっているはずなので、そちらの元の値を書き換えるほうが綺麗ですが、ここでは直接書き換えています。

platform :ios, '13.4'

AppDelegate.mmの編集

続いて、AppDelegateに以下を追加します。

#import "AppDelegate.h"
#import <GoogleMaps/GoogleMaps.h> // 追加

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.moduleName = @"*****";
  self.initialProps = @{};

  [GMSServices provideAPIKey:@"********************"]; // 追加

provideAPIKeyの@以降には実際に取得したAPIキーを指定してください。

info.plistの追加

最後に、位置情報の取得に関する定義をplistに追加します。
(こちらは現在地を取得するわけでない場合は不要かと思います。)

<key>NSLocationWhenInUseUsageDescription</key>
<string>地図上へのプロットのために位置情報を取得します</string>

コードの実装

これでiOSでGoogleMapのAPIを利用する準備が完了しましたので、実際にソースコードの実装をしていきます。

まずはコンポーネント全体のコードがこちらです。

import React, {useEffect, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import MapView, {Marker, PROVIDER_GOOGLE} from 'react-native-maps';
import Geocoder from 'react-native-geocoding';
import {deviceWidth} from '../../libs/dimensions';
import config from '../../../config';

const API_KEY: string = config.googleMapIos;
Geocoder.init(API_KEY, {language: 'ja'});

type Props = {
  address: string;
};

const GoogleMap: React.FC<Props> = props => {
  const [lat, setLat] = useState<number | null>(null);
  const [lng, setLng] = useState<number | null>(null);
  const address = props.address;

  useEffect(() => {
    const geocode = async (address: string) => {
      try {
        const response = await Geocoder.from(address);
        const {lat, lng} = response.results[0].geometry.location;
        setLat(lat);
        setLng(lng);
        console.log(`緯度: ${lat}, 経度: ${lng}`);
      } catch (error) {
        console.error(error);
      }
    };

    geocode(address);
  }, []);

  return (
    <View style={styles.container}>
      {lat !== null && lng !== null && (
        <MapView
          style={styles.map}
          provider={PROVIDER_GOOGLE}
          initialRegion={{
            latitude: lat,
            longitude: lng,
            latitudeDelta: 0.01,
            longitudeDelta: 0.01,
          }}>
          <Marker coordinate={{latitude: lat, longitude: lng}} />
        </MapView>
      )}
    </View>
  );
};

export default GoogleMap;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    width: deviceWidth - 48,
    height: deviceWidth - 48,
  },
});

要所の解説をしていきます

const API_KEY: string = config.googleMapIos;
Geocoder.init(API_KEY, {language: 'ja'});

住所の文字列をそのままGoogleMapに渡すことはできないため、住所を緯度経度に変換するためのGeocoderを初期化しています。ここのAPI KEYはreact-native-mapsで設定したものと同じものを使用します。

  useEffect(() => {
    const geocode = async (address: string) => {
      try {
        const response = await Geocoder.from(address);
        const {lat, lng} = response.results[0].geometry.location;
        setLat(lat);
        setLng(lng);
        console.log(`緯度: ${lat}, 経度: ${lng}`);
      } catch (error) {
        console.error(error);
      }
    };

    geocode(address);
  }, []);

Geocoderによる緯度経度の取得を待ってから画面を描画したいため、useEffectを使用して非同期処理を行い、取得結果をuseStateを使って変数に格納しています。
今回使用するのはgeometry.location.latとgeometry.location.lngだけなのでそれを取り出していますが、Geocoderのレスポンスは他にも様々な情報が取得できます(割愛)。

  return (
    <View style={styles.container}>
      {lat !== null && lng !== null && (
        <MapView
          style={styles.map}
          provider={PROVIDER_GOOGLE}
          initialRegion={{
            latitude: lat,
            longitude: lng,
            latitudeDelta: 0.01,
            longitudeDelta: 0.01,
          }}>
          <Marker coordinate={{latitude: lat, longitude: lng}} />
        </MapView>
      )}
    </View>
  );

latとlngの状態を監視してあげることで、取得結果を元に地図上にプロットするようにしています。

MapViewのinitialRegionは初期位置の指定、Markerはピンの位置の指定になります。

今回は初期位置=ピンの位置のため、同じ値をそれぞれ指定しています。

latitudeDelta, langitudeDeltaは表示倍率に該当するため、数値が小さければ小さいほどズームイン、数値が大きければ大きいほどズームアウトになるので、実際に目視で確認しながら調整されるとよいかと思います。

さいごに、先ほどのコンポーネントを呼び出す際は任意の箇所で以下のように呼び出します。

<GoogleMap address={"東京都******"} />

この記事を書いた人

Infigate

北海道札幌市のシステム開発会社、株式会社Infigateです。 活動内容やシステム開発技術に関するTips、DX化に関連する記事やその他地域活性化に繋がる情報等を発信していきます。

お問い合わせ

Contact

お見積ご相談は無料です。
どうぞお気軽にご相談くださいませ。