見出し画像

GoogleMapsAPIで郵便番号で住所変換を行う


やりたいこと

前回の記事でGoogleMaps APIで地図表示を行うサンプルコードの郵便番号の住所検索には、郵便番号検索APIを利用していたのですが、友人に試してもらったら「東京都庁の郵便番号(〒163-8001)で検索がエラーになる」「GoogleMaps APIで郵便番号の住所検索は出来ないの?」と指摘されましたので、今回は、郵便番号検索、緯度・経度の取得、地図表示の機能を全てGoogleMaps APIで行う様に改良した記録を公開します。

結論から言うと、東京都庁の郵便番号(〒163-8001)が検索エラーになるのは「事業所の個別郵便番号」がサポート対象外であることが理由でした。
GoogleMaps APIでは「事業所の個別郵便番号」がサポートされているケースもあります(東京都庁の郵便番号もエラーになりませんでした)。

郵便番号検索APIは事業所の個別郵便番号はサポート対象外

郵便番号検索APIは、zip-cloudというサイトで運営されており、日本郵便のWebサイトで公開されている郵便番号データを再配信するサービスです。との説明がありますが、運営会社は株式会社アイビスという民間企業です。
日本郵便が直接配信しているわけではなので、データ更新が最新でないリスクは多少ありますが、無料で利用できるAPIサービスです。
なお、東京都庁の郵便番号(〒163-8001)が検索エラーになるのは、データ品質の問題ではなく「事業所の個別郵便番号」(配達物数の多い大口事業所を表す個別の郵便番号)はサポート対象外であることが理由でした。

Google Maps APIで郵便番号から住所を取得する際の注意点と解決方法

注意点

Google Maps APIを使用して郵便番号から住所に変換する際、APIの戻り値の配列番号を使って市区町村名を特定する手法は推奨されていません。
現在のAPI仕様では、配列番号に依存すると正しい市区町村名を取得できない可能性があります。

解決方法

正しい住所情報を取得するためには、Google Maps APIのレスポンス内に含まれるaddress_componentsのtypes属性を利用して、住所コンポーネントを特定することが必要です。例えば、「locality」は市区町村名を、「neighborhood」は町名を示すため、これらのタイプに基づいて情報を抽出することが有効です。

具体例

以下は、東京都千代田区永田町の住所データの例です。
配列要素は6個であり、町名の「永田町」は4番目です。

[
    {"long_name": "日本", "types": ["country"]},
    {"long_name": "東京都", "types": ["administrative_area_level_1"]},
    {"long_name": "千代田区", "types": ["locality"]},
    {"long_name": "永田町", "types": ["neighborhood"]},
    {"long_name": "1丁目", "types": ["sublocality_level_2"]},
    {"long_name": "1-7", "types": ["premise"]}
]

次は、大阪市庁舎の「大阪府大阪市北区中之島1丁目3-20」の住所データ例です。配列要素は7個であり、町名の「中之島」は5番目です。

[
    {"long_name": "日本", "types": ["country"]},
    {"long_name": "大阪府", "types": ["administrative_area_level_1"]},
    {"long_name": "大阪市", "types": ["locality"]},
    {"long_name": "北区",   "types": ["word"]},
    {"long_name": "中之島", "types": ["neighborhood"]},
    {"long_name": "1丁目", "types": ["sublocality_level_2"]},
    {"long_name": "3-20", "types": ["premise"]}
]

以上の様に、日本の住所は、地域によって配列の要素数が変化しますので、配列番号に依存せず、typesプロパティを用いることで正確な住所コンポーネントを取得する必要があります。

インターネットで公開されているGoogleMaps APIの郵便番号から住所変換の古いサンプルコードは、配列番号に基づいて住所を取得しているものが割と多いのですが、これらは現在のGoogleMaps API仕様では正しく動作しないため、修正が必要です。


GoogleMaps APIを用いた郵便番号の住所変換と地図表示アプリ

以下に、GoogleMaps APIだけを用いた郵便番号の住所変換と地図表示アプリのデモ画面とサンプルコードを掲載します。

デモ画面

デモ画面では、東京都庁の郵便番号(16380010)を入力して住所検索ボタンで、「東京都」「新宿区」「西新宿」の住所と緯度・経度をGoogleMaps APIで自動取得して地図表示しています。
郵便番号検索APIでは「事業所の個別郵便番号」では検索エラーでしたが、GoogleMaps APIでは、検索できる様になりました。

サンプルコード

HTMLファイルの中にJavascriptを埋め込んだソースコードです。
文字コードは「UTF-8」にセットして保存して下さい
<script>タグ直下の 'YOUR_API_KEY' の部分に、自分用のGoogleMaps APIキーをセットするだけで動作します。
APIキーの取得方法は、前回の記事を参照して下さい

<!--
 GoogleMap API Sample Programe
 * 郵便番号で住所、緯度・経度を取得し地図を表示する Ver2
 * Copyright (c) 2024 アプリ活用研究会(Application Utilization Study Group)
 * Licensed under the MIT License
-->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>住所検索と地図表示</title>
    <script>
        const apiKey = 'YOUR_API_KEY'; // Google Maps APIキーを設定

        // Google Mapsのスクリプトを動的に読み込む
        function loadGoogleMapsScript() {
            const script = document.createElement('script');
            script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=initMap`;
            script.async = true;
            script.defer = true;
            document.head.appendChild(script);
        }

        window.onload = loadGoogleMapsScript;
    </script>
    <style>
        #map {
            height: 400px;
            width: 100%;
        }

        #postalCode {
            width: 100px;
        }
    </style>
</head>
<body>
    <h2>住所検索とGoogleマップ表示</h2>
    
    <form>
        <label for="postalCode">郵便番号(7桁):</label>
        <input type="text" id="postalCode" maxlength="7" placeholder="例: 1000001">
        <button type="button" onclick="searchAddress()">検索</button>
        <br><br>
        
        <label for="prefecture">都道府県:</label>
        <input type="text" id="prefecture" readonly>
        
        <label for="city">市区名:</label>
        <input type="text" id="city" readonly>
        
        <label for="town">町名:</label>
        <input type="text" id="town" readonly><br><br>
        
        <label for="address">番 地 :</label>
        <input type="text" id="address">
        
        <label for="building">ビル名:</label>
        <input type="text" id="building"><br><br>

        <button type="button" onclick="showMap()">地図表示</button>
        <br><br>

        <label for="latitude">緯度:</label>
        <input type="text" id="latitude" readonly><br><br>
        
        <label for="longitude">経度:</label>
        <input type="text" id="longitude" readonly><br><br>
    </form>
    
    <div id="map"></div>

    <script>
        let map, geocoder, marker = null;

        // Google Maps の初期化
        function initMap() {
            map = new google.maps.Map(document.getElementById("map"), {
                center: { lat: 35.681236, lng: 139.767125 }, // 初期表示は東京駅
                zoom: 15
            });
            geocoder = new google.maps.Geocoder();
        }

        // マーカーをリセットして新しいマーカーを表示
        function setMarker(location) {
            if (marker) marker.setMap(null);
            marker = new google.maps.Marker({
                map: map,
                position: location
            });
        }

        // 住所のフィールドを更新
        function updateAddressFields(components) {
            let prefecture = '', city = '', district = '', town = '';

            components.forEach(component => {
                if (component.types.includes('administrative_area_level_1')) prefecture = component.long_name;
                if (component.types.includes('locality')) city = component.long_name;
                if (component.types.includes('sublocality_level_1') || component.types.includes('administrative_area_level_2')) district = component.long_name;
                if ((component.types.includes('sublocality_level_2') || component.types.includes('neighborhood')) &&
                    !component.types.includes('administrative_area_level_1') &&
                    !component.types.includes('administrative_area_level_2') &&
                    !component.types.includes('locality') &&
                    !component.types.includes('country')) {
                    town = component.long_name;
                }
            });

            const cityDistrict = district ? `${city}${district}` : city;
            document.getElementById('prefecture').value = prefecture;
            document.getElementById('city').value = cityDistrict;
            document.getElementById('town').value = town;
        }

        // 緯度と経度のフィールドを更新
        function updateLatLngFields(location) {
            document.getElementById('latitude').value = location.lat();
            document.getElementById('longitude').value = location.lng();
        }

        // 住所検索の共通処理
        function geocodeAddress(address) {
            geocoder.geocode({ address, region: 'jp' }, (results, status) => {
                if (status === 'OK') {
                    const location = results[0].geometry.location;
                    updateAddressFields(results[0].address_components);
                    map.setCenter(location);
                    setMarker(location);
                    updateLatLngFields(location);
                } else {
                    alert('住所検索に失敗しました: ' + status);
                }
            });
        }

        // 郵便番号から住所を検索
        function searchAddress() {
            const postalCode = document.getElementById('postalCode').value;
            if (postalCode.length !== 7 || isNaN(postalCode)) {
                alert("有効な7桁の郵便番号を入力してください。");
                return;
            }
            geocodeAddress(postalCode);
        }

        // フル住所で地図表示
        function showMap() {
            const prefecture = document.getElementById('prefecture').value;
            const city = document.getElementById('city').value;
            const town = document.getElementById('town').value;
            const address = document.getElementById('address').value;
            const building = document.getElementById('building').value;

            if (!prefecture || !city || !town) {
                alert("住所が正しく入力されていません。");
                return;
            }

            const fullAddress = `${prefecture} ${city} ${town} ${address} ${building}`;
            geocodeAddress(fullAddress);
        }
    </script>
</body>
</html>

カスタマイズした感想

今回のカスタマイズは、前回記事のサンプルコードを試した友人からの質問で「東京都庁の郵便番号(〒163-8001)で住所検索がエラーになる」「GoogleMaps APIで郵便番号の住所検索は出来ないの?」と指摘されたことが始まりでした。
結果として、GoogleMaps APIの仕様と利用方法について勉強になったこと、郵便番号検索APIで検索エラーが発生する理由も「事業所の個別郵便番号」がサポート対象外であることが分かりましたので、個人的には、新しい知見が得られた良い経験でした。
APIの利用回数の課金が気になる人は、郵便番号の住所検索は、無料APIと使い分けする選択肢も有りだと思います。

今回も最後まで読んで頂いてありがとうございました。

いいなと思ったら応援しよう!

アプリ活用研究会
よろしければサポートお願いします! いただいたサポートは、note記事制作の活動費に使わせていただきます!