3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OSM & ZENRIN Maps APIで活用!地番対応の住所正規化エンジンを試す

Posted at

📌 今回やりたかったこと

📍「住所検索の正確さを比較!Google Maps APIとZENRIN Maps API」では、Google Maps APIを使った住所検索について詳しく解説しましたが、
💡 標準機能では住所の「読み」を取得できない という課題がありました。
しかし、日本の住所を扱う上で 「読み」 は非常に重要な情報です!
そこで今回は、無料で使える「OpenStreetMap」
@geolonia/normalize-japanese-addresses を組み合わせて、
より高度な住所表示を実現する方法をご紹介します! 🚀

🤔 Google Maps APIでは「読み」の取得に工夫が必要

📌 記事内でも触れましたが、Google Maps APIのジオコーディング結果から「読み」を正確に取得するのは困難 です。
住所コンポーネントから無理やり抽出しても、
必ずしも正確な情報が得られるとは限らない のが現状です... 😢

✅ normalize-japanese-addressesの活用で「読み」もバッチリ!

🔧 @geolonia/normalize-japanese-addresses は、
日本の住所を正規化 し、以下のような情報を付与してくれる便利なライブラリです!

🔹 都道府県コード
🔹 市区町村コード
🔹 緯度・経度
🔹 そして「読み」情報も! 🎉

このライブラリを活用すれば、
正規化された住所情報 + 読み仮名情報簡単に取得できる ようになります! 💪✨

🎉 2か月無料でお試しできる ZENRIN Maps API 🆓

🗺 ZENRIN Maps API は、
日本国内の詳細な地図情報を提供する高精度なマップサービスです! 📍✨

💡 なんと、2か月間も無料でお試し可能! 🎊
このチャンスに、高精度な住所検索や地図機能 を体験してみませんか? 🚀

APIキーの取得方法はこちら! 🔽
ZENRIN Maps APIを使ってみた

🔎 難読住所の検索結果

日本には一見すると読みにくい住所がたくさんあります!📜💦
ここでは、「OpenStreetMap」「ZENRIN Maps API」 で検索した結果を比較してみます! 🧐


🏠 1. 埼玉県 蕨市(わらびし)

📌 OpenStreetMap
osm_address_warabi.png

📍 ZENRIN Maps API
ZMA_address_warabi.png


🏠 2. 千葉県 匝瑳市(そうさし)

📌 OpenStreetMap
osm_address_sousa.png

📍 ZENRIN Maps API
ZMA_address_sousa.png


🏠 3. 石川県 羽咋市(はくいし)

📌 OpenStreetMap
osm_address_hakui.png

📍 ZENRIN Maps API
ZMA_address_hakui.png


サンプルコード

Open Street Map

osm_address_search.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>住所検索</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css"/>
    <style>
        #mapid {
            height: 400px;
        }
    </style>
</head>
<body>
    <div id="mapid"></div>
    <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
    <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
    <script type="module" src="osm_address_searche.js"></script>
</body>
</html>
osm_address_searche.js
import { normalize } from './address-normalizer/node_modules/@geolonia/normalize-japanese-addresses/dist/main-esm.mjs';

// 地図の初期設定
var map = L.map('mapid', {
    center: [35.7122, 139.8117],
    zoom: 15,
    minZoom: 13,
    maxZoom: 16
});

// OpenStreetMapのタイルを追加
var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
});
tileLayer.addTo(map);

// Nominatim Geocoderの設定
var geocoder = L.Control.Geocoder.nominatim();

// Geocoderを地図に追加
var osmGeocoder = new L.Control.Geocoder({
    geocoder: geocoder
});
osmGeocoder.addTo(map);

// 検索結果の表示
osmGeocoder.on('markgeocode', async function(e) {
    var result = e.geocode;
    var latlng = result.center;
    var address = result.name;

    // 住所の正規化
    const normalizedAddress = await normalize(address);
    const prefecture = normalizedAddress.pref;
    const city = normalizedAddress.city;
    const town = normalizedAddress.town;

    // metadataから読み方を取得
    let prefread = '';
    let cityread = '';
    let machiread = '';

    if (normalizedAddress.metadata && normalizedAddress.metadata.prefecture && normalizedAddress.metadata.prefecture.pref_k) {
        prefread = normalizedAddress.metadata.prefecture.pref_k;
    }

    if (normalizedAddress.metadata && normalizedAddress.metadata.city && normalizedAddress.metadata.city.city_k) {
        cityread = normalizedAddress.metadata.city.city_k;
    }

    if (normalizedAddress.metadata && normalizedAddress.metadata.machiAza && normalizedAddress.metadata.machiAza.oaza_cho_k) {
        machiread = normalizedAddress.metadata.machiAza.oaza_cho_k;
    }

    const reading = prefread + cityread + machiread;

    // ポップアップの内容
    var popupContent = `
        住所: ${address}<br>
        読み方: ${reading}<br>
        緯度: ${latlng.lat}<br>
        経度: ${latlng.lng}
    `;

    // ポップアップを表示
    var popup = L.popup()
        .setLatLng(latlng)
        .setContent(popupContent)
        .openOn(map);
});

// 文字列をカタカナに変換する関数
function toKatakana(str) {
    return str.replace(/[\u3041-\u3096]/g, function(match) {
        var charCode = match.charCodeAt(0) + 96;
        return String.fromCharCode(charCode);
    });
}

ZENRIN Maps API

zma_address_search.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Display a map</title>
  <style>
    body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
    
    #search-bar {
      position: absolute;
      top: 10px;
      left: 10px;
      background-color: #fff;
      padding: 10px;
      border-radius: 5px;
      box-shadow: 0 0 10px rgba(0,0,0,0.2);
      z-index: 1;
    }

    #result {
      position: absolute;
      top: 70px;
      left: 10px;
      background-color: rgba(255, 255, 255, 0.9);
      padding: 10px;
      border-radius: 5px;
      box-shadow: 0 0 10px rgba(0,0,0,0.2);
      z-index: 1;
      display: none;
    }

    #ZMap {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }
  </style>
  
  <!-- ローダーを読み込む -->
  <script src="https://test-js.zmaps-api.com/zma_loader.js?key=YOUR_API_KEY&auth=referer"></script>
</head>
<body>
  <div id="search-bar">
    <input type="text" id="address" placeholder="住所を入力してください">
    <button onclick="searchAddress()">検索</button>
  </div>

  <!-- 検索結果の表示エリア -->
  <div id="result">
    <p><strong>検索結果:</strong></p>
    <p>住所: <span id="result-address">-</span></p>
    <p>住所読み: <span id="result-address_read">-</span></p> 
    <p>緯度: <span id="result-lat">-</span></p>
    <p>経度: <span id="result-lng">-</span></p>
  </div>

  <div id="ZMap"></div>

  <script src="js/zma_addressmatch3.js"></script>
</body>
</html>
zma_address_search.js
// グローバル変数として `map` と `mrk_widget` を定義
let map;
let mrk_widget;

// ローダーのメソッド実行
ZMALoader.setOnLoad(function (mapOptions, error) {
  if (error) {
    console.error(error)
    return
  }

  // 地図オブジェクト
//  var map;

  // 中心点の緯度経度(東京駅)
  const lat = 35.681406, lng = 139.767132;

  // マップコンテナ要素を取得する
  const mapElement = document.getElementById('ZMap');

  // MapOptionsをデフォルト値から変更する場合各パラメータに値を設定
  // 中心点の緯度経度を設定
  mapOptions.center = new ZDC.LatLng(lat, lng);
  mapOptions.zipsMapType = 'VeAmBrmV'
  mapOptions.mouseWheelReverseZoom = true; // ★マウスホイールのズーム方向の反転を指定


  // 地図を生成
  map = new ZDC.Map(
    mapElement,
    mapOptions,
    function() {
      // Success callback
      // コントロールを追加する
      // 左上 拡大縮小ボタン表示
      map.addControl(new ZDC.ZoomButton('bottom-right'));
      // 右上 コンパス表示
      map.addControl(new ZDC.Compass('top-right'));
      // 左下 スケールバー表示
      map.addControl(new ZDC.ScaleBar('bottom-left'));

    },
    function() {
      // Failure callback
    }
  );
})

async function searchAddress() {
  const address = document.getElementById("address").value;

  if (!address) {
      alert("住所を入力してください");
      return;
  }

  try {
      const response = await fetch('https://test-web.zmaps-api.com/search/address', {
          method: 'POST',
          headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'x-api-key': 'YOUR_API_KEY',
              'Authorization': 'referer'
          },
          body: new URLSearchParams({
              word: address,
              word_match_type: '3'
          })
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      console.log(data.result.item);

        if (data.status === "OK" && data.result.info.hit > 0) {
            const location = data.result.item[0].position;
              
              // 配列の順序が[経度, 緯度]であることを確認
              const lng = location[0];
              const lat = location[1];

              const latLng = new ZDC.LatLng(lat, lng);

              // 地図をその位置に移動
              map.setCenter(latLng);

              /* Markerの設置 */
              center_mrk =new ZDC.CenterMarker();
              // MarkerをMapに追加
              map.addControl(center_mrk);

              // 検索結果を表示
              document.getElementById("result-address").textContent = data.result.item[0].address;
              document.getElementById("result-address_read").textContent = data.result.item[0].address_read;
              document.getElementById("result-lat").textContent = lat;
              document.getElementById("result-lng").textContent = lng;
              document.getElementById("result").style.display = "block";

              console.log("緯度:", lat, "経度:", lng);
          } else {
              alert("住所の位置情報が見つかりませんでした");
          }
      }  catch (error) {
      console.error("エラーが発生しました:", error);
      alert("住所の検索中にエラーが発生しました");
  }
}

地図表示

Open Street Map
osm_address.png
ZENRIN Maps API
ZENRINMapsAPI 住所検索
zma_address_kucchanchou.png

解説

Open Street Map

1.準備:必要なライブラリの読み込み(HTML)

まず、HTMLファイルで必要なライブラリを読み込みます。
<head>タグ内で、Google Maps APIのスクリプトを読み込み、callbackとしてinitMap関数を指定しています。

osm_address_search.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>住所検索</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css"/>
    <style>
        #mapid {
            height: 400px;
        }
    </style>
</head>
<body>
    <div id="mapid"></div>
    <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
    <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
    <script type="module" src="osm_address_searche.js"></script>
</body>
</html>

Leaflet: 地図表示のための基本ライブラリです。CSSとJavaScriptファイルを読み込みます。
leaflet-control-geocoder: 住所検索機能を追加するためのライブラリです。CSSとJavaScriptファイルを読み込みます。
osm_address_searche.js: アプリケーションの主要なロジックを記述するJavaScriptファイルです。type="module"属性をつけて読み込むことで、ES Modules形式で記述されたJavaScriptファイルとして扱われます。
CSS: 地図表示領域のスタイルを定義しています。

2.住所正規化ライブラリのインポート(JavaScript)

JavaScriptファイル(osm_address_searche.js)で、住所正規化ライブラリをインポートします。

import { normalize } from './address-normalizer/node_modules/@geolonia/normalize-japanese-addresses/dist/main-esm.mjs';

@geolonia/normalize-japanese-addresses: 住所を正規化するためのライブラリです。この例では、ローカルにインストールされたモジュールを参照しています。(注意:このパスはプロジェクトの構成によって異なります。プロジェクトのnode_modulesディレクトリ内の実際のパスに合わせてください。

3.地図の初期設定(JavaScript)

// 地図の初期設定
var map = L.map('mapid', {
    center: [35.7122, 139.8117], // 初期表示の中心座標(緯度, 経度)
    zoom: 15,                // 初期ズームレベル
    minZoom: 13,                // 最小ズームレベル
    maxZoom: 16                 // 最大ズームレベル
});

// OpenStreetMapのタイルを追加
var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
});
tileLayer.addTo(map);

L.map('mapid', { ... }): 指定されたID(mapid)の要素に地図を埋め込みます。centerオプションで初期表示の中心座標を、zoomオプションで初期ズームレベルを設定します。
L.tileLayer(...): 地図のタイルレイヤーを追加します。この例では、OpenStreetMapのタイルを使用しています。attributionオプションで、著作権表示を設定します。

4.Geocoderの設定と地図への追加(JavaScript)

leaflet-control-geocoderを使って、住所検索機能を設定し、地図に追加します。

// Nominatim Geocoderの設定
var geocoder = L.Control.Geocoder.nominatim();

// Geocoderを地図に追加
var osmGeocoder = new L.Control.Geocoder({
    geocoder: geocoder
});
osmGeocoder.addTo(map);

L.Control.Geocoder.nominatim(): Nominatim Geocoderのインスタンスを作成します。
L.Control.Geocoder({ geocoder: geocoder }): Geocoderコントロールを作成し、Nominatim Geocoderのインスタンスを渡します。
osmGeocoder.addTo(map): Geocoderコントロールを地図に追加します。

5.検索結果の処理(JavaScript)

Geocoderで検索された住所情報を取得し、正規化して地図上に表示します。

// 検索結果の表示
osmGeocoder.on('markgeocode', async function(e) {
    var result = e.geocode;
    var latlng = result.center;
    var address = result.name;

    // 住所の正規化
    const normalizedAddress = await normalize(address);
    const prefecture = normalizedAddress.pref;
    const city = normalizedAddress.city;
    const town = normalizedAddress.town;

    // metadataから読み方を取得
    let prefread = '';
    let cityread = '';
    let machiread = '';

    if (normalizedAddress.metadata && normalizedAddress.metadata.prefecture && normalizedAddress.metadata.prefecture.pref_k) {
        prefread = normalizedAddress.metadata.prefecture.pref_k;
    }

    if (normalizedAddress.metadata && normalizedAddress.metadata.city && normalizedAddress.metadata.city.city_k) {
        cityread = normalizedAddress.metadata.city.city_k;
    }

    if (normalizedAddress.metadata && normalizedAddress.metadata.machiAza && normalizedAddress.metadata.machiAza.oaza_cho_k) {
        machiread = normalizedAddress.metadata.machiAza.oaza_cho_k;
    }

    const reading = prefread + cityread + machiread;

    // ポップアップの内容
    var popupContent = `
        住所: ${address}<br>
        読み方: ${reading}<br>
        緯度: ${latlng.lat}<br>
        経度: ${latlng.lng}
    `;

    // ポップアップを表示
    var popup = L.popup()
        .setLatLng(latlng)
        .setContent(popupContent)
        .openOn(map);
});

osmGeocoder.on('markgeocode', function(e) { ... }): Geocoderで検索が実行され、結果が地図上に表示されたときに実行されるイベントリスナーを設定します。
result.center: 検索結果の中心座標(緯度、経度)を取得します。
result.name: 検索結果の住所文字列を取得します。
normalize(address): 取得した住所を@geolonia/normalize-japanese-addressesで正規化します。
・normalizedAddress.metadataから読み仮名情報を取得します。 normalizedAddress.metadata の prefecture、city、machiAza プロパティが存在し、かつそれぞれの pref_k、city_k、oaza_cho_k プロパティが存在する場合に、その値を変数に格納します。
L.popup(): 地図上にポップアップを作成し、住所、読み仮名、緯度、経度を表示します。

ZENRIN Maps API

1.地図の初期化

zma_address_search.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Display a map</title>
    <style>
        /* スタイル設定 */
        body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
        
        #search-bar {
          position: absolute;
          top: 10px;
          left: 10px;
          background-color: #fff;
          padding: 10px;
          border-radius: 5px;
          box-shadow: 0 0 10px rgba(0,0,0,0.2);
          z-index: 1;
        }

        #result {
          position: absolute;
          top: 70px;
          left: 10px;
          background-color: rgba(255, 255, 255, 0.9);
          padding: 10px;
          border-radius: 5px;
          box-shadow: 0 0 10px rgba(0,0,0,0.2);
          z-index: 1;
          display: none;
        }

        #ZMap {
          position: absolute;
          top: 0;
          bottom: 0;
          width: 100%;
        }
    </style>
    
    <!-- ローダーを読み込む -->
    <script src="https://test-js.zmaps-api.com/zma_loader.js?key=YOUR_API_KEY&auth=referer"></script>
</head>
<body>
    <div id="search-bar">
        <input type="text" id="address" placeholder="住所を入力してください">
        <button onclick="searchAddress()">検索</button>
    </div>

    <!-- 検索結果の表示エリア -->
    <div id="result">
        <p><strong>検索結果:</strong></p>
        <p>住所: <span id="result-address">-</span></p>
        <p>住所読み: <span id="result-address_read">-</span></p>    
        <p>緯度: <span id="result-lat">-</span></p>
        <p>経度: <span id="result-lng">-</span></p>
    </div>

    <div id="ZMap"></div>

    <script src="js/zma_address_search.js"></script>
</body>
</html>

基本構造: HTML文書の基本的な構造を定義しています。
スタイル設定: CSSスタイルで、検索バー、結果表示エリア、地図のレイアウトを設定しています。
ローダーの読み込み: ZENRIN Maps APIのローダーを読み込みます。

2.初期関数

zma_address_search.js
ZMALoader.setOnLoad(function (mapOptions, error) {
   if (error) {
       console.error(error)
       return
   }

   // 地図オブジェクト
   const lat = 35.681406, lng = 139.767132;
   const mapElement = document.getElementById('ZMap');

   // MapOptionsを設定
   mapOptions.center = new ZDC.LatLng(lat, lng);
   mapOptions.zipsMapType = 'VeAmBrmV'
   mapOptions.mouseWheelReverseZoom = true;

   // 地図を生成
   map = new ZDC.Map(
       mapElement,
       mapOptions,
       function() {
           // Success callback
           map.addControl(new ZDC.ZoomButton('bottom-right'));
           map.addControl(new ZDC.Compass('top-right'));
           map.addControl(new ZDC.ScaleBar('bottom-left'));
       },
       function() {
           // Failure callback
       }
   );
})

地図の初期化: 東京駅を中心に地図を初期化し、ズームボタンやコンパスなどのコントロールを追加します。

3.住所検索関数 searchAddress()

zma_address_search.js
async function searchAddress() {
   const address = document.getElementById("address").value;

   if (!address) {
       alert("住所を入力してください");
       return;
   }

   try {
       const response = await fetch('https://test-web.zmaps-api.com/search/address', {
           method: 'POST',
           headers: {
               'Content-Type': 'application/x-www-form-urlencoded',
               'x-api-key': 'YOUR_API_KEY',
               'Authorization': 'referer'
           },
           body: new URLSearchParams({
               word: address,
               word_match_type: '3'
           })
       });

       if (!response.ok) {
           throw new Error(`HTTP error! status: ${response.status}`);
       }

       const data = await response.json();
       console.log(data.result.item);

       if (data.status === "OK" && data.result.info.hit > 0) {
           const location = data.result.item[0].position;
           const lng = location[0];
           const lat = location[1];

           const latLng = new ZDC.LatLng(lat, lng);

           // 地図をその位置に移動
           map.setCenter(latLng);

           /* Markerの設置 */
           center_mrk = new ZDC.CenterMarker();
           map.addControl(center_mrk);

           // 検索結果を表示
           document.getElementById("result-address").textContent = data.result.item[0].address;
           document.getElementById("result-address_read").textContent = data.result.item[0].address_read;
           document.getElementById("result-lat").textContent = lat;
           document.getElementById("result-lng").textContent = lng;
           document.getElementById("result").style.display = "block";

           console.log("緯度:", lat, "経度:", lng);
       } else {
           alert("住所の位置情報が見つかりませんでした");
       }
   } catch (error) {
       console.error("エラーが発生しました:", error);
       alert("住所の検索中にエラーが発生しました");
   }
}

住所入力のチェック: 住所が入力されていない場合、アラートを表示します。
住所検索APIの呼び出し: ZENRIN Maps APIの住所検索APIを使用して、入力された住所の位置情報を取得します。
地図の更新と結果表示: 検索結果に基づいて地図を更新し、住所や緯度経度を表示します。

🗺 OpenStreetMap vs ZENRIN Maps API 徹底比較!

地図サービスにはそれぞれ特徴があります。
ここでは OpenStreetMapZENRIN Maps API のメリット・デメリットを比較しながら、
さらに改善できるポイントも考えてみました!🔍


🌍 OpenStreetMap(OSM)

良いところ

🔹 無料で利用可能
 OpenStreetMapは完全無料!オープンソースの地図データを活用できます。

🔹 カスタマイズ性が高い
 地図データや表示方法を自由に変更でき、特定のニーズに合わせたアプリ開発が可能。

🔹 世界中の地図データが利用可能
 地域に関係なく、グローバルな地図データを利用可能!🌏

⚠️ 改善点

データ精度が地域によって異なる
 ボランティア更新のため、データの精度や更新頻度にばらつきあり。

商用利用には制限がある
 商用利用には特定の条件が必要。


🗾 ZENRIN Maps API

良いところ

🔹 高精度な地図データ
 専門の調査員による詳細な地図データで、日本国内の利用に最適!📍

🔹 リアルタイムデータの提供
 交通情報🚗や天候🌤をリアルタイムで提供。
 ナビゲーションや配送ルートの最適化に活用できる!

🔹 高度なルート検索機能
 車種や時間帯ごとの規制を考慮した精密なルート検索が可能。

⚠️ 改善点

有料サービス
 利用には契約が必要で、コストが発生。💰

カスタマイズ性が低い
 商用APIのため、OpenStreetMapほど自由なカスタマイズができない。


🎉 無料で使える住所正規化エンジン! 🏠

🚀 オープンソース住所正規化エンジンを地番住所に対応したメジャーバージョンをリリースしました!
使う前にチェック!
📝 使用方法を確認して、安全に活用しましょう!

3
4
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
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?