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

サンプル・プログラム
stationsearch.php | サンプル・プログラム本体。 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
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も利用できるようにした |
バージョン | 更新日 | 内容 |
---|---|---|
6.5.0 | 2025/06/14 | GoogleMaps JavaScript APIの変更に対応 |
6.4.0 | 2025/03/01 | makeYOLP_GeoSelectCategory()--引数$flagWorld追加 |
6.3.3 | 2024/09/14 | $this->NOMINATIM_EMAIL 追加 |
6.3.2 | 2024/02/14 | getStaticMap() -- bug-fix |
6.3.1 | 2023/07/09 | bug-fix |
バージョン | 更新日 | 内容 |
---|---|---|
1.8.1 | 2025/03/15 | validRegexPattern() -- debug |
1.8.0 | 2024/11/12 | validRegexPattern() 追加 |
1.7.0 | 2024/10/09 | validURL() validEmail() 追加 |
1.6.0 | 2024/10/07 | isButton() -- buttonタグに対応 |
1.5.0 | 2024/01/28 | exitIfExceedVersion() 追加 |
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' と指定してやればよい。
サンプル・プログラムの流れ

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

地図や住所検索として Google を利用するのであれば、Google Cloud Platform APIキー とマップID が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば、Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、それぞれ参照されたい。
準備:地図サービスの選択
stationsearch.php
56: //地図描画サービスの選択
57: // 0:Google
58: // 2:地理院地図・OSM
59: define('MAPSERVICE', 2);
60:
61: //住所検索サービスの選択
62: // 0:Google
63: // 1:Yahoo!ジオコーダAPI
64: // 11:HeartRails Geo API
65: // 12:OSM Nominatim Search API
66: // 13:国土地理院ジオコーディングAPI
67: define('GEOSERVICE', 1);
68:
69: //逆ジオコーディングサービスの選択
70: // 0:Google
71: // 1:Yahoo!JAPAN
72: // 11:HeartRails Geo API
73: // 21:簡易ジオコーディングサービス
74: define('REVGEOSERVICE', 1);
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo APIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、Google、Yahoo!JAPAN、HeartRails Geo API、簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。


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
221: /**
222: * HeartRails Express のURLを取得する
223: * @param double $lat 緯度(世界測地系)
224: * @param double $lng 経度(世界測地系)
225: * @return string URL
226: */
227: function getURL_Heartrails($lat, $lng) {
228: $res = "http://express.heartrails.com/api/xml?method=getStations&x={$lng}&y={$lat}";
229:
230: return $res;
231: }
stationsearch.php
233: /**
234: * HeartRails Express API から必要な情報を配列に格納する
235: * @param double $latitude 緯度(世界測地系)
236: * @param double $longitude 経度(世界測地系)
237: * @return array(ヒットした施設数, メッセージ, APIのURL)
238: * @return int ヒット数
239: */
240: function getResults_Heartrails($latitude, $longitude, &$items) {
241: //受信データの要素名
242: $tbl = array(
243: 'name', //最寄駅名
244: 'prev', //前の駅名 (始発駅の場合は null)
245: 'next', //次の駅名 (終着駅の場合は null)
246: 'x', //最寄駅の経度 (世界測地系)
247: 'y', //最寄駅の緯度 (世界測地系)
248: 'distance', //指定の場所から最寄駅までの距離 (精度は 10 m)
249: 'postal', //最寄駅の郵便番号
250: 'prefecture', //最寄駅の存在する都道府県名
251: 'line' //最寄駅の存在する路線名
252: );
253:
254: $url = $this->getURL_Heartrails($latitude, $longitude); //リクエストURL
255: $cnt = 1;
256:
257: //PHP4用; DOM XML利用
258: if (! $this->isphp5over()) {
259: if (($dom = $this->read_xml($url)) == NULL) {
260: return array(FALSE, 'WebAPIのトラブルです.', FALSE);
261: }
262: $resultset = $dom->get_elements_by_tagname('response');
263: $results = $resultset[0]->get_elements_by_tagname('station');
264: //検索結果取りだし
265: foreach ($results as $element) {
266: foreach ($tbl as $name) {
267: $node = $element->get_elements_by_tagname($name);
268: if ($node != NULL) {
269: $items[$cnt][$name] = (string)$node[0]->get_content();
270: }
271: }
272: $items[$cnt]['id'] = $this->num2alpha($cnt);
273: $items[$cnt]['title'] = $items[$cnt]['name'];
274: $items[$cnt]['longitude'] = $items[$cnt]['x'];
275: $items[$cnt]['latitude'] = $items[$cnt]['y'];
276: $items[$cnt]['description'] =<<< EOT
277: {$items[$cnt]['name']} ({$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']} ({$items[$cnt]['line']}){$items[$cnt]['distance']}
302: EOT;
303: $cnt++;
304: }
305: }
306:
307: return array($cnt - 1, '', $url);
308: }
Googleマップ描画
pahooGeoCode.php
867: /**
868: * Googleマップを描く
869: * @param string $id マップID
870: * @param float $latitude 中心座標:緯度(世界測地系)
871: * @param float $longitude 中心座標:経度(世界測地系)
872: * @param string $type マップタイプ:HYBRID/ROADMAP/SATELLITE/TERRAIN
873: * @param int $zoom 拡大率
874: * @param string $call イベント発生時にコールする関数(省略可)
875: * @param array $items 地点情報(省略可能)
876: * string title タイトル
877: * string description 情報ウィンドウに表示する内容(HTML文)
878: * float latitude 緯度
879: * float longitude 経度
880: * string icon アイコンURL
881: * @param string $call2 追加スクリプト(省略可)
882: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
883: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
884: * @return string Googleマップのコード
885: */
886: function drawGMap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL) {
887: $key = $this->GOOGLE_API_KEY_1;
888: $call = ($call != NULL) ? $call . '()' : '';
889: if (! is_array($offset)) {
890: $offset = array(0, 0);
891: }
892:
893: $mapId = $this->GOOGLE_MAP_ID;
894: $code =<<< EOT
895: <script src="https://maps.googleapis.com/maps/api/js?key={$key}&loading=async&libraries=marker&callback=initMap®ion=JP" async defer loading="async"></script>
896: <script>
897: function initMap() {
898: var map = new google.maps.Map(document.getElementById('{$id}'), {
899: center: { lat: {$latitude}, lng: {$longitude} },
900: mapId: '{$mapId}',
901: zoom: {$zoom},
902: mapTypeId: google.maps.MapTypeId.{$type},
903: mapTypeControl: true,
904: scaleControl: true
905: });
906:
907: map.addListener('dragend', getPointData);
908: map.addListener('zoom_changed', getPointData);
909: map.addListener('maptypeid_changed', getPointData);
910:
911: // イベント発生時の地図情報を取得・格納
912: function getPointData() {
913: var point = map.getCenter();
914: // 経度
915: if (document.getElementById("longitude") != null) {
916: document.getElementById("longitude").value = point.lng();
917: }
918: // 緯度
919: if (document.getElementById("latitude") != null) {
920: document.getElementById("latitude").value = point.lat();
921: }
922: // ズーム
923: if (document.getElementById("zoom") != null) {
924: document.getElementById("zoom").value = map.getZoom();
925: }
926: // 地図タイプ
927: if (document.getElementById("type") != null) {
928: var type_g = map.getMapTypeId();
929: var types = {"roadmap":"地図", "satellite":"航空写真", "hybrid":"ハイブリッド", "terrain":"地形図" };
930: for (key in types) {
931: if (key == type_g) {
932: document.getElementById("type").value = key;
933: break;
934: }
935: }
936: }
937: {$call}
938: }
939:
940: EOT;
941: // 地点情報
942: if ($items != NULL) {
943: foreach ($items as $i=>$item) {
944: if ($i > 999) break; // 最大999箇所まで
945: $mark = (string)sprintf('%03d', $i);
946: // アイコン
947: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
948: $icon = isset($item['icon']) ? $item['icon'] :
949: "https://www.google.com/mapfiles/marker{$mark2}.png";
950: list($icon_width, $icon_height) = getimagesize($icon);
951: if (isset($item['label']) && ($item['label'] != '')) {
952: $size1 = $item['label_size'] * mb_strlen($item['label']);
953: $size2 = (int)($size1 / 2);
954: $ss =<<< EOT
955: const icon = document.createElement('div');
956: icon.innerHTML = `
957: <div style="position: relative; text-align: center;">
958: <img src="https://www.pahoo.org/common/space.gif" style="width:{$size1}px; height:{$size1}px;">
959: <div style="position: absolute; top:{$size2}px; left:0px; width:100%; font-size:{$item['label_size']}px; color:{$item['label_color']}; font-weight:{$item['label_weight']};">
960: {$item['label']}
961: </div>
962: </div>
963: `;
964:
965: EOT;
966: } else {
967: $ss =<<< EOT
968: const icon = document.createElement('img');
969: icon.src = '{$icon}';
970: icon.style.width = '{$icon_width}px';
971: icon.style.height = '{$icon_height}px';
972: icon.style.transform = 'translate(0%, 0%)';
973:
974: EOT;
975: }
976: $code .=<<< EOT
977: const marker_{$mark} = new google.maps.marker.AdvancedMarkerElement({
978: position: { lat: {$item['latitude']}, lng: {$item['longitude']} },
979: map: map,
980: content: (() => {
981: {$ss}
982: return icon;
983: })(),
984: title: '{$item['title']}',
985: zIndex: 100
986: });
987:
988: EOT;
989: if (isset($item['description'])) {
990: $code .=<<< EOT
991: var infowindow_{$mark} = new google.maps.InfoWindow({
992: content: '{$item['description']}',
993: maxWidth: {$max_width},
994: pixelOffset: new google.maps.Size({$offset[0]}, {$offset[1]})
995: });
996: marker_{$mark}.addListener('gmp-click', function() {
997: infowindow_{$mark}.open(map, marker_{$mark});
998: });
999:
1000: EOT;
1001: }
1002: }
1003: }
1004: // 追加関数
1005: if ($call2 != NULL) {
1006: $code .=<<< EOT
1007: {$call2}
1008:
1009: EOT;
1010: }
1011: $code .=<<< EOT
1012: }
1013: </script>
1014:
1015: EOT;
1016:
1017: return $code;
1018: }
まずソースとして、"https://maps.googleapis.com/maps/api/js" を読み込む。このとき、下表に示すパラメータを渡してやる必要がある。
パラメータ | 値 | 意味 |
---|---|---|
key | Google Cloud Platform APIキー | |
loading | async | 非同期呼び出しを行う。 |
libraries | marker | 高度なマーカーを利用する。 |
callback | initMap | コールバック関数名 |
region | JP | 日本の地図を表示する。 |

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