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.1 2023/07/09 bug-fix
6.3.0 2023/07/02 getPointsGSI()追加
6.2.0 2023/07/02 ip2address()追加
6.1.0 2022/12/30 ip2address()追加
6.0.4 2022/12/13 PHP8.2対応
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.3.0 2023/07/11 roundFloat() 追加
1.2.0 2023/04/22 exitIfLessVersion() 追加
1.1.2 2023/02/05 validString() 修正
1.11 2022/07/03 isCommandLine() 修正
1.1 2022/06/04 getValidString() 修正
サンプル・プログラムは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である。

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

メソッド 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)が提供する地図タイルも利用できるようにした。

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

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

動的マップ描画

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

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

その他の WebAPI

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

活用例

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

参考サイト

(この項おわり)
header