React NativeでMapにマーカーを動的に表示させる


(1)react-native-mapsで動的にマーカーを表示する

普段はソーシャルワーカー(PSW)として障がいをお持ちの方の支援をしています。ReactやReact Nativeでアプリ作成を楽しんでいます。以前作成したReactNativeアプリでreact-native-mapsを使用したので、まとめておこうと思います。

参考にした記事【ReactNative】react-native-mapsで地図の表示領域が描画される度に領域情報を取得する

React Native のアドベントカレンダーを作成してくれた@nekonikiさんの記事です、ありがとうございます

(2)およそ 25000 か所

こちらは日本全国にある障がい者向けの就労支援事業所の数です、近年とても増えてきておりIT技術を学ぶことができる事業所もあります。すべての事業所を一度マップにしてみたいなと思っていたので、データ集めから始めました。 各都道府県及び政令指定都市が法に基づいて公開しています、しかし各都道府県のデータは様式がさまざま、PDF形式やExcel形式などであり、スクレイピングを併用しながらデータ集めをしました。結構な時間がかかりました💦


ちなみに介護保険の全国事業所一覧については、デジタル庁が運営する データカタログサイト で取得することができます、ジオコードも含まれており、すぐにマップに落とすことができるのでとても便利です。

(3)データの加工

全国の事業所情報を集めることができたら加工を行っていきます。
まずは、住所からジオコードを作成します。エクセルでyahooジオコードを利用します。
=FILTERXML(WEBSERVICE("https://map.yahooapis.jp/geocode/V1/geoCoder?appid=<アプリケーションID>&query=" & <住所>),"//Coordinates")
セルにこの関数を入力することで一気にジオコードが取得できます。うまく変換できていない場合もあるので、それなりにデータチェックが必要です。また、取得されたジオコードを緯度経度にセルを分けます。
csv形式で保存しnodejsを使ってjson形式に変換します。

// tojson.js
const fs = require("fs");
const csv = require("csv");

fs.createReadStream("変換したいファイル.csv").pipe(
  csv.parse({ columns: true }, function (err, data) {
    function writeFile(path, data) {
      const jsonStr = JSON.stringify(data);
      fs.writeFile(path, jsonStr, (err) => {
        if (!err) {
          console.log("Json作成完了");
          console.log(data);
        }
      });
    }
  })
);
// node tojson 変換したいファイル.csv

作成したjsonをJavaScript オブジェクトへ変換して準備完了です。

(4)コード

さて作成したデータをreact-native-mapsに表示させましょう。
使用したライブラリ
・react-native
・expo 42.0.1
・react-native-maps 0.28.0

マップのコンポーネントになります

 <MapView
  onRegionChangeComplete={handleRegionChange} // 地図を移動し終えた後に発火するイベント
  initialRegion={{ 
      latitude: 35.681236, // 初期表示は東京駅に設定
      longitude: 139.767125,
      latitudeDelta: 0.05, // 初期表示範囲
      longitudeDelta: 0.05,
     }}
      minZoomLevel={9} // 初期縮尺レベル
      maxZoomLevel={30}
   >
    // mapを使って作成したデータオブジェクトを展開
    // mapListは作成したデータを必要な形(次のコード)にしてセットする変数
     {mapList.map((item) => { 
        return (
        <Marker
           pinColor={"red"}
           key={item.id} // データidをインデックスに 
           title={item.name} 
           coordinate={{
            latitude: Number(item.lat), // 緯度経度を数値型に変換
            longitude: Number(item.lng),
          }}
           >
          <Callout
              onPress={() => {
              navigation.navigate(); // 事業所個別ページに遷移
              }}
            >
          <View>
           <Text>
            {item.name} // ピンをタップしたときに表示させる
           </Text>
          </View>
        </Callout>
       </Marker>
     );
   })}
</MapView>

地図にデータを表示させる時に事業所数が多いと読み込みに時間がかかり、またごちゃごちゃするため、一定の範囲内にあるデータのみを表示させましょう。地図を動かす毎に基準のregionが変化し、その範囲内にある事業所が新たに表示されます。

import { list } from "作成したファイル.js"; // 作成したデータをオブジェクトに変換したjsファイル
import { dummy } from "ダミーファイル.js"; // 初期表示させるダミーデータjsファイル

 const [mapList, setMapList] = useState(dummy); // 初期表示はダミーデータでエラー回避
 const [map, setMap] = useState({}); // onRegionChangeCompleteで取得されるregionをセットする変数

 handleRegionChange = (region) => {
    setMap(region);
        let newList = list.filter(
          (item) =>
            item.lng >= map.longitude - 0.3 && // 緯度経度でフィルターをかけ
            item.lng <= map.longitude + 0.3 && 
            item.lat >= map.latitude - 0.1 &&
            item.lat <= map.latitude + 0.1
        );
        setMapList(newLlist); // 範囲内の事業所をmapListにセット
    }
  };

iosはAppleマップを使う場合特に設定は必要ないのですが、androidはapp.jsに下記の記載が必要です。
GCPコンソールから Maps SDK for Android を取得してください。

"android": {
      "package": "",
      "permissions": [],
      "versionCode": ,
      "config": {
        "googleMaps": {
          "apiKey": APIキー // Maps SDK for Android
        }
      }
    },

(5)終わりに

実際の動きです

二日続けてAdvent Calendarに参加させてもらいました書き留めていたものを年末に書き出して来年も良いreactな一年にしたいなと思っています!