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

サンプル・プログラム
stationsearch.php | サンプル・プログラム本体。 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
4.7.0 | 2025/08/24 | .pahooEnv導入 |
4.6.0 | 2025/07/19 | マップ中心マーカー表示オプションを追加 |
4.5.0 | 2023/07/14 | 検索キーの最小・最大長の指定 |
4.4.0 | 2023/07/02 | 国土地理院ジオコーディングAPIを追加 |
4.3 | 2021/02/20 | PHP8対応,Yahoo! JavaScriptマップ廃止 |
バージョン | 更新日 | 内容 |
---|---|---|
6.8.0 | 2025/08/10 | アクセスキーなどを ".env" に分離 |
6.7.1 | 2025/07/26 | jsLine_Gmap() - bug-fix |
6.7.0 | 2025/07/20 | drawJSmap,drawGMap -- 引数 $markerLevel 追加 |
6.6.0 | 2025/07/19 | drawJSmap,drawGMap,drawLeaflet -- マップ中心マーカー表示引数を追加 |
6.5.0 | 2025/06/14 | GoogleMaps JavaScript APIの変更に対応 |
バージョン | 更新日 | 内容 |
---|---|---|
2.0.1 | 2025/08/11 | getParam() bug-fix |
2.0.0 | 2025/08/11 | pahooLoadEnv() 追加 |
1.9.0 | 2025/07/26 | getParam() 引数に$trim追加 |
1.8.1 | 2025/03/15 | validRegexPattern() debug |
1.8.0 | 2024/11/12 | validRegexPattern() 追加 |
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 の https対応


Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dllLinuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。
準備:pahooGeoCode クラス
pahooGeoCode.php
40: class pahooGeoCode {
41: public $items; // 検索結果格納用
42: public $error; // エラー・フラグ
43: public $errmsg; // エラー・メッセージ
44: public $hits; // 検索ヒット件数
45: public $webapi; // 直前に呼び出したWebAPI URL
46:
47: // -- 以下のデータは .env ファイルに記述可能
48: // Google Cloud Platform APIキー
49: // https://cloud.google.com/maps-platform/
50: // ※Google Maps APIを利用しないのなら登録不要
51: public $GOOGLE_API_KEY_1 = ''; // HTTPリファラ用
52: public $GOOGLE_API_KEY_2 = ''; // IP制限用
53: public $GOOGLE_MAP_ID = ''; // GoogleMaps ID
54:
55: // Yahoo! JAPAN Webサービス アプリケーションID
56: // https://e.developer.yahoo.co.jp/register
57: // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
58: public $YAHOO_APPLICATION_ID = '';
59:
60: // OSM Nominatim Search API利用時に知らせるメールアドレス
61: // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
62: // ※OSM Nominatim Search APIを利用しないのなら登録不要
63: public $NOMINATIM_EMAIL = '';
64:
65: // IP2Location.io APIキー
66: // https://www.ip2location.io/
67: // ※IP2Location.ioを利用しないのなら登録不要
68: public $IP2LOCATION_API_KEY = '';

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

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。
準備:pahooInputData 関数群

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。
準備:各種定数など
stationsearch.php
56: // 各種定数(START) ===========================================================
57:
58: // 地図描画サービスの選択
59: // 0:Google
60: // 2:地理院地図・OSM
61: define('MAPSERVICE', 2);
62:
63: // 住所検索サービスの選択
64: // 0:Google
65: // 1:Yahoo!ジオコーダAPI
66: // 11:HeartRails Geo API
67: // 12:OSM Nominatim Search API
68: // 13:国土地理院ジオコーディングAPI
69: define('GEOSERVICE', 1);
70:
71: // 逆ジオコーディングサービスの選択
72: // 0:Google
73: // 1:Yahoo!JAPAN
74: // 11:HeartRails Geo API
75: // 21:簡易ジオコーディングサービス
76: define('REVGEOSERVICE', 1);
77:
78: // マップの表示サイズ(単位:ピクセル)
79: define('MAP_WIDTH', 600);
80: define('MAP_HEIGHT', 400);
81: // マップID
82: define('MAPID', 'map_id');
83: // マップ中心マーカーのURL;表示したくなければNULLにする
84: define('CENTER_MARKER', 'https://maps.google.com/mapfiles/ms/micons/ltblu-pushpin.png');
85:
86: // 初期値
87: define('DEF_LATITUDE', 35.7); // 緯度
88: define('DEF_LONGITUDE', 139.7); // 経度
89: define('DEF_TYPE', 'roadmap'); // マップタイプ
90: define('DEF_ZOOM', 13); // ズーム
91: define('DEF_CATEGORY', 'address'); // カテゴリ
92:
93: // 検索キーの最小文字長
94: define('QUERY_MIN_LEN', 3);
95:
96: // 検索キーの最大文字長
97: define('QUERY_MAX_LEN', 99);
98:
99: // 各種定数(END) ===============================================================

表示する地図は、Googleマップ、地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo APIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、Google、Yahoo!JAPAN、HeartRails Geo API、簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。


準備:その他の初期値
stationsearch.php
78: // マップの表示サイズ(単位:ピクセル)
79: define('MAP_WIDTH', 600);
80: define('MAP_HEIGHT', 400);
81: // マップID
82: define('MAPID', 'map_id');
83: // マップ中心マーカーのURL;表示したくなければNULLにする
84: define('CENTER_MARKER', 'https://maps.google.com/mapfiles/ms/micons/ltblu-pushpin.png');
85:
86: // 初期値
87: define('DEF_LATITUDE', 35.7); // 緯度
88: define('DEF_LONGITUDE', 139.7); // 経度
89: define('DEF_TYPE', 'roadmap'); // マップタイプ
90: define('DEF_ZOOM', 13); // ズーム
91: define('DEF_CATEGORY', 'address'); // カテゴリ
92:
93: // 検索キーの最小文字長
94: define('QUERY_MIN_LEN', 3);
95:
96: // 検索キーの最大文字長
97: define('QUERY_MAX_LEN', 99);
HeartRails Geo API 最寄駅検索
これらの API は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は XML で戻るという形である。
- エリア情報取得 API
- 都道府県情報取得 API
- 市区町村情報取得 API
- 町域情報取得 API
- 最寄駅情報取得 API
- 郵便番号による住所検索 API
- 緯度経度による住所検索 API
- キーワードによる住所検索 API
- 「エリア名」 「市区町村名」 「町域名」 の連結コンボボックス
- 「都道府県名」 「市区町村名」 「町域名」 の連結コンボボックス
- 「郵便番号」 による住所検索フォーム
URL |
---|
https://express.heartrails.com/api/xml |
フィールド名 | 要否 | 内 容 |
---|---|---|
method | 必須 | メソッド名:getStation(固定) |
x | 必須 | 最寄り駅を取得したい場所の経度(世界測地系)。 |
y | 必須 | 最寄り駅を取得したい場所の緯度(世界測地系)。 |
解説:HeartRails Geo APIの呼び出し
stationsearch.php
228: /**
229: * HeartRails Express のURLを取得する
230: * @param double $lat 緯度(世界測地系)
231: * @param double $lng 経度(世界測地系)
232: * @return string URL
233: */
234: function getURL_Heartrails($lat, $lng) {
235: $res = "http://express.heartrails.com/api/xml?method=getStations&x={$lng}&y={$lat}";
236:
237: return $res;
238: }
stationsearch.php
240: /**
241: * HeartRails Express API から必要な情報を配列に格納する
242: * @param double $latitude 緯度(世界測地系)
243: * @param double $longitude 経度(世界測地系)
244: * @return array(ヒットした施設数, メッセージ, APIのURL)
245: * @return int ヒット数
246: */
247: function getResults_Heartrails($latitude, $longitude, &$items) {
248: // 受信データの要素名
249: $tbl = array(
250: 'name', // 最寄駅名
251: 'prev', // 前の駅名 (始発駅の場合は null)
252: 'next', // 次の駅名 (終着駅の場合は null)
253: 'x', // 最寄駅の経度 (世界測地系)
254: 'y', // 最寄駅の緯度 (世界測地系)
255: 'distance', // 指定の場所から最寄駅までの距離 (精度は 10 m)
256: 'postal', // 最寄駅の郵便番号
257: 'prefecture', // 最寄駅の存在する都道府県名
258: 'line' // 最寄駅の存在する路線名
259: );
260:
261: $url = $this->getURL_Heartrails($latitude, $longitude); // リクエストURL
262: $cnt = 1;
263:
264: // PHP4用; DOM XML利用
265: if (! $this->isphp5over()) {
266: if (($dom = $this->read_xml($url)) == NULL) {
267: return array(FALSE, 'WebAPIのトラブルです.', FALSE);
268: }
269: $resultset = $dom->get_elements_by_tagname('response');
270: $results = $resultset[0]->get_elements_by_tagname('station');
271: // 検索結果取りだし
272: foreach ($results as $element) {
273: foreach ($tbl as $name) {
274: $node = $element->get_elements_by_tagname($name);
275: if ($node != NULL) {
276: $items[$cnt][$name] = (string)$node[0]->get_content();
277: }
278: }
279: $items[$cnt]['id'] = $this->num2alpha($cnt);
280: $items[$cnt]['title'] = $items[$cnt]['name'];
281: $items[$cnt]['longitude'] = $items[$cnt]['x'];
282: $items[$cnt]['latitude'] = $items[$cnt]['y'];
283: $items[$cnt]['description'] =<<< EOT
284: {$items[$cnt]['name']} ({$items[$cnt]['line']}){$items[$cnt]['distance']}
285: EOT;
286: $cnt++;
287: }
288:
289: // PHP5用; SimpleXML利用
290: } else {
291: $response = simplexml_load_file($url);
292: // レスポンス・チェック
293: if (isset($response->station) == FALSE) {
294: return array(FALSE, 'WebAPIのトラブルです.', FALSE);
295: }
296: // 検索結果取りだし
297: foreach ($response->station as $element) {
298: foreach ($tbl as $name) {
299: if (isset($element->$name)) {
300: $items[$cnt][$name] = (string)$element->$name;
301: }
302: }
303: $items[$cnt]['id'] = $this->num2alpha($cnt);
304: $items[$cnt]['title'] = $items[$cnt]['name'];
305: $items[$cnt]['longitude'] = $items[$cnt]['x'];
306: $items[$cnt]['latitude'] = $items[$cnt]['y'];
307: $items[$cnt]['description'] =<<< EOT
308: {$items[$cnt]['name']} ({$items[$cnt]['line']}){$items[$cnt]['distance']}
309: EOT;
310: $cnt++;
311: }
312: }
313:
314: return array($cnt - 1, '', $url);
315: }
Googleマップ描画
pahooGeoCode.php
888: /**
889: * Googleマップを描く
890: * @param string $id マップID
891: * @param float $latitude 中心座標:緯度(世界測地系)
892: * @param float $longitude 中心座標:経度(世界測地系)
893: * @param string $type マップタイプ:HYBRID/ROADMAP/SATELLITE/TERRAIN
894: * @param int $zoom 拡大率
895: * @param string $call イベント発生時にコールする関数(省略可)
896: * @param array $items 地点情報(省略可能)
897: * string title タイトル
898: * string description 情報ウィンドウに表示する内容(HTML文)
899: * float latitude 緯度
900: * float longitude 経度
901: * string icon アイコンURL
902: * @param string $call2 追加スクリプト(省略可)
903: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
904: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
905: * @param string $centerMarker マップ中心マーカーURL(省略可能)
906: * @param string $markerLevel マーカーの種類(0:Maker, 1:AdvancedMarker;省略可能))
907: * @return string Googleマップのコード
908: */
909: function drawGMap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL, $centerMarker=NULL, $markerLevel=1) {
910: $key = $this->GOOGLE_API_KEY_1;
911: $call = ($call != NULL) ? $call . '()' : '';
912: if (! is_array($offset)) {
913: $offset = array(0, 0);
914: }
915:
916: // マップ中心マーカー
917: $jsCenterMarker = '';
918: if ($centerMarker !== NULL) {
919: $size = getimagesize($centerMarker);
920: $jsCenterMarker .=<<< EOT
921: const markerCenter = new google.maps.marker.AdvancedMarkerElement({
922: position: { lat: {$latitude}, lng: {$longitude} },
923: map: map,
924: content: (() => {
925: const icon = document.createElement('img');
926: icon.src = '{$centerMarker}';
927: icon.style.width = '{$size[0]}px';
928: icon.style.height = '{$size[1]}px';
929: icon.style.transform = 'translate(0%, 0%)';
930: return icon;
931: })(),
932: zIndex: 10
933: });
934:
935: EOT;
936: }
937:
938: $mapId = $this->GOOGLE_MAP_ID;
939: $code =<<< EOT
940: <script src="https://maps.googleapis.com/maps/api/js?key={$key}&loading=async&libraries=marker&callback=initMap®ion=JP" async defer loading="async"></script>
941: <script>
942: function initMap() {
943: let map = new google.maps.Map(document.getElementById('{$id}'), {
944: center: { lat: {$latitude}, lng: {$longitude} },
945: mapId: '{$mapId}',
946: zoom: {$zoom},
947: mapTypeId: google.maps.MapTypeId.{$type},
948: mapTypeControl: true,
949: scaleControl: true
950: });
951:
952: map.addListener('dragend', getPointData);
953: map.addListener('zoom_changed', getPointData);
954: map.addListener('maptypeid_changed', getPointData);
955:
956: // イベント発生時の地図情報を取得・格納
957: function getPointData() {
958: const point = map.getCenter();
959: // 経度
960: if (document.getElementById("longitude") != null) {
961: document.getElementById("longitude").value = point.lng();
962: }
963: // 緯度
964: if (document.getElementById("latitude") != null) {
965: document.getElementById("latitude").value = point.lat();
966: }
967: // ズーム
968: if (document.getElementById("zoom") != null) {
969: document.getElementById("zoom").value = map.getZoom();
970: }
971: // 地図タイプ
972: if (document.getElementById("type") != null) {
973: const type_g = map.getMapTypeId();
974: const types = {"roadmap":"地図", "satellite":"航空写真", "hybrid":"ハイブリッド", "terrain":"地形図" };
975: for (key in types) {
976: if (key == type_g) {
977: document.getElementById("type").value = key;
978: break;
979: }
980: }
981: }
982: {$call}
983: }
984: {$jsCenterMarker}
985:
986: EOT;
987: // 地点情報
988: if ($items != NULL) {
989: // AdvancedMarker
990: if ($markerLevel > 0) {
991: foreach ($items as $i=>$item) {
992: if ($i > 999) break; // 最大999箇所まで
993: $mark = (string)sprintf('%03d', $i);
994: // アイコン
995: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
996: $icon = isset($item['icon']) ? $item['icon'] :
997: "https://www.google.com/mapfiles/marker{$mark2}.png";
998: list($icon_width, $icon_height) = getimagesize($icon);
999: if (isset($item['label']) && ($item['label'] != '')) {
1000: $size1 = $item['label_size'] * mb_strlen($item['label']);
1001: $size2 = (int)($size1 / 2);
1002: $ss =<<< EOT
1003: const icon = document.createElement('div');
1004: icon.innerHTML = `
1005: <div style="position: relative; text-align: center;">
1006: <img src="https://www.pahoo.org/common/space.gif" style="width:{$size1}px; height:{$size1}px;">
1007: <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']};">
1008: {$item['label']}
1009: </div>
1010: </div>
1011: `;
1012:
1013: EOT;
1014: } else {
1015: $ss =<<< EOT
1016: const icon = document.createElement('img');
1017: icon.src = '{$icon}';
1018: icon.style.width = '{$icon_width}px';
1019: icon.style.height = '{$icon_height}px';
1020: icon.style.transform = 'translate(0%, 0%)';
1021:
1022: EOT;
1023: }
1024: $code .=<<< EOT
1025: const marker_{$mark} = new google.maps.marker.AdvancedMarkerElement({
1026: position: { lat: {$item['latitude']}, lng: {$item['longitude']} },
1027: map: map,
1028: content: (() => {
1029: {$ss}
1030: return icon;
1031: })(),
1032: title: '{$item['title']}',
1033: zIndex: 100,
1034: });
1035:
1036: EOT;
1037: if (isset($item['description'])) {
1038: $code .=<<< EOT
1039: const infowindow_{$mark} = new google.maps.InfoWindow({
1040: content: '{$item['description']}',
1041: maxWidth: {$max_width},
1042: pixelOffset: new google.maps.Size({$offset[0]}, {$offset[1]})
1043: });
1044: marker_{$mark}.addListener('gmp-click', function() {
1045: infowindow_{$mark}.open(map, marker_{$mark});
1046: });
1047:
1048: EOT;
1049: }
1050: }
1051:
1052: // Marker
1053: } else {
1054: foreach ($items as $i=>$item) {
1055: if ($i > 999) break; // 最大999箇所まで
1056: $mark = (string)sprintf('%03d', $i);
1057: // アイコン
1058: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
1059: $icon = isset($item['icon']) ? $item['icon'] :
1060: "https://www.google.com/mapfiles/marker{$mark2}.png";
1061: list($icon_width, $icon_height) = getimagesize($icon);
1062: if (isset($item['label']) && ($item['label'] != '')) {
1063: $ss =<<< EOT
1064: icon: {
1065: url: 'https://www.pahoo.org/common/space.gif'
1066: },
1067: label: {
1068: text: '{$item['label']}',
1069: color: '{$item['label_color']}',
1070: fontSize: '{$item['label_size']}px',
1071: fontWeight: '{$item['label_weight']}'
1072: }
1073:
1074: EOT;
1075: } else {
1076: $ss =<<< EOT
1077: icon: {
1078: url: '{$icon}',
1079: size: new google.maps.Size({$icon_width}, {$icon_height}),
1080: origin: new google.maps.Point(0, 0),
1081: anchor: new google.maps.Point({$icon_width} / 2, {$icon_height})
1082: }
1083:
1084: EOT;
1085: }
1086: $code .=<<< EOT
1087: const marker_{$mark} = new google.maps.Marker({
1088: position: new google.maps.LatLng({$item['latitude']}, {$item['longitude']}),
1089: map: map,
1090: {$ss},
1091: title: '{$item['title']}',
1092: zIndex: 100
1093: });
1094:
1095: EOT;
1096: if (isset($item['description'])) {
1097: $code .=<<< EOT
1098: const infowindow_{$mark} = new google.maps.InfoWindow({
1099: content: '{$item['description']}',
1100: maxWidth: {$max_width},
1101: pixelOffset: new google.maps.Size({$offset[0]}, {$offset[1]})
1102: });
1103: marker_{$mark}.addListener('click', function() {
1104: infowindow_{$mark}.open(map, marker_{$mark});
1105: });
1106:
1107: EOT;
1108: }
1109: }
1110: }
1111: }
1112:
1113: // 追加関数
1114: if ($call2 != NULL) {
1115: $code .=<<< EOT
1116: {$call2}
1117:
1118: EOT;
1119: }
1120: $code .=<<< EOT
1121: }
1122:
1123: </script>
1124:
1125: EOT;
1126:
1127: return $code;
1128: }
まずソースとして、"https://maps.googleapis.com/maps/api/js" を読み込む。このとき、下表に示すパラメータを渡してやる必要がある。
パラメータ | 値 | 意味 |
---|---|---|
key | Google Cloud Platform APIキー | |
loading | async | 非同期呼び出しを行う。 |
libraries | marker | 高度なマーカーを利用する。 |
callback | initMap | コールバック関数名 |
region | JP | 日本の地図を表示する。 |

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描画

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

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

Leaflet のレイヤを切り替えることで、以下の地図が切り替わるようにしてある。
- 地理院地図:標準地図
- 地理院地図:淡色地図
- 地理院地図:白地図
- 地理院地図:写真
- OpenStreetMap

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

引数 $items を渡してやれば、地図上にマーキングする。
$items は2次元配列で、1次元目は地点番号(1以上)、2次元目は情報の種類である。たとえば $items[3]['description'] には、地点番号3の情報ウィンドウに表示する内容HTMLを代入する。
$items には、マッピングするアイコンURLも指定可能である。指定しない場合は、Googleマップの標準的なアルファベットアイコンによってマーキングする。
動的マップ描画
pahooGeoCode.php
2507: /**
2508: * 地図サービスを利用してJavaScriptマップを描く
2509: * @param string $id マップID
2510: * @param float $latitude 中心座標:緯度(世界測地系)
2511: * @param float $longitude 中心座標:経度(世界測地系)
2512: * @param string $type マップタイプ
2513: * Googleの場合 HYBRID/ROADMAP/SATELLITE/TERRAIN
2514: * Yahoo!JAPANの場合 NORMAL/PHOTO/B1/OSM
2515: * @param int $zoom 拡大率
2516: * @param string $call イベント発生時にコールする関数(省略可)
2517: * @param array $items 地点情報(省略可能)
2518: * string title タイトル(Yahoo!では無効)
2519: * string description 情報ウィンドウに表示する内容(HTML文)
2520: * float latitude 緯度
2521: * float longitude 経度
2522: * string icon アイコンURL
2523: * string label アイコン・ラベル(省略可能)
2524: * string label_size アイコン・ラベルのサイズ(省略可能)
2525: * string label_weight アイコン・ラベルの太さ(省略可能)
2526: * string label_color アイコン・ラベルの色(省略可能)
2527: * @param string $api 0:Google Maps JavaScript(省略時)
2528: * 2:地理院地図・OSM(Leaflet使用)
2529: * @param string $call2 追加スクリプト(省略可)
2530: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
2531: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
2532: * @param array $overlays Leaflet用オーバーレイ
2533: * @param string $centerMarker マップ中心マーカーURL(省略可能)
2534: * @param string $markerLevel GoogleMaps用マーカーの種類(0:Maker, 1:AdvancedMarker;省略可能)
2535: * @return string JavaScriptマップのコード
2536: */
2537: function drawJSmap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $api=0, $call2=NULL, $max_width=200, $offset=NULL, $overlays=NULL, $centerMarker=NULL, $markerLevel=1) {
2538: // マップタイプの読み替え
2539: static $tbl1 = array('HYBRID'=>'PHOTO', 'ROADMAP'=>'NORMAL', 'SATELLITE'=>'PHOTO', 'TERRAIN'=>'PHOTO');
2540: static $tbl2 = array('NORMAL'=>'ROADMAP', 'PHOTO'=>'SATELLITE', 'B1'=>'ROADMAP', 'OSM'=>'ROADMAP');
2541: static $tbl3 = array('HYBRID'=>'GSISTD', 'ROADMAP'=>'OSM', 'SATELLITE'=>'GSIPHOTO', 'TERRAIN'=>'GSIPHOTO');
2542: $type = strtoupper($type);
2543:
2544: switch ($api) {
2545: // Google Maps JavaScript
2546: case 0;
2547: $type = isset($tbl2[$type]) ? $tbl2[$type] : $type;
2548: $js = $this->drawGMap($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset, $centerMarker, $markerLevel);
2549: break;
2550: // 地理院地図・OSM(Leaflet使用)
2551: case 2:
2552: $type = isset($tbl3[$type]) ? $tbl3[$type] : $type;
2553: $js = $this->drawLeaflet($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset, $overlays, $centerMarker);
2554: break;
2555: }
2556:
2557: return $js;
2558: }
マップタイプについては、相互に読み替えができるようにしてある。
その他の WebAPI
pahooGeoCode::getAddress3 が呼び出すWebAPIについては、「PHPで緯度・経度から住所を求める」を参照のこと。
活用例
参考サイト
- HeartRails Geo API
- Google Maps JavaScript API
- 【重要】 YOLP Web APIにおける一部API・SDKの提供終了お知らせ:Yahoo! Open Local Platform (YOLP)
- Leaflet
- 地理院タイル:国土地理院
- OpenStreetMap
- PHPで住所・ランドマークから緯度・経度を求める:ぱふぅ家のホームページ
- PHPで緯度・経度から住所を求める:ぱふぅ家のホームページ
- 地図・住所から「最寄り駅」をグーグルマップで探す:みんなの知識 ちょっと便利帳
これと、Googleや地理院地図、オープンストリートマップの地図サービスをクラウド連携することで、住所やランドマークから最寄り駅を求めるPHPプログラムを作ってみることにする。
なお、Yahoo! JavaScriptマップは、2020年(令和2年)10月31日をもってサービスを終了しており利用できない。
(2025年8月24日).pahooEnv 導入
(2025年7月20日)マップ中央にマーカーを置くことができるようにした.
(2025年6月14日)GoogleMaps JavaScript APIの変更に対応した.