目次
サンプル・プログラムの実行例
サンプル・プログラム
| stationsearch.php | サンプル・プログラム本体。 |
| pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
| pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 4.8.0 | 2026/01/11 | PHP8.5対応:double→float |
| 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を追加 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 6.9.1 | 2025/11/25 | PHP8.5対応:double→float |
| 6.9.0 | 2025/09/21 | jsPolygon, jsPolygon_Gmap, jsPolygon_Leaflet, loadGeoJSON, getPrefBorderList を追加 |
| 6.8.0 | 2025/08/10 | アクセスキーなどを ".pahooEnd" に分離 |
| 6.7.1 | 2025/07/26 | jsLine_Gmap() - bug-fix |
| 6.7.0 | 2025/07/20 | drawJSmap,drawGMap -- 引数 $markerLevel 追加 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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
41: class pahooGeoCode {
42: public $items; // 検索結果格納用
43: public $error; // エラー・フラグ
44: public $errmsg; // エラー・メッセージ
45: public $hits; // 検索ヒット件数
46: public $webapi; // 直前に呼び出したWebAPI URL
47:
48: // 都道府県境界線データ
49: // SimpleMaps.com is a product of Pareto Software, LLC. © 2010-2025
50: // https://simplemaps.com/gis/country/jp
51: // ※各自の環境に合わせて設定すること
52: public $GeoJsonJP = __DIR__ . '/jp.json';
53:
54: // -- 以下のデータは .env ファイルに記述可能
55: // Google Cloud Platform APIキー
56: // https://cloud.google.com/maps-platform/
57: // ※Google Maps APIを利用しないのなら登録不要
58: public $GOOGLE_API_KEY_1 = ''; // HTTPリファラ用
59: public $GOOGLE_API_KEY_2 = ''; // IP制限用
60: public $GOOGLE_MAP_ID = ''; // GoogleMaps ID
61:
62: // Yahoo! JAPAN Webサービス アプリケーションID
63: // https://e.developer.yahoo.co.jp/register
64: // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
65: public $YAHOO_APPLICATION_ID = '';
66:
67: // OSM Nominatim Search API利用時に知らせるメールアドレス
68: // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
69: // ※OSM Nominatim Search APIを利用しないのなら登録不要
70: public $NOMINATIM_EMAIL = '';
71:
72: // IP2Location.io APIキー
73: // https://www.ip2location.io/
74: // ※IP2Location.ioを利用しないのなら登録不要
75: 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 float $lat 緯度(世界測地系)
231: * @param float $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 float $latitude 緯度(世界測地系)
243: * @param float $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
895: /**
896: * Googleマップを描く
897: * @param string $id マップID
898: * @param float $latitude 中心座標:緯度(世界測地系)
899: * @param float $longitude 中心座標:経度(世界測地系)
900: * @param string $type マップタイプ:HYBRID/ROADMAP/SATELLITE/TERRAIN
901: * @param int $zoom 拡大率
902: * @param string $call イベント発生時にコールする関数(省略可)
903: * @param array $items 地点情報(省略可能)
904: * string title タイトル
905: * string description 情報ウィンドウに表示する内容(HTML文)
906: * float latitude 緯度
907: * float longitude 経度
908: * string icon アイコンURL
909: * @param string $call2 追加スクリプト(省略可)
910: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
911: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
912: * @param string $centerMarker マップ中心マーカーURL(省略可能)
913: * @param string $markerLevel マーカーの種類(0:Maker, 1:AdvancedMarker;省略可能))
914: * @return string Googleマップのコード
915: */
916: function drawGMap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL, $centerMarker=NULL, $markerLevel=1) {
917: $key = $this->GOOGLE_API_KEY_1;
918: $call = ($call != NULL) ? $call . '()' : '';
919: if (! is_array($offset)) {
920: $offset = array(0, 0);
921: }
922:
923: // マップ中心マーカー
924: $jsCenterMarker = '';
925: if ($centerMarker !== NULL) {
926: $size = getimagesize($centerMarker);
927: $jsCenterMarker .=<<< EOT
928: const markerCenter = new google.maps.marker.AdvancedMarkerElement({
929: position: { lat: {$latitude}, lng: {$longitude} },
930: map: map,
931: content: (() => {
932: const icon = document.createElement('img');
933: icon.src = '{$centerMarker}';
934: icon.style.width = '{$size[0]}px';
935: icon.style.height = '{$size[1]}px';
936: icon.style.transform = 'translate(0%, 0%)';
937: return icon;
938: })(),
939: zIndex: 10
940: });
941:
942: EOT;
943: }
944:
945: $mapId = $this->GOOGLE_MAP_ID;
946: $code =<<< EOT
947: <script src="https://maps.googleapis.com/maps/api/js?key={$key}&loading=async&libraries=marker&callback=initMap®ion=JP" async defer loading="async"></script>
948: <script>
949: function initMap() {
950: let map = new google.maps.Map(document.getElementById('{$id}'), {
951: center: { lat: {$latitude}, lng: {$longitude} },
952: mapId: '{$mapId}',
953: zoom: {$zoom},
954: mapTypeId: google.maps.MapTypeId.{$type},
955: mapTypeControl: true,
956: scaleControl: true
957: });
958:
959: map.addListener('dragend', getPointData);
960: map.addListener('zoom_changed', getPointData);
961: map.addListener('maptypeid_changed', getPointData);
962:
963: // イベント発生時の地図情報を取得・格納
964: function getPointData() {
965: const point = map.getCenter();
966: // 経度
967: if (document.getElementById("longitude") != null) {
968: document.getElementById("longitude").value = point.lng();
969: }
970: // 緯度
971: if (document.getElementById("latitude") != null) {
972: document.getElementById("latitude").value = point.lat();
973: }
974: // ズーム
975: if (document.getElementById("zoom") != null) {
976: document.getElementById("zoom").value = map.getZoom();
977: }
978: // 地図タイプ
979: if (document.getElementById("type") != null) {
980: const type_g = map.getMapTypeId();
981: const types = {"roadmap":"地図", "satellite":"航空写真", "hybrid":"ハイブリッド", "terrain":"地形図" };
982: for (key in types) {
983: if (key == type_g) {
984: document.getElementById("type").value = key;
985: break;
986: }
987: }
988: }
989: {$call}
990: }
991: {$jsCenterMarker}
992:
993: EOT;
994: // 地点情報
995: if ($items != NULL) {
996: // AdvancedMarker
997: if ($markerLevel > 0) {
998: foreach ($items as $i=>$item) {
999: if ($i > 999) break; // 最大999箇所まで
1000: $mark = (string)sprintf('%03d', $i);
1001: // アイコン
1002: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
1003: $icon = isset($item['icon']) ? $item['icon'] :
1004: "https://www.google.com/mapfiles/marker{$mark2}.png";
1005: list($icon_width, $icon_height) = getimagesize($icon);
1006: if (isset($item['label']) && ($item['label'] != '')) {
1007: $size1 = $item['label_size'] * mb_strlen($item['label']);
1008: $size2 = (int)($size1 / 2);
1009: $ss =<<< EOT
1010: const icon = document.createElement('div');
1011: icon.innerHTML = `
1012: <div style="position: relative; text-align: center;">
1013: <img src="https://www.pahoo.org/common/space.gif" style="width:{$size1}px; height:{$size1}px;">
1014: <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']};">
1015: {$item['label']}
1016: </div>
1017: </div>
1018: `;
1019:
1020: EOT;
1021: } else {
1022: $ss =<<< EOT
1023: const icon = document.createElement('img');
1024: icon.src = '{$icon}';
1025: icon.style.width = '{$icon_width}px';
1026: icon.style.height = '{$icon_height}px';
1027: icon.style.transform = 'translate(0%, 0%)';
1028:
1029: EOT;
1030: }
1031: $code .=<<< EOT
1032: const marker_{$mark} = new google.maps.marker.AdvancedMarkerElement({
1033: position: { lat: {$item['latitude']}, lng: {$item['longitude']} },
1034: map: map,
1035: content: (() => {
1036: {$ss}
1037: return icon;
1038: })(),
1039: title: '{$item['title']}',
1040: zIndex: 100,
1041: });
1042:
1043: EOT;
1044: if (isset($item['description'])) {
1045: $code .=<<< EOT
1046: const infowindow_{$mark} = new google.maps.InfoWindow({
1047: content: '{$item['description']}',
1048: maxWidth: {$max_width},
1049: pixelOffset: new google.maps.Size({$offset[0]}, {$offset[1]})
1050: });
1051: marker_{$mark}.addListener('gmp-click', function() {
1052: infowindow_{$mark}.open(map, marker_{$mark});
1053: });
1054:
1055: EOT;
1056: }
1057: }
1058:
1059: // Marker
1060: } else {
1061: foreach ($items as $i=>$item) {
1062: if ($i > 999) break; // 最大999箇所まで
1063: $mark = (string)sprintf('%03d', $i);
1064: // アイコン
1065: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
1066: $icon = isset($item['icon']) ? $item['icon'] :
1067: "https://www.google.com/mapfiles/marker{$mark2}.png";
1068: list($icon_width, $icon_height) = getimagesize($icon);
1069: if (isset($item['label']) && ($item['label'] != '')) {
1070: $ss =<<< EOT
1071: icon: {
1072: url: 'https://www.pahoo.org/common/space.gif'
1073: },
1074: label: {
1075: text: '{$item['label']}',
1076: color: '{$item['label_color']}',
1077: fontSize: '{$item['label_size']}px',
1078: fontWeight: '{$item['label_weight']}'
1079: }
1080:
1081: EOT;
1082: } else {
1083: $ss =<<< EOT
1084: icon: {
1085: url: '{$icon}',
1086: size: new google.maps.Size({$icon_width}, {$icon_height}),
1087: origin: new google.maps.Point(0, 0),
1088: anchor: new google.maps.Point({$icon_width} / 2, {$icon_height})
1089: }
1090:
1091: EOT;
1092: }
1093: $code .=<<< EOT
1094: const marker_{$mark} = new google.maps.Marker({
1095: position: new google.maps.LatLng({$item['latitude']}, {$item['longitude']}),
1096: map: map,
1097: {$ss},
1098: title: '{$item['title']}',
1099: zIndex: 100
1100: });
1101:
1102: EOT;
1103: if (isset($item['description'])) {
1104: $code .=<<< EOT
1105: const infowindow_{$mark} = new google.maps.InfoWindow({
1106: content: '{$item['description']}',
1107: maxWidth: {$max_width},
1108: pixelOffset: new google.maps.Size({$offset[0]}, {$offset[1]})
1109: });
1110: marker_{$mark}.addListener('click', function() {
1111: infowindow_{$mark}.open(map, marker_{$mark});
1112: });
1113:
1114: EOT;
1115: }
1116: }
1117: }
1118: }
1119:
1120: // 追加関数
1121: if ($call2 != NULL) {
1122: $code .=<<< EOT
1123: {$call2}
1124:
1125: EOT;
1126: }
1127: $code .=<<< EOT
1128: }
1129:
1130: </script>
1131:
1132: EOT;
1133:
1134: return $code;
1135: }
まずソースとして、"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
1908: /**
1909: * Leafletによるマップ描画
1910: * @param string $id マップID
1911: * @param float $latitude 中心座標:緯度(世界測地系)
1912: * @param float $longitude 中心座標:経度(世界測地系)
1913: * @param string $type マップタイプ:GSISTD/GSIPALE/GSIBLANK/GSIPHOTO/OSM
1914: * @param int $zoom 拡大率
1915: * @param string $call イベント発生時にコールする関数(省略可)
1916: * @param array $items 地点情報(省略可能)
1917: * string description 情報ウィンドウに表示する内容(HTML文)
1918: * float latitude 緯度
1919: * float longitude 経度
1920: * string icon アイコンURL
1921: * @param string $call2 追加スクリプト(省略可)
1922: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
1923: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:NULL)
1924: * @param array $overlays オーバーレイ:GSIELEV/GSIFAULT/GSIFLOOD
1925: * @param string $centerMarker マップ中心マーカーURL(省略可能)
1926: * @return string Leafletマップのコード
1927: */
1928: function drawLeaflet($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL, $overlays=NULL, $centerMarker=NULL) {
1929:
1930: if (! is_array($offset)) {
1931: $offset = array(0, 0);
1932: }
1933: // デフォルト・オーバーレイ
1934: $addoverlay = '';
1935: if ($overlays != NULL) {
1936: foreach ($overlays as $overlay) {
1937: $addoverlay .=<<< EOT
1938: {$overlay}.addTo(map);
1939:
1940: EOT;
1941: }
1942: }
1943:
1944: // マップ中心マーカー
1945: $jsCenterMarker = '';
1946: if ($centerMarker !== NULL) {
1947: $size = getimagesize($centerMarker);
1948: $ofstx = (int)($size[0] / 2);
1949: $ofsty = (int)($size[1] / 2);
1950: $jsCenterMarker .=<<< EOT
1951: let iconCenter = new L.icon({
1952: iconUrl: '{$centerMarker}',
1953: iconAnchor: [$ofstx, $ofsty]
1954: });
1955: let markerCenter = new L.Marker([{$latitude}, {$longitude}], {icon: iconCenter}).addTo(map);
1956:
1957: EOT;
1958: }
1959:
1960: // マップ描画コード
1961: $code =<<< EOT
1962: <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
1963: <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
1964: <script>
1965: window.onload = function() {
1966: let map = L.map('{$id}',{zoomControl:false});
1967: map.setView([{$latitude}, {$longitude}], {$zoom});
1968: L.control.scale({
1969: maxWidth: 200,
1970: position: 'bottomright',
1971: imperial: false
1972: }).addTo(map);
1973: L.control.zoom({position:'topleft'}).addTo(map);
1974:
1975: // 地理院地図:標準地図
1976: let GSISTD = new L.tileLayer(
1977: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
1978: {
1979: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1980: minZoom: 0,
1981: maxZoom: 18,
1982: name: 'GSISTD'
1983: });
1984: // 地理院地図:淡色地図
1985: let GSIPALE = new L.tileLayer(
1986: 'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
1987: {
1988: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1989: minZoom: 2,
1990: maxZoom: 18,
1991: name: 'GSIPALE'
1992: });
1993: // 地理院地図:白地図
1994: let GSIBLANK = new L.tileLayer(
1995: 'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
1996: {
1997: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1998: minZoom: 5,
1999: maxZoom: 14,
2000: name: 'GSIBLANK'
2001: });
2002: // 地理院地図:写真
2003: let GSIPHOTO = new L.tileLayer(
2004: 'https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg',
2005: {
2006: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
2007: minZoom: 2,
2008: maxZoom: 18,
2009: name: 'GSIPHOTO'
2010: });
2011: // OpenStreetMap
2012: let OSM = new L.tileLayer(
2013: 'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
2014: {
2015: attribution: "© <a href='https://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
2016: minZoom: 0,
2017: maxZoom: 18,
2018: name: 'OSM'
2019: });
2020:
2021: // baseMapsオブジェクトにタイル設定
2022: let baseMaps = {
2023: "地理院地図" : GSISTD,
2024: "淡色地図" : GSIPALE,
2025: "白地図" : GSIBLANK,
2026: "写真地図" : GSIPHOTO,
2027: "オープンストリートマップ" : OSM
2028: };
2029:
2030: // 地理院地図:色別標高図(オーバーレイ)
2031: let GSIELEV = new L.tileLayer(
2032: 'https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png',
2033: {
2034: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
2035: opacity: 0.6,
2036: minZoom: 5,
2037: maxZoom: 15,
2038: name: 'GSIELEV'
2039: });
2040: // 地理院地図:活断層図(オーバーレイ)
2041: let GSIFAULT = new L.tileLayer(
2042: 'https://cyberjapandata.gsi.go.jp/xyz/afm/{z}/{x}/{y}.png',
2043: {
2044: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
2045: opacity: 0.6,
2046: minZoom: 11,
2047: maxZoom: 16,
2048: name: 'GSIFAULT'
2049: });
2050: // 地理院地図:治水地形分類図 更新版(オーバーレイ)
2051: let GSIFLOOD = new L.tileLayer(
2052: 'https://cyberjapandata.gsi.go.jp/xyz/lcmfc2/{z}/{x}/{y}.png',
2053: {
2054: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
2055: opacity: 0.6,
2056: minZoom: 11,
2057: maxZoom: 16,
2058: name: 'GSIFLOOD'
2059: });
2060:
2061: // baseMapsオブジェクトにオーバーレイ設定
2062: let overlayMaps = {
2063: "色別標高図" : GSIELEV,
2064: "活断層図" : GSIFAULT,
2065: "治水地形分類図" : GSIFLOOD,
2066: };
2067:
2068: // layersコントロールにbaseMapsオブジェクトを設定して地図に追加
2069: L.control.layers(baseMaps, overlayMaps).addTo(map);
2070: {$type}.addTo(map);
2071: {$addoverlay}
2072:
2073: // イベント追加
2074: map.on('viewreset', getPointData);
2075: map.on('zoomend', getPointData);
2076: map.on('baselayerchange', getPointData);
2077: map.on('overlayadd', getPointData);
2078: map.on('overlayremove', getPointData);
2079: map.on('move', getPointData);
2080:
2081: // イベント発生時の地図情報を取得・格納
2082: function getPointData() {
2083: let pos = map.getCenter();
2084: // 経度
2085: if (document.getElementById('longitude') != null) {
2086: document.getElementById('longitude').value = pos.lng;
2087: }
2088: // 緯度
2089: if (document.getElementById('latitude') != null) {
2090: document.getElementById('latitude').value = pos.lat;
2091: }
2092: // ズーム
2093: if (document.getElementById('zoom') != null) {
2094: document.getElementById('zoom').value = map.getZoom();
2095: }
2096: // タイプ
2097: if (document.getElementById('type') != null) {
2098: for (let k in baseMaps) {
2099: if (map.hasLayer(baseMaps[k])) {
2100: document.getElementById('type').value = baseMaps[k].options.name;
2101: }
2102: }
2103: }
2104: // オーバーレイ
2105: if (document.getElementById('overlays') != null) {
2106: let str = '';
2107: let cnt = 0;
2108: for (let k in overlayMaps) {
2109: if (map.hasLayer(overlayMaps[k])) {
2110: if (cnt > 0) str += ',';
2111: str += overlayMaps[k].options.name;
2112: cnt++;
2113: }
2114: }
2115: document.getElementById('overlays').value = str;
2116: }
2117: {$call}
2118: }
2119: {$jsCenterMarker}
2120:
2121: EOT;
2122: // 地点情報
2123: if ($items != NULL) {
2124: foreach ($items as $i=>$item) {
2125: if ($i > 999) break; // 最大999箇所まで
2126: $mark = (string)sprintf('%03d', $i);
2127: // アイコン
2128: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
2129: $icon = isset($item['icon']) ? $item['icon'] :
2130: "https://www.google.com/mapfiles/marker{$mark2}.png";
2131: list($icon_width, $icon_height) = getimagesize($icon);
2132: $offx = round($icon_width / 2);
2133: $offy = $icon_height;
2134: $info = isset($item['description']) ? "marker_{$mark}.bindPopup('{$item['description']}', {maxWidth: {$max_width}, offset: [{$offset[0]}, {$offset[1]}] });" : '';
2135:
2136: // アイコン・ラベル
2137: if (isset($item['label']) && ($item['label'] != '')) {
2138: $ss =<<< EOT
2139: let icon_{$mark} = new L.divIcon({
2140: html: '<span style="color:{$item['label_color']}; font-size:{$item['label_size']}px; font-weight:{$item['label_weight']}; white-space:nowrap;">{$item['label']}</span>',
2141: iconSize: [0, 0],
2142: iconAnchor: [{$item['label_size']}, {$item['label_size']}],
2143: });
2144:
2145: EOT;
2146: // 通常アイコン
2147: } else {
2148: $ss =<<< EOT
2149: let icon_{$mark} = new L.icon({
2150: iconUrl: '{$icon}',
2151: iconAnchor: [{$offx}, {$offy}]
2152: });
2153: EOT;
2154: }
2155: $code .=<<< EOT
2156: {$ss}
2157: let marker_{$mark} = new L.Marker([{$item['latitude']}, {$item['longitude']}], {icon: icon_{$mark}}).addTo(map);
2158: {$info}
2159:
2160: EOT;
2161: }
2162: }
2163: // 追加関数
2164: if ($call2 != NULL) {
2165: $code .=<<< EOT
2166: {$call2}
2167:
2168: EOT;
2169: }
2170: $code .=<<< EOT
2171: }
2172: </script>
2173:
2174: EOT;
2175:
2176: return $code;
2177: }
まずソースとして、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
2585: /**
2586: * 地図サービスを利用してJavaScriptマップを描く
2587: * @param string $id マップID
2588: * @param float $latitude 中心座標:緯度(世界測地系)
2589: * @param float $longitude 中心座標:経度(世界測地系)
2590: * @param string $type マップタイプ
2591: * Googleの場合 HYBRID/ROADMAP/SATELLITE/TERRAIN
2592: * Yahoo!JAPANの場合 NORMAL/PHOTO/B1/OSM
2593: * @param int $zoom 拡大率
2594: * @param string $call イベント発生時にコールする関数(省略可)
2595: * @param array $items 地点情報(省略可能)
2596: * string title タイトル(Yahoo!では無効)
2597: * string description 情報ウィンドウに表示する内容(HTML文)
2598: * float latitude 緯度
2599: * float longitude 経度
2600: * string icon アイコンURL
2601: * string label アイコン・ラベル(省略可能)
2602: * string label_size アイコン・ラベルのサイズ(省略可能)
2603: * string label_weight アイコン・ラベルの太さ(省略可能)
2604: * string label_color アイコン・ラベルの色(省略可能)
2605: * @param string $api 0:Google Maps JavaScript(省略時)
2606: * 2:地理院地図・OSM(Leaflet使用)
2607: * @param string $call2 追加スクリプト(省略可)
2608: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
2609: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
2610: * @param array $overlays Leaflet用オーバーレイ
2611: * @param string $centerMarker マップ中心マーカーURL(省略可能)
2612: * @param string $markerLevel GoogleMaps用マーカーの種類(0:Maker, 1:AdvancedMarker;省略可能)
2613: * @return string JavaScriptマップのコード
2614: */
2615: 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) {
2616: // マップタイプの読み替え
2617: static $tbl1 = array('HYBRID'=>'PHOTO', 'ROADMAP'=>'NORMAL', 'SATELLITE'=>'PHOTO', 'TERRAIN'=>'PHOTO');
2618: static $tbl2 = array('NORMAL'=>'ROADMAP', 'PHOTO'=>'SATELLITE', 'B1'=>'ROADMAP', 'OSM'=>'ROADMAP');
2619: static $tbl3 = array('HYBRID'=>'GSISTD', 'ROADMAP'=>'OSM', 'SATELLITE'=>'GSIPHOTO', 'TERRAIN'=>'GSIPHOTO');
2620: $type = strtoupper($type);
2621:
2622: switch ($api) {
2623: // Google Maps JavaScript
2624: case 0:
2625: $type = isset($tbl2[$type]) ? $tbl2[$type] : $type;
2626: $js = $this->drawGMap($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset, $centerMarker, $markerLevel);
2627: break;
2628: // 地理院地図・OSM(Leaflet使用)
2629: case 2:
2630: $type = isset($tbl3[$type]) ? $tbl3[$type] : $type;
2631: $js = $this->drawLeaflet($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset, $overlays, $centerMarker);
2632: break;
2633: }
2634:
2635: return $js;
2636: }
マップタイプについては、相互に読み替えができるようにしてある。
その他の 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日をもってサービスを終了しており利用できない。
(2026年1月11日) PHP8.5対応:double→float
(2025年8月24日).pahooEnv 導入
(2025年7月20日)マップ中央にマーカーを置くことができるようにした.
(2025年6月14日)GoogleMaps JavaScript APIの変更に対応した.