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

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

(2023年7月14日)検索キーの最小・最大長が指定できるようにした.
(2023年7月2日)国土地理院ジオコーディング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.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
6.3.0 2023/07/02 getPointsGSI()追加
6.2.0 2023/07/02 ip2address()追加
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
1.4.1 2023/09/30 コメントの訂正
1.4.0 2023/09/09 $_GET, $_POST参照をfilter_input()関数に置換
1.3.0 2023/07/11 roundFloat() 追加
サンプル・プログラムは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 クラス

  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: 
  50:     //Yahoo! JAPAN Webサービス アプリケーションID
  51:     //https://e.developer.yahoo.co.jp/register
  52:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  53:     var $YAHOO_APPLICATION_ID = '*****************************';

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

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

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

  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の呼び出し

 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: }

 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である。

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

メソッド drawGMap は、Googleマップを描画するためのJavaScriptを生成する。
まずソースとして、"https://maps.googleapis.com/maps/api/js" を読み込む。このとき、パラメータ keyGoogle Cloud Platform APIキー を渡してやる必要がある。
また、コールバック関数として initMap() を呼び出し、このユーザー関数内で初期化を行う。

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

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

地理院地図・OSM描画

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

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

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

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

メソッド 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マップの標準的なアルファベットアイコンによってマーキングする。

動的マップ描画

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

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

その他の WebAPI

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

活用例

みんなの知識 ちょっと便利帳」では、「グーグルマップで見る京都の地下鉄路線図」からリンクする「地図・住所から「最寄り駅」をグーグルマップで探す - 初期設定:京都府庁周辺」や、「地図・住所から「最寄り駅」をグーグルマップで探す」で本プログラムを利用し、検索しやすいページを提供している。ありがとうございます。

参考サイト

(この項おわり)
header