4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ドワンゴAdvent Calendar 2024

Day 23

住所一覧から地図にピンを打つくん作ってみた

Last updated at Posted at 2024-12-23

住所から緯度経度がほしいぞ

ひょんなひらめきからジオコーディング技術が必要になり、「シンプル ジオコーディング 実験」に参加したのでその記録を残します。

インフルエンザの冬

乾燥の季節、冬です。
冬といえばインフルエンザが蔓延しますね。

みなさんおなじみの関東ITソフトウェア健康保険組合ではインフルエンザ予防接種の接種費用補助が受けられます。
https://www.its-kenpo.or.jp/kanri/influenza.html

これを利用しない手はないでしょう。

病院探しがちょっと手間

補助の対象となる病院は接種会場一覧のExcelファイルから確認できます。
しかし、すべての都道府県の情報が一つのファイルにまとまっていて、近所の病院だけを探し出すのが少し面倒です。
検索してしまっても良いですが、ここはクリエイティビティを発揮しましょう。

住所データから地図にピン打つくんを作る

理想を考えたとき、地図上に補助対象病院が記されている景色が脳裏をよぎりました。
そこで今回は、病院一覧データに含まれる住所情報を使って、病院の位置にピンを打ったマップを生成してみます。

実装について

Webアプリケーションとして実装しました。
主な使用技術はこちら。

  • Next.js
  • Leafret
  • シンプルジオコーディング

Leafret

地図表示ライブラリ。
強い意志があったわけではないですが、体感コスト低めに使えた気がします。
地図の画像データは別で用意する必要があり、そちらは OpenStreetMap を使用しました。

シンプルジオコーディング

シンプル ジオコーディング 実験って?

東京大学で行われている研究とのことです。
以下の内容を目的として行っているそうな。

本実験は,研究開発したジオコーディングエンジンを,特定のデータセットに含まれる住所表記ではなく,広く一般的に利用されている住所表記に対して適用し,その変換精度および変換速度を検証する目的で,試験として行っています.

利用規約に同意してAPIを使用することで実験参加となるようなので、参加してみました。

シンプルジオコーディング実験万歳

今回のジオコーディングではこちらの実験に助けられました。

地図上での位置マッピングにて難点となるのがジオコーディング。
住所から座標を求めるにはなかなかの課題があります。
有料サービスを使用することで解決する手もありますが、今回のようなお遊び実装では節約したい気持ちが出てしまいます。

この実験に出会うまでに自力で簡易ジオコーディングを頑張った際のつらい話は おまけ にて。

完成品がこちら

(リリースはサボったので動きが見たい方はリポジトリからどうぞ。)

地点の名称とその住所をCSV形式で与えると地図上にピンを打つ仕様です。
ITSの補助対象病院一覧は権利の問題で掲載できないので、気分で集めた愛知県にあるコメダ珈琲20件分のデータでお試し動作させます。

本店,愛知県名古屋市瑞穂区上山町3ー14ー8
高岳店,愛知県名古屋市東区泉2ー21ー3
松風店,愛知県名古屋市昭和区松風町1−7
上前津店,愛知県名古屋市中区大須3−31−42
東郊通店,愛知県名古屋市昭和区御器所1−3−1
五女子店,愛知県名古屋市中川区露橋2−25−15
刈谷店,愛知県刈谷市井ヶ谷町天白19−4
本町店,愛知県名古屋市中区大須2−5−13
新栄店,愛知県名古屋市中区新栄1ー17ー1
平和店,愛知県名古屋市中区平和1−10−10コスモパレス
庄内通店,愛知県名古屋市西区庄内通4−1庄内ハイツ
浦里店,愛知県名古屋市緑区浦里1−160グランドハイツ浦里
児玉店,愛知県名古屋市西区児玉町3ー5ー10
法華店,愛知県名古屋市中川区法華西町1−13
野並店,愛知県名古屋市天白区井の森232−1アイコービル1F
中割店,愛知県名古屋市南区中割町3−120−1
山手店,愛知県名古屋市昭和区山手通4ー15ペポ山手ビル2F
港店,愛知県名古屋市港区名港2−9−27ポートプラザビル1F
城北店,愛知県名古屋市北区八代町2−10−2
豊田梅坪店,愛知県豊田市梅坪町1−18−1

このCSVファイルを読み込ませた結果がこちら。
image.png

豊田市に住んでいて「近所の店舗だけ知りたいぞ」という場合は、絞り込みもできます。
image.png

全件表示させてしまうとシンプルジオコーディング実験のサーバーに負荷をかけてしまうため、一度に最大20件までの実行としました。
そのため、本来の目的である病院位置のマッピング時には豊田市の実行例のように住んでいる地名などを入れることを想定しています。

ジオコーディングAPIの利用

実装自体は変わったことをしているわけではないため、実装内容説明はAPI使用部について触れるのみにしておきます。

以下のようにaddrパラメータを指定することで緯度経度情報が含まれているXMLが返ってくる仕様でした。
https://geocode.csis.u-tokyo.ac.jp/cgi-bin/simple_geocode.cgi?charset=UTF8&addr=愛知県名古屋市瑞穂区上山町3ー14ー8

実行結果

<results>
    <query>愛知県名古屋市瑞穂区上山町3ー14ー8</query>
    <geodetic>wgs1984</geodetic>
    <iConf>5</iConf>
    <converted>愛知県名古屋市瑞穂区上山町3ー14ー</converted>
    <candidate>
        <address>愛知県/名古屋市/瑞穂区/上山町/三丁目/14番地</address>
        <longitude>136.949219</longitude>
        <latitude>35.137566</latitude>
        <iLvl>7</iLvl>
    </candidate>
</results>

読み込んだ住所一覧CSVファイルから取得した住所を使って上記APIを叩くことで緯度経度を得ています。
(jsミリしらなので参考程度にどうぞ)

const updateMapData = () => {
    const reader = new FileReader();
    reader.onload = async (e) => {
      if (!e.target) return;
      const csv = e.target.result as string;
      const csv_data = parse<string[]>(csv, { header: false });

      const fetchMapData: MapData[] = [];

      // 件数を抑えるため、都道府県や検索ワードで絞り込みを行う
      const filtedData = csv_data.data.filter((result) => {
        return result[1].includes(pref) && result[1].includes(searchText);
      });

      Promise.all(filtedData.slice(0, maxPin).map(async (result) => {
        const name = result[0];
        const address = result[1];

        // パラメータに住所を追加してAPIを叩く
        const url = "https://geocode.csis.u-tokyo.ac.jp/cgi-bin/simple_geocode.cgi?charset=UTF8&addr=";
        await fetch(url + address)
          .then((res) => res.text())
          .then((data) => {
            const parser = new DOMParser();
            const doc = parser.parseFromString(data, "text/xml");
            const longitude = doc.getElementsByTagName("longitude")[0].textContent;
            const latitude = doc.getElementsByTagName("latitude")[0].textContent;
            fetchMapData.push({
              name: name,
              point: {
                longitude: parseFloat(longitude || "0"),
                latitude: parseFloat(latitude || "0"),
              },
            });
        });
      })).then(() => {
        setMapData(fetchMapData);
      });
    };
    if (!file) return;
    reader.readAsText(file);
  };

おまけ

住所つらい話。
自力簡易ジオコーディングをしようとした結果、住所つらいなぁと感じたので印象的だった内容を残します。

市区町村名、大字・丁目名、小字・通称名 の分割が難しい

住所を記載する際、ほとんどの場合一つの文字列として扱ってしまいます。
これをジオコーディングなどで使用する場合はそれぞれの要素を分割する必要が出てくるわけです。
例えば今回使用したAPIの実行結果の中には住所を分割した結果が記されています。

愛知県名古屋市瑞穂区上山町3ー14ー8
愛知県/名古屋市/瑞穂区/上山町/三丁目/14番地

これが素人には無理すぎました。
名称の規則性には例外が多くあり、これらをひとまとまりに分割する仕組みを作るには住所に対する理解がまるで足りません。

世の中のジオコーディング装置どうやって実装されてるんだ...?

表記ゆれ

住所というものはとてもあいまいな表現が許されています。
例えば、
ほげほげ町一丁目2-3
ほげほげ町1丁目2-3
ほげほげ町1-2-3
ほげほげ町1ー2ー3
は同じ住所でしょう。

世の中の住所一覧はこれらの記載が混ざりに混ざっているため機械的に振り分けるのがとても難しいのです。

〇丁目

表記ゆれが解決されれば平和になると考えていた時代が私にもありました。

住所に頻出する「丁目」的な概念。
これは「[数値] + 丁目」というものではなく、文字列全体で名詞なのだとか。
なので一部地域では「〇〇丁」であったり、「〇〇丁目北」と表記されていたりします。

正しく表記されている場合はたいして問題にならないと思いますが、表記ゆれとの合わせ技でじわじわと苦しめてきます。

ほげほげ町1-2-3
ほげほげ町1丁目北4-5

のような混ざり方をされるとおしまいなのです。

まとめ

なんとか理想のマップが生み出せたのでニコニコです。
ちょっとしたジオコーディングをしたい場合は今回参加した実験に参加してみると楽に実装できてうれしいかもしれません。
ついでに研究への貢献をしている感があってよいです。

住所がつらい問題については、人類が緯度経度を標準で使うようになれば解決ですね。(地盤は動いているので無理)

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?