PHPで住所・ランドマークから最寄り駅を求める

(1/1)
HeartRails Geo API」は、路線/駅名データ等の地理情報を無償のWebAPIとして提供している。
これと、Googleや地理院地図、オープンストリートマップの地図サービスをクラウド連携することで、住所やランドマークから最寄り駅を求めるPHPプログラムを作ってみることにする。
なお、Yahoo! JavaScriptマップは、2020年(令和2年)10月31日をもってサービスを終了しており利用できない。

(2025年6月14日)GoogleMaps JavaScript APIの変更に対応した.

目次

サンプル・プログラムの実行例

PHPで住所・ランドマークから最寄り駅を求める
Googleマップ表示

サンプル・プログラム

圧縮ファイルの内容
stationsearch.phpサンプル・プログラム本体。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
stationsearch.php 更新履歴
バージョン 更新日 内容
4.5.0 2023/07/14 検索キーの最小・最大長の指定
4.4.0 2023/07/02 国土地理院ジオコーディングAPIを追加
4.3 2021/02/20 PHP8対応,Yahoo! JavaScriptマップ廃止
4.2 2020/03/21 OSM Nominatim Search API追加
4.1 2019/05/11 地理院地図、OpenStreetMapも利用できるようにした
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.5.0 2025/06/14 GoogleMaps JavaScript APIの変更に対応
6.4.0 2025/03/01 makeYOLP_GeoSelectCategory()--引数$flagWorld追加
6.3.3 2024/09/14 $this->NOMINATIM_EMAIL 追加
6.3.2 2024/02/14 getStaticMap() -- bug-fix
6.3.1 2023/07/09 bug-fix
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.8.1 2025/03/15 validRegexPattern() -- debug
1.8.0 2024/11/12 validRegexPattern() 追加
1.7.0 2024/10/09 validURL() validEmail() 追加
1.6.0 2024/10/07 isButton() -- buttonタグに対応
1.5.0 2024/01/28 exitIfExceedVersion() 追加
サンプル・プログラムは2つの機能を持つ。
1つは、そのまま実行すると、検索キーワードを入力する画面を表示する機能――[検索]ボタンを押下することで、検索結果を表示する。
もう1つは、前述の変数を GET 渡しで呼び出すことで、検索結果のみを表示する機能である。たとえば「東京都千代田区皇居外苑1-1」の最寄り駅を検索する場合は、URL で 'stationsearch.php?query=%93%8C%8B%9E%93s%90%E7%91%E3%93c%8B%E6%8Dc%8B%8F%8AO%89%91%82P%81%7C%82P' と指定してやればよい。

サンプル・プログラムの流れ

PHPで住所・ランドマークから最寄り駅を求める

準備:pahooGeoCode クラス

pahooGeoCode.php

  37: class pahooGeoCode {
  38:     var $items;     // 検索結果格納用
  39:     var $error;     // エラー・フラグ
  40:     var $errmsg;    // エラー・メッセージ
  41:     var $hits;      // 検索ヒット件数
  42:     var $webapi;    // 直前に呼び出したWebAPI URL
  43: 
  44:     // Google Cloud Platform APIキー
  45:     // https://cloud.google.com/maps-platform/
  46:     // ※Google Maps APIを利用しないのなら登録不要
  47:     var $GOOGLE_API_KEY_1 = '**************************';   // HTTPリファラ用
  48:     var $GOOGLE_API_KEY_2 = '**************************';   // IP制限用
  49:     var $GOOGLE_MAP_ID = '*************************';       // GoogleMaps ID
  50: 
  51:     // Yahoo! JAPAN Webサービス アプリケーションID
  52:     // https://e.developer.yahoo.co.jp/register
  53:     // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  54:     var $YAHOO_APPLICATION_ID = '*****************************';

地図描画や住所検索を行うために、クラスファイル "pahooGeoCode.php" を使用する。組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図や住所検索として Google を利用するのであれば、Google Cloud Platform APIキーマップID が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば、Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、それぞれ参照されたい。

準備:地図サービスの選択

stationsearch.php

  56: //地図描画サービスの選択
  57: //    0:Google
  58: //    2:地理院地図・OSM
  59: define('MAPSERVICE', 2);
  60: 
  61: //住所検索サービスの選択
  62: //    0:Google
  63: //    1:Yahoo!ジオコーダAPI
  64: //   11:HeartRails Geo API
  65: //   12:OSM Nominatim Search API
  66: //   13:国土地理院ジオコーディングAPI
  67: define('GEOSERVICE', 1);
  68: 
  69: //逆ジオコーディングサービスの選択
  70: //    0:Google
  71: //    1:Yahoo!JAPAN
  72: //   11:HeartRails Geo API
  73: //   21:簡易ジオコーディングサービス
  74: define('REVGEOSERVICE', 1);

表示する地図は、Googleマップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。
住所検索サービスは、GoogleYahoo!JAPANHeartRails Geo APIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、GoogleYahoo!JAPANHeartRails Geo API簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。
PHPで住所・ランドマークから最寄り駅を求める
地理院地図(淡色)
PHPで住所・ランドマークから最寄り駅を求める
OpenStreetMap

HeartRails Geo API 最寄駅検索

HeartRails Geo API」は、2015年(平成27年)9月現在、以下のAPIを無償公開している。
これらの API は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は XML で戻るという形である。
  • エリア情報取得 API
  • 都道府県情報取得 API
  • 市区町村情報取得 API
  • 町域情報取得 API
  • 最寄駅情報取得 API
  • 郵便番号による住所検索 API
  • 緯度経度による住所検索 API
  • キーワードによる住所検索 API
  • 「エリア名」 「市区町村名」 「町域名」 の連結コンボボックス
  • 「都道府県名」 「市区町村名」 「町域名」 の連結コンボボックス
  • 「郵便番号」 による住所検索フォーム
WebAPIのURL
URL
https://express.heartrails.com/api/xml

入力パラメータ
フィールド名 要否 内  容
method 必須 メソッド名:getStation(固定)
x 必須 最寄り駅を取得したい場所の経度(世界測地系)。
y 必須 最寄り駅を取得したい場所の緯度(世界測地系)。
応答データ(xml) response station name 駅名 line 路線名 distance 検索地点からの距離 x 経度 y 緯度 prefecture 都道府県 postal 郵便番号 next 次の駅 prev 前の駅

解説:HeartRails Geo APIの呼び出し

stationsearch.php

 221: /**
 222:  * HeartRails Express のURLを取得する
 223:  * @param   double $lat 緯度(世界測地系)
 224:  * @param   double $lng 経度(世界測地系)
 225:  * @return  string URL
 226: */
 227: function getURL_Heartrails($lat, $lng) {
 228:     $res = "http://express.heartrails.com/api/xml?method=getStations&x={$lng}&y={$lat}";
 229: 
 230:     return $res;
 231: }

stationsearch.php

 233: /**
 234:  * HeartRails Express API から必要な情報を配列に格納する
 235:  * @param   double $latitude  緯度(世界測地系)
 236:  * @param   double $longitude 経度(世界測地系)
 237:  * @return  array(ヒットした施設数, メッセージ, APIのURL)
 238:  * @return  int ヒット数
 239: */
 240: function getResults_Heartrails($latitude, $longitude, &$items) {
 241: //受信データの要素名
 242: $tbl = array(
 243:     'name',         //最寄駅名
 244:     'prev',         //前の駅名 (始発駅の場合は null)
 245:     'next',         //次の駅名 (終着駅の場合は null)
 246:     'x',            //最寄駅の経度 (世界測地系)
 247:     'y',            //最寄駅の緯度 (世界測地系)
 248:     'distance', //指定の場所から最寄駅までの距離 (精度は 10 m)
 249:     'postal',       //最寄駅の郵便番号 
 250:     'prefecture',   //最寄駅の存在する都道府県名
 251:     'line'          //最寄駅の存在する路線名
 252: );
 253: 
 254:     $url = $this->getURL_Heartrails($latitude, $longitude); //リクエストURL
 255:     $cnt = 1;
 256: 
 257: //PHP4用; DOM XML利用
 258:     if (! $this->isphp5over()) {
 259:         if (($dom = $this->read_xml($url)) == NULL) {
 260:             return array(FALSE, 'WebAPIのトラブルです.', FALSE);
 261:         }
 262:         $resultset = $dom->get_elements_by_tagname('response');
 263:         $results = $resultset[0]->get_elements_by_tagname('station');
 264:         //検索結果取りだし
 265:         foreach ($results as $element) {
 266:             foreach ($tbl as $name) {
 267:                 $node = $element->get_elements_by_tagname($name);
 268:                 if ($node !NULL) {
 269:                     $items[$cnt][$name] = (string)$node[0]->get_content();
 270:                 }
 271:             }
 272:             $items[$cnt]['id']        = $this->num2alpha($cnt);
 273:             $items[$cnt]['title']     = $items[$cnt]['name'];
 274:             $items[$cnt]['longitude'] = $items[$cnt]['x'];
 275:             $items[$cnt]['latitude']  = $items[$cnt]['y'];
 276:             $items[$cnt]['description']  =<<< EOT
 277: {$items[$cnt]['name']}&nbsp;({$items[$cnt]['line']}){$items[$cnt]['distance']}
 278: EOT;
 279:             $cnt++;
 280:         }
 281: 
 282: //PHP5用; SimpleXML利用
 283:     } else {
 284:         $response = simplexml_load_file($url);
 285:         //レスポンス・チェック
 286:         if (isset($response->station) == FALSE) {
 287:             return array(FALSE, 'WebAPIのトラブルです.', FALSE);
 288:         }
 289:         //検索結果取りだし
 290:         foreach ($response->station as $element) {
 291:             foreach ($tbl as $name) {
 292:                 if (isset($element->$name)) {
 293:                     $items[$cnt][$name] = (string)$element->$name;
 294:                 }
 295:             }
 296:             $items[$cnt]['id']        = $this->num2alpha($cnt);
 297:             $items[$cnt]['title']     = $items[$cnt]['name'];
 298:             $items[$cnt]['longitude'] = $items[$cnt]['x'];
 299:             $items[$cnt]['latitude']  = $items[$cnt]['y'];
 300:             $items[$cnt]['description']  =<<< EOT
 301: {$items[$cnt]['name']}&nbsp;({$items[$cnt]['line']}){$items[$cnt]['distance']}
 302: EOT;
 303:             $cnt++;
 304:         }
 305:     }
 306: 
 307:     return array($cnt - 1, '', $url);
 308: }

HeartRails Geo 最寄駅情報取得 APIの使い方は、前回と同じである。応答メッセージの処理は、PHP4では DOM XML を、PHP5以降では SimpleXML を使っている。

Googleマップ描画

Google Maps JavaScript API は、JavaScriptを使ってGoogleマップを動的に描画するためのWebAPIである。

pahooGeoCode.php

 867: /**
 868:  * Googleマップを描く
 869:  * @param   string $id        マップID
 870:  * @param   float  $latitude  中心座標:緯度(世界測地系)
 871:  * @param   float  $longitude 中心座標:経度(世界測地系)
 872:  * @param   string $type      マップタイプ:HYBRID/ROADMAP/SATELLITE/TERRAIN
 873:  * @param   int    $zoom      拡大率
 874:  * @param   string $call      イベント発生時にコールする関数(省略可)
 875:  * @param   array  $items     地点情報(省略可能)
 876:  *                  string title       タイトル
 877:  *                  string description 情報ウィンドウに表示する内容(HTML文)
 878:  *                  float latitude    緯度
 879:  *                  float longitude   経度
 880:  *                  string icon        アイコンURL
 881:  * @param   string $call2     追加スクリプト(省略可)
 882:  * @param   int    $max_width 情報ウィンドウの最大幅(省略時:200)
 883:  * @param   array  $offset    アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
 884:  * @return  string Googleマップのコード
 885: */
 886: function drawGMap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL) {
 887:     $key = $this->GOOGLE_API_KEY_1;
 888:     $call = ($call !NULL? $call . '()' : '';
 889:     if (! is_array($offset)) {
 890:         $offset = array(0, 0);
 891:     }
 892: 
 893:     $mapId  = $this->GOOGLE_MAP_ID;
 894:     $code =<<< EOT
 895: <script src="https://maps.googleapis.com/maps/api/js?key={$key}&loading=async&libraries=marker&callback=initMap&region=JP" async defer loading="async"></script>
 896: <script>
 897: function initMap() {
 898:     var map = new google.maps.Map(document.getElementById('{$id}'), {
 899:         center: { lat: {$latitude}, lng: {$longitude} },
 900:         mapId: '{$mapId}',
 901:         zoom: {$zoom},
 902:         mapTypeId: google.maps.MapTypeId.{$type},
 903:         mapTypeControl: true,
 904:         scaleControl: true
 905:     });
 906: 
 907:     map.addListener('dragend', getPointData);
 908:     map.addListener('zoom_changed', getPointData);
 909:     map.addListener('maptypeid_changed', getPointData);
 910: 
 911:     // イベント発生時の地図情報を取得・格納
 912:     function getPointData() {
 913:         var point = map.getCenter();
 914:         // 経度
 915:         if (document.getElementById("longitude") != null) {
 916:             document.getElementById("longitude").value = point.lng();
 917:         }
 918:         // 緯度
 919:         if (document.getElementById("latitude") != null) {
 920:             document.getElementById("latitude").value = point.lat();
 921:         }
 922:         // ズーム
 923:         if (document.getElementById("zoom") != null) {
 924:             document.getElementById("zoom").value = map.getZoom();
 925:         }
 926:         // 地図タイプ
 927:         if (document.getElementById("type") != null) {
 928:             var type_g = map.getMapTypeId();
 929:             var types = {"roadmap":"地図", "satellite":"航空写真", "hybrid":"ハイブリッド", "terrain":"地形図" };
 930:             for (key in types) {
 931:                 if (key == type_g) {
 932:                     document.getElementById("type").value = key;
 933:                     break;
 934:                 }
 935:             }
 936:         }
 937:         {$call}
 938:     }
 939: 
 940: EOT;
 941:     // 地点情報
 942:     if ($items !NULL) {
 943:         foreach ($items as $i=>$item) {
 944:             if ($i > 999)   break;      // 最大999箇所まで
 945:             $mark = (string)sprintf('%03d', $i);
 946:             // アイコン
 947:             $mark2 = ($i <26? $this->num2alpha($i: 'Z';
 948:             $icon = isset($item['icon']) ? $item['icon': 
 949:                         "https://www.google.com/mapfiles/marker{$mark2}.png";
 950:             list($icon_width, $icon_height) = getimagesize($icon);
 951:             if (isset($item['label']) && ($item['label'!'')) {
 952:                 $size1 = $item['label_size'* mb_strlen($item['label']);
 953:                 $size2 = (int)($size1 / 2);
 954:                 $ss =<<< EOT
 955:     const icon = document.createElement('div');
 956:     icon.innerHTML = `
 957:         <div style="position: relative; text-align: center;">
 958:         <img src="https://www.pahoo.org/common/space.gif" style="width:{$size1}px; height:{$size1}px;">
 959:         <div style="position: absolute; top:{$size2}px; left:0px; width:100%; font-size:{$item['label_size']}px; color:{$item['label_color']}; font-weight:{$item['label_weight']};">
 960:         {$item['label']}
 961:     </div>
 962:   </div>
 963: `;
 964: 
 965: EOT;
 966:             } else {
 967:                 $ss =<<< EOT
 968:     const icon = document.createElement('img');
 969:     icon.src = '{$icon}';
 970:     icon.style.width  = '{$icon_width}px';
 971:     icon.style.height = '{$icon_height}px';
 972:     icon.style.transform = 'translate(0%, 0%)';
 973: 
 974: EOT;
 975:             }
 976:             $code .=<<< EOT
 977: const marker_{$mark} = new google.maps.marker.AdvancedMarkerElement({
 978:     position: { lat: {$item['latitude']}, lng: {$item['longitude']} },
 979:     map: map,
 980:     content: (() => {
 981: {$ss}
 982:         return icon;
 983:     })(),
 984:     title: '{$item['title']}',
 985:     zIndex: 100
 986: });
 987: 
 988: EOT;
 989:             if (isset($item['description'])) {
 990:                 $code .=<<< EOT
 991: var infowindow_{$mark} = new google.maps.InfoWindow({
 992:     content: '{$item['description']}',
 993:     maxWidth: {$max_width},
 994:     pixelOffset: new google.maps.Size({$offset[0]}, {$offset[1]})
 995: });
 996: marker_{$mark}.addListener('gmp-click', function() {
 997:     infowindow_{$mark}.open(map, marker_{$mark});
 998: });
 999: 
1000: EOT;
1001:             }
1002:         }
1003:     }
1004:     // 追加関数
1005:     if ($call2 !NULL) {
1006:         $code .=<<< EOT
1007: {$call2}
1008: 
1009: EOT;
1010:     }
1011:     $code .=<<< EOT
1012: }
1013: </script>
1014: 
1015: EOT;
1016: 
1017:     return $code;
1018: }

メソッド drawGMap は、Googleマップを描画するためのJavaScriptを生成する。
まずソースとして、"https://maps.googleapis.com/maps/api/js" を読み込む。このとき、下表に示すパラメータを渡してやる必要がある。
GoogleMaps JavaScript API パラメータ
パラメータ 意味
key Google Cloud Platform APIキー  
loading async 非同期呼び出しを行う。
libraries marker 高度なマーカーを利用する。
callback initMap コールバック関数名
region JP 日本の地図を表示する。
また、非同期呼び出しのために sync defer loading="async" を指定する。
JavaScript のコールバック関数 initMap の中で、google.maps.Map インスタンスを生成する。このとき、後述する新しいマーカーを使うために、mapId要素にマップIDをセットする。

マップのドラッグ、ズーム変更、マップタイプ変更のイベントを拾って(addListner)、getPointDate() 関数を呼び出す。
このユーザー関数内で、緯度・経度、ズーム値、マップタイプを、IDで示されるオブジェクトに格納する。hidden属性のテキストボックスに格納することを想定している。
これにより、ページ切替が起きても地図の状態を保持できる。
また、本メソッドに JavaScriptを引数 $call として渡してやれば、getPointDate() 関数内で追加で呼び出す。

引数 $items を渡してやれば、地図上にマーキングする。
$items は2次元配列で、1次元目は地点番号(1以上)、2次元目は情報の種類である。たとえば $items[3]['description'] には、地点番号3の情報ウィンドウに表示する内容HTMLを代入する。
$items には、マッピングするマーカーURLも指定可能である。指定しない場合は、Googleマップの標準的なアルファベットマーカーによってマーキングする。

GoogleMapsの新しいマーカー(AdvancedMarkerElement)では、マーカーを DOMオブジェクトとして扱うことができる。
$items[3]['label'] が存在するときには、そこに代入された文字をマーカーにする。すなわち、空白画像(https://www.pahoo.org/common/space.gif)を背景として、div要素を使って文字を配置したオブジェクトを JavaScriptの変数 icon に格納する。
一方、$items[3]['label'] が存在しないときは、img要素を使って、指定されたURLにあるマーカー画像を JavaScriptの変数 icon に格納する。
そして、google.maps.marker.AdvancedMarkerElement のインスタンスを生成する際に、content要素として、いずれかを代入する。ここで、いずれも JavaScriptの文となっているため、そのまま content要素にセットすることができない。そこで、無名関数を用いて、iconの値を returnさせて content要素にセットするようにした。

地理院地図・OSM描画

国土地理院 は、基本測量結果や電子国土基本図、白地図、航空写真などを、各縮尺に応じて、256×256ドットの分割画像である地理院タイルとして、URLを指定して直接参照できるようにしている。

ここでは、このタイル画像を繋げて、Googleマップのようなユーザー・インターフェースを提供する無償のJavaScriptライブラリ Leaflet を利用することにする。

あわせて、自由に利用できる世界地図作成プロジェクト「OpenStreetMap」(OSM)が提供する地図タイルも利用できるようにした。

pahooGeoCode.php

1858: /**
1859:  * Leafletによるマップ描画
1860:  * @param   string $id        マップID
1861:  * @param   float  $latitude  中心座標:緯度(世界測地系)
1862:  * @param   float  $longitude 中心座標:経度(世界測地系)
1863:  * @param   string $type      マップタイプ:GSISTD/GSIPALE/GSIBLANK/GSIPHOTO/OSM
1864:  * @param   int    $zoom      拡大率
1865:  * @param   string $call      イベント発生時にコールする関数(省略可)
1866:  * @param   array  $items     地点情報(省略可能)
1867:  *                  string description 情報ウィンドウに表示する内容(HTML文)
1868:  *                  float  latitude    緯度
1869:  *                  float  longitude   経度
1870:  *                  string icon        アイコンURL
1871:  * @param   string $call2     追加スクリプト(省略可)
1872:  * @param   int    $max_width 情報ウィンドウの最大幅(省略時:200)
1873:  * @param   array  $offset    アイコンから情報ウィンドウのオフセット位置(省略時:NULL)
1874:  * @param   array  $overlays  オーバーレイ:GSIELEV/GSIFAULT/GSIFLOOD
1875:  * @return  string Leafletマップのコード
1876: */
1877: function drawLeaflet($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL, $overlays=NULL) {
1878: 
1879:     if (! is_array($offset)) {
1880:         $offset = array(0, 0);
1881:     }
1882:     // デフォルト・オーバーレイ
1883:     $addoverlay = '';
1884:     if ($overlays !NULL) {
1885:         foreach ($overlays as $overlay) {
1886:             $addoverlay .=<<< EOT
1887:     {$overlay}.addTo(map);
1888: 
1889: EOT;
1890:         }
1891:     }
1892: 
1893:     $code =<<< EOT
1894: <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
1895: <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
1896: <script>
1897: window.onload = function() {
1898:     let map = L.map('{$id}',{zoomControl:false});
1899:     map.setView([{$latitude}, {$longitude}], {$zoom});
1900:     L.control.scale({
1901:         maxWidth: 200,
1902:         position: 'bottomright',
1903:         imperial: false
1904:     }).addTo(map);
1905:     L.control.zoom({position:'topleft'}).addTo(map);
1906: 
1907:     // 地理院地図:標準地図
1908:     let GSISTD = new L.tileLayer(
1909:         'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
1910:         {
1911:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1912:             minZoom: 0,
1913:             maxZoom: 18,
1914:             name: 'GSISTD'
1915:         });
1916:     // 地理院地図:淡色地図
1917:     let GSIPALE = new L.tileLayer(
1918:         'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
1919:         {
1920:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1921:             minZoom: 2,
1922:             maxZoom: 18,
1923:             name: 'GSIPALE'
1924:         });
1925:     // 地理院地図:白地図
1926:     let GSIBLANK = new L.tileLayer(
1927:         'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
1928:         {
1929:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1930:             minZoom: 5,
1931:             maxZoom: 14,
1932:             name: 'GSIBLANK'
1933:         });
1934:     // 地理院地図:写真
1935:     let GSIPHOTO = new L.tileLayer(
1936:         'https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg',
1937:         {
1938:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1939:             minZoom: 2,
1940:             maxZoom: 18,
1941:             name: 'GSIPHOTO'
1942:         });
1943:     // OpenStreetMap
1944:     let OSM = new L.tileLayer(
1945:         'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
1946:         {
1947:             attribution: "© <a href='https://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
1948:             minZoom: 0,
1949:             maxZoom: 18,
1950:             name: 'OSM'
1951:         });
1952: 
1953:     // baseMapsオブジェクトにタイル設定
1954:     let baseMaps = {
1955:         "地理院地図" : GSISTD,
1956:         "淡色地図"   : GSIPALE,
1957:         "白地図"     : GSIBLANK,
1958:         "写真地図"   : GSIPHOTO,
1959:         "オープンストリートマップ" : OSM
1960:     };
1961: 
1962:     // 地理院地図:色別標高図(オーバーレイ)
1963:     let GSIELEV = new L.tileLayer(
1964:         'https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png',
1965:         {
1966:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1967:             opacity: 0.6,
1968:             minZoom: 5,
1969:             maxZoom: 15,
1970:             name: 'GSIELEV'
1971:         });
1972:     // 地理院地図:活断層図(オーバーレイ)
1973:     let GSIFAULT = new L.tileLayer(
1974:         'https://cyberjapandata.gsi.go.jp/xyz/afm/{z}/{x}/{y}.png',
1975:         {
1976:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1977:             opacity: 0.6,
1978:             minZoom: 11,
1979:             maxZoom: 16,
1980:             name: 'GSIFAULT'
1981:         });
1982:     // 地理院地図:治水地形分類図 更新版(オーバーレイ)
1983:     let GSIFLOOD = new L.tileLayer(
1984:         'https://cyberjapandata.gsi.go.jp/xyz/lcmfc2/{z}/{x}/{y}.png',
1985:         {
1986:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1987:             opacity: 0.6,
1988:             minZoom: 11,
1989:             maxZoom: 16,
1990:             name: 'GSIFLOOD'
1991:         });
1992: 
1993:     // baseMapsオブジェクトにオーバーレイ設定
1994:     let overlayMaps = {
1995:         "色別標高図"     : GSIELEV,
1996:         "活断層図"       : GSIFAULT,
1997:         "治水地形分類図" : GSIFLOOD,
1998:     };
1999: 
2000:     // layersコントロールにbaseMapsオブジェクトを設定して地図に追加
2001:     L.control.layers(baseMaps, overlayMaps).addTo(map);
2002:     {$type}.addTo(map);
2003: {$addoverlay}
2004: 
2005:     // イベント追加
2006:     map.on('viewreset', getPointData);
2007:     map.on('zoomend', getPointData);
2008:     map.on('baselayerchange', getPointData);
2009:     map.on('overlayadd', getPointData);
2010:     map.on('overlayremove', getPointData);
2011:     map.on('move', getPointData);
2012: 
2013:     // イベント発生時の地図情報を取得・格納
2014:     function getPointData() {
2015:         let pos = map.getCenter();
2016:         // 経度
2017:         if (document.getElementById('longitude') != null) {
2018:             document.getElementById('longitude').value = pos.lng;
2019:         }
2020:         // 緯度
2021:         if (document.getElementById('latitude') != null) {
2022:             document.getElementById('latitude').value = pos.lat;
2023:         }
2024:         // ズーム
2025:         if (document.getElementById('zoom') != null) {
2026:             document.getElementById('zoom').value = map.getZoom();
2027:         }
2028:         // タイプ
2029:         if (document.getElementById('type') != null) {
2030:             for (let k in baseMaps) {
2031:                 if (map.hasLayer(baseMaps[k])) {
2032:                     document.getElementById('type').value = baseMaps[k].options.name;
2033:                 }
2034:             }
2035:         }
2036:         // オーバーレイ
2037:         if (document.getElementById('overlays') != null) {
2038:             let str = '';
2039:             let cnt = 0;
2040:             for (let k in overlayMaps) {
2041:                 if (map.hasLayer(overlayMaps[k])) {
2042:                     if (cnt > 0)    str += ',';
2043:                     str += overlayMaps[k].options.name;
2044:                     cnt++;
2045:                 }
2046:             }
2047:             document.getElementById('overlays').value = str;
2048:         }
2049:         {$call}
2050:     }
2051: 
2052: 
2053: EOT;
2054:     // 地点情報
2055:     if ($items !NULL) {
2056:         foreach ($items as $i=>$item) {
2057:             if ($i > 999)   break;      // 最大999箇所まで
2058:             $mark = (string)sprintf('%03d', $i);
2059:             // アイコン
2060:             $mark2 = ($i <26? $this->num2alpha($i: 'Z';
2061:             $icon = isset($item['icon']) ? $item['icon': 
2062:                         "https://www.google.com/mapfiles/marker{$mark2}.png";
2063:             list($icon_width, $icon_height) = getimagesize($icon);
2064:             $offx = round($icon_width / 2);
2065:             $offy = $icon_height;
2066:             $info  = isset($item['description']) ? "marker_{$mark}.bindPopup('{$item['description']}', {maxWidth: {$max_width}, offset: [{$offset[0]}, {$offset[1]}] });" : '';
2067: 
2068:             // アイコン・ラベル
2069:             if (isset($item['label']) && ($item['label'!'')) {
2070:                 $ss =<<< EOT
2071:     let icon_{$mark} = new L.divIcon({
2072:         html: '<span style="color:{$item['label_color']}; font-size:{$item['label_size']}px; font-weight:{$item['label_weight']}; white-space:nowrap;">{$item['label']}</span>',
2073:         iconSize: [0, 0],
2074:         iconAnchor: [{$item['label_size']}, {$item['label_size']}],
2075:     });
2076: 
2077: EOT;
2078:             // 通常アイコン
2079:             } else {
2080:                 $ss =<<< EOT
2081:     let icon_{$mark} = new L.icon({
2082:         iconUrl: '{$icon}',
2083:         iconAnchor: [{$offx}, {$offy}]
2084:     });
2085: EOT;
2086:             }
2087:             $code .=<<< EOT
2088:     {$ss}
2089:     let marker_{$mark} = new L.Marker([{$item['latitude']}, {$item['longitude']}], {icon: icon_{$mark}}).addTo(map);
2090:     {$info}
2091: 
2092: EOT;
2093:         }
2094:     }
2095:     // 追加関数
2096:     if ($call2 !NULL) {
2097:         $code .=<<< EOT
2098: {$call2}
2099: 
2100: EOT;
2101:     }
2102:     $code .=<<< EOT
2103: }
2104: </script>
2105: 
2106: EOT;
2107: 
2108:     return $code;
2109: }

メソッド drawLeaflet は、地理院地図とOSMを描画するためのJavaScriptを生成する。
まずソースとして、UNPKG から Leaflet 本体とスタイルシートを読み込む。

Leaflet のレイヤを切り替えることで、以下の地図が切り替わるようにしてある。
  1. 地理院地図:標準地図
  2. 地理院地図:淡色地図
  3. 地理院地図:白地図
  4. 地理院地図:写真
  5. OpenStreetMap
この他、タイル形式の地図コンテンツがあれば、自由に追加できる。じつはGoogleマップもタイル形式で呼び出すことができるのだが、APIコールしないとGoogleの規約違反となるため、実装してはいけない。

マップのドラッグ、ズーム変更、レイヤ変更のイベントを拾って(on)、getPointDate() 関数を呼び出す。
このユーザー関数内で、緯度・経度、ズーム値を、IDで示されるオブジェクトに格納する。hidden属性のテキストボックスに格納することを想定している。
これにより、ページ切替が起きても地図の状態を保持できる。
また、本メソッドに JavaScriptを引数 $call として渡してやれば、getPointDate() 関数内で追加で呼び出す。

引数 $items を渡してやれば、地図上にマーキングする。
$items は2次元配列で、1次元目は地点番号(1以上)、2次元目は情報の種類である。たとえば $items[3]['description'] には、地点番号3の情報ウィンドウに表示する内容HTMLを代入する。
$items には、マッピングするアイコンURLも指定可能である。指定しない場合は、Googleマップの標準的なアルファベットアイコンによってマーキングする。

動的マップ描画

pahooGeoCode.php

2482: /**
2483:  * 地図サービスを利用してJavaScriptマップを描く
2484:  *
2485:  * @param   string $id        マップID
2486:  * @param   float  $latitude  中心座標:緯度(世界測地系)
2487:  * @param   float  $longitude 中心座標:経度(世界測地系)
2488:  * @param   string $type      マップタイプ
2489:  *                              Googleの場合 HYBRID/ROADMAP/SATELLITE/TERRAIN
2490:  *                              Yahoo!JAPANの場合 NORMAL/PHOTO/B1/OSM
2491:  * @param   int    $zoom      拡大率
2492:  * @param   string $call      イベント発生時にコールする関数(省略可)
2493:  * @param   array  $items     地点情報(省略可能)
2494:  *                  string title        タイトル(Yahoo!では無効)
2495:  *                  string description  情報ウィンドウに表示する内容(HTML文)
2496:  *                  float  latitude     緯度
2497:  *                  float  longitude    経度
2498:  *                  string icon         アイコンURL
2499:  *                  string label        アイコン・ラベル(省略可能)
2500:  *                  string label_size   アイコン・ラベルのサイズ(省略可能)
2501:  *                  string label_weight アイコン・ラベルの太さ(省略可能)
2502:  *                  string label_color  アイコン・ラベルの色(省略可能)
2503:  * @param   string $api   0:Google Maps JavaScript(省略時)
2504:  *                        2:地理院地図・OSM(Leaflet使用)
2505:  * @param   string $call2 追加スクリプト(省略可)
2506:  * @param   int    $max_width 情報ウィンドウの最大幅(省略時:200)
2507:  * @param   array  $offset    アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
2508:  * @param   array  $overlays  Leaflet用オーバーレイ
2509:  * @return  string JavaScriptマップのコード
2510: */
2511: function drawJSmap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $api=0, $call2=NULL, $max_width=200, $offset=NULL, $overlays=NULL) {
2512:     // マップタイプの読み替え
2513:     static $tbl1 = array('HYBRID'=>'PHOTO', 'ROADMAP'=>'NORMAL', 'SATELLITE'=>'PHOTO', 'TERRAIN'=>'PHOTO');
2514:     static $tbl2 = array('NORMAL'=>'ROADMAP', 'PHOTO'=>'SATELLITE', 'B1'=>'ROADMAP', 'OSM'=>'ROADMAP');
2515:     static $tbl3 = array('HYBRID'=>'GSISTD', 'ROADMAP'=>'OSM', 'SATELLITE'=>'GSIPHOTO', 'TERRAIN'=>'GSIPHOTO');
2516:     $type = strtoupper($type);
2517: 
2518:     switch ($api) {
2519:     // Google Maps JavaScript
2520:     case 0;
2521:         $type = isset($tbl2[$type]) ? $tbl2[$type: $type;
2522:         $js = $this->drawGMap($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset);
2523:         break;
2524: /** 2020年10月31日サービス終了
2525:     // Yahoo! JavaScriptマップ
2526:     case 1:
2527:         $type = isset($tbl1[$type]) ? $tbl1[$type] : $type;
2528:         $js = $this->drawYOLPmap($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2);
2529:         break;
2530: **/
2531:     // 地理院地図・OSM(Leaflet使用)
2532:     case 2:
2533:         $type = isset($tbl3[$type]) ? $tbl3[$type: $type;
2534:         $js = $this->drawLeaflet($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset, $overlays);
2535:         break;
2536:     }
2537: 
2538:     return $js;
2539: }

メソッド drawJSmap は、引数 $api の値によって、Googleマップ描画メソッド drawGMap、地理院地図・OSM描画メソッド drawLeaflet のいずれかを呼び出す。
マップタイプについては、相互に読み替えができるようにしてある。

その他の WebAPI

pahooGeoCode::searchPoint3 が呼び出すWebAPIについては、「PHPで住所・ランドマークから緯度・経度を求める」を参照のこと。
pahooGeoCode::getAddress3 が呼び出すWebAPIについては、「PHPで緯度・経度から住所を求める」を参照のこと。

活用例

みんなの知識 ちょっと便利帳」では、「地図・住所から「最寄り駅」をグーグルマップで探す」で本プログラムを利用し、検索しやすいページを提供している。ありがとうございます。

参考サイト

(この項おわり)
header