目次
サンプル・プログラムの実行例
サンプル・プログラム
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.3.3 | 2024/09/14 | $this->NOMINATIM_EMAIL 追加 |
6.3.2 | 2024/02/14 | getStaticMap() -- bug-fix |
6.3.1 | 2023/07/09 | bug-fix |
6.3.0 | 2023/07/02 | getPointsGSI()追加 |
6.2.0 | 2023/07/02 | ip2address()追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.5.0 | 2024/01/28 | exitIfExceedVersion() 追加 |
1.4.2 | 2024/01/28 | exitIfLessVersion() メッセージ修正 |
1.4.1 | 2023/09/30 | コメントの訂正 |
1.4.0 | 2023/09/09 | $_GET, $_POST参照をfilter_input()関数に置換 |
1.3.0 | 2023/07/11 | roundFloat() 追加 |
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 クラス
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 = '*****************************';
クラスについては「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、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の呼び出し
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']} ({$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マップ描画
866: /**
867: * Googleマップを描く
868: * @param string $id マップID
869: * @param float $latitude 中心座標:緯度(世界測地系)
870: * @param float $longitude 中心座標:経度(世界測地系)
871: * @param string $type マップタイプ:HYBRID/ROADMAP/SATELLITE/TERRAIN
872: * @param int $zoom 拡大率
873: * @param string $call イベント発生時にコールする関数(省略可)
874: * @param array $items 地点情報(省略可能)
875: * string title タイトル
876: * string description 情報ウィンドウに表示する内容(HTML文)
877: * float latitude 緯度
878: * float longitude 経度
879: * string icon アイコンURL
880: * @param string $call2 追加スクリプト(省略可)
881: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
882: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
883: * @return string Googleマップのコード
884: */
885: function drawGMap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL) {
886: $key = $this->GOOGLE_API_KEY_1;
887: $call = ($call != NULL) ? $call . '()' : '';
888: if (! is_array($offset)) {
889: $offset = array(0, 0);
890: }
891:
892: $code =<<< EOD
893: <script src="https://maps.googleapis.com/maps/api/js?key={$key}&callback=initMap&region=JP" async defer></script>
894: <script>
895: function initMap() {
896: var map = new google.maps.Map(document.getElementById('{$id}'), {
897: center: new google.maps.LatLng({$latitude}, {$longitude}),
898: zoom: {$zoom},
899: mapTypeId: google.maps.MapTypeId.{$type},
900: mapTypeControl: true,
901: scaleControl: true
902: });
903:
904: map.addListener('dragend', getPointData);
905: map.addListener('zoom_changed', getPointData);
906: map.addListener('maptypeid_changed', getPointData);
907:
908: //イベント発生時の地図情報を取得・格納
909: function getPointData() {
910: var point = map.getCenter();
911: //経度
912: if (document.getElementById("longitude") != null) {
913: document.getElementById("longitude").value = point.lng();
914: }
915: //緯度
916: if (document.getElementById("latitude") != null) {
917: document.getElementById("latitude").value = point.lat();
918: }
919: //ズーム
920: if (document.getElementById("zoom") != null) {
921: document.getElementById("zoom").value = map.getZoom();
922: }
923: //地図タイプ
924: if (document.getElementById("type") != null) {
925: var type_g = map.getMapTypeId();
926: var types = {"roadmap":"地図", "satellite":"航空写真", "hybrid":"ハイブリッド", "terrain":"地形図" };
927: for (key in types) {
928: if (key == type_g) {
929: document.getElementById("type").value = key;
930: break;
931: }
932: }
933: }
934: {$call}
935: }
936:
937: EOD;
938: //地点情報
939: if ($items != NULL) {
940: foreach ($items as $i=>$item) {
941: if ($i > 999) break; //最大999箇所まで
942: $mark = (string)sprintf('%03d', $i);
943: //アイコン
944: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
945: $icon = isset($item['icon']) ? $item['icon'] :
946: "https://www.google.com/mapfiles/marker{$mark2}.png";
947: list($icon_width, $icon_height) = getimagesize($icon);
948: if (isset($item['label']) && ($item['label'] != '')) {
949: $ss =<<< EOD
950: icon: {
951: url: 'https://www.pahoo.org/common/space.gif'
952: },
953: label: {
954: text: '{$item['label']}',
955: color: '{$item['label_color']}',
956: fontSize: '{$item['label_size']}px',
957: fontWeight: '{$item['label_weight']}'
958: }
959:
960: EOD;
961: } else {
962: $ss =<<< EOD
963: icon: {
964: url: '{$icon}',
965: size: new google.maps.Size({$icon_width}, {$icon_height}),
966: origin: new google.maps.Point(0, 0),
967: anchor: new google.maps.Point({$icon_width} / 2, {$icon_height})
968: }
969:
970: EOD;
971: }
972: $code .=<<< EOD
973: var marker_{$mark} = new google.maps.Marker({
974: position: new google.maps.LatLng({$item['latitude']}, {$item['longitude']}),
975: map: map,
976: {$ss},
977: title: '{$item['title']}',
978: zIndex: 100
979: });
980:
981: EOD;
982: if (isset($item['description'])) {
983: $code .=<<< EOD
984: var infowindow_{$mark} = new google.maps.InfoWindow({
985: content: '{$item['description']}',
986: maxWidth: {$max_width},
987: pixelOffset: new google.maps.Size({$offset[0]}, {$offset[1]})
988: });
989: marker_{$mark}.addListener('click', function() {
990: infowindow_{$mark}.open(map, marker_{$mark});
991: });
992:
993: EOD;
994: }
995: }
996: }
997: //追加関数
998: if ($call2 != NULL) {
999: $code .=<<< EOD
1000: {$call2}
1001:
1002: EOD;
1003: }
1004: $code .=<<< EOD
1005: }
1006: </script>
1007:
1008: EOD;
1009:
1010: return $code;
1011: }
まずソースとして、"https://maps.googleapis.com/maps/api/js" を読み込む。このとき、パラメータ key に Google 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描画
ここでは、このタイル画像を繋げて、Googleマップのようなユーザー・インターフェースを提供する無償のJavaScriptライブラリ Leaflet を利用することにする。
あわせて、自由に利用できる世界地図作成プロジェクト「OpenStreetMap」(OSM)が提供する地図タイルも利用できるようにした。
1844: /**
1845: * Leafletによるマップ描画
1846: * @param string $id マップID
1847: * @param float $latitude 中心座標:緯度(世界測地系)
1848: * @param float $longitude 中心座標:経度(世界測地系)
1849: * @param string $type マップタイプ:GSISTD/GSIPALE/GSIBLANK/GSIPHOTO/OSM
1850: * @param int $zoom 拡大率
1851: * @param string $call イベント発生時にコールする関数(省略可)
1852: * @param array $items 地点情報(省略可能)
1853: * string description 情報ウィンドウに表示する内容(HTML文)
1854: * float latitude 緯度
1855: * float longitude 経度
1856: * string icon アイコンURL
1857: * @param string $call2 追加スクリプト(省略可)
1858: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
1859: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:NULL)
1860: * @param array $overlays オーバーレイ:GSIELEV/GSIFAULT/GSIFLOOD
1861: * @return string Leafletマップのコード
1862: */
1863: function drawLeaflet($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $call2=NULL, $max_width=200, $offset=NULL, $overlays=NULL) {
1864:
1865: if (! is_array($offset)) {
1866: $offset = array(0, 0);
1867: }
1868: //デフォルト・オーバーレイ
1869: $addoverlay = '';
1870: if ($overlays != NULL) {
1871: foreach ($overlays as $overlay) {
1872: $addoverlay .=<<< EOD
1873: {$overlay}.addTo(map);
1874:
1875: EOD;
1876: }
1877: }
1878:
1879: $code =<<< EOD
1880: <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
1881: <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
1882: <script>
1883: window.onload = function() {
1884: let map = L.map('{$id}',{zoomControl:false});
1885: map.setView([{$latitude}, {$longitude}], {$zoom});
1886: L.control.scale({
1887: maxWidth: 200,
1888: position: 'bottomright',
1889: imperial: false
1890: }).addTo(map);
1891: L.control.zoom({position:'topleft'}).addTo(map);
1892:
1893: //地理院地図:標準地図
1894: let GSISTD = new L.tileLayer(
1895: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
1896: {
1897: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1898: minZoom: 0,
1899: maxZoom: 18,
1900: name: 'GSISTD'
1901: });
1902: //地理院地図:淡色地図
1903: let GSIPALE = new L.tileLayer(
1904: 'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
1905: {
1906: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1907: minZoom: 2,
1908: maxZoom: 18,
1909: name: 'GSIPALE'
1910: });
1911: //地理院地図:白地図
1912: let GSIBLANK = new L.tileLayer(
1913: 'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
1914: {
1915: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1916: minZoom: 5,
1917: maxZoom: 14,
1918: name: 'GSIBLANK'
1919: });
1920: //地理院地図:写真
1921: let GSIPHOTO = new L.tileLayer(
1922: 'https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg',
1923: {
1924: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1925: minZoom: 2,
1926: maxZoom: 18,
1927: name: 'GSIPHOTO'
1928: });
1929: //OpenStreetMap
1930: let OSM = new L.tileLayer(
1931: 'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
1932: {
1933: attribution: "© <a href='https://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
1934: minZoom: 0,
1935: maxZoom: 18,
1936: name: 'OSM'
1937: });
1938:
1939: //baseMapsオブジェクトにタイル設定
1940: let baseMaps = {
1941: "地理院地図" : GSISTD,
1942: "淡色地図" : GSIPALE,
1943: "白地図" : GSIBLANK,
1944: "写真地図" : GSIPHOTO,
1945: "オープンストリートマップ" : OSM
1946: };
1947:
1948: //地理院地図:色別標高図(オーバーレイ)
1949: let GSIELEV = new L.tileLayer(
1950: 'https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png',
1951: {
1952: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1953: opacity: 0.6,
1954: minZoom: 5,
1955: maxZoom: 15,
1956: name: 'GSIELEV'
1957: });
1958: //地理院地図:活断層図(オーバーレイ)
1959: let GSIFAULT = new L.tileLayer(
1960: 'https://cyberjapandata.gsi.go.jp/xyz/afm/{z}/{x}/{y}.png',
1961: {
1962: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1963: opacity: 0.6,
1964: minZoom: 11,
1965: maxZoom: 16,
1966: name: 'GSIFAULT'
1967: });
1968: //地理院地図:治水地形分類図 更新版(オーバーレイ)
1969: let GSIFLOOD = new L.tileLayer(
1970: 'https://cyberjapandata.gsi.go.jp/xyz/lcmfc2/{z}/{x}/{y}.png',
1971: {
1972: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1973: opacity: 0.6,
1974: minZoom: 11,
1975: maxZoom: 16,
1976: name: 'GSIFLOOD'
1977: });
1978:
1979: //baseMapsオブジェクトにオーバーレイ設定
1980: let overlayMaps = {
1981: "色別標高図" : GSIELEV,
1982: "活断層図" : GSIFAULT,
1983: "治水地形分類図" : GSIFLOOD,
1984: };
1985:
1986: //layersコントロールにbaseMapsオブジェクトを設定して地図に追加
1987: L.control.layers(baseMaps, overlayMaps).addTo(map);
1988: {$type}.addTo(map);
1989: {$addoverlay}
1990:
1991: //イベント追加
1992: map.on('viewreset', getPointData);
1993: map.on('zoomend', getPointData);
1994: map.on('baselayerchange', getPointData);
1995: map.on('overlayadd', getPointData);
1996: map.on('overlayremove', getPointData);
1997: map.on('move', getPointData);
1998:
1999: //イベント発生時の地図情報を取得・格納
2000: function getPointData() {
2001: let pos = map.getCenter();
2002: //経度
2003: if (document.getElementById('longitude') != null) {
2004: document.getElementById('longitude').value = pos.lng;
2005: }
2006: //緯度
2007: if (document.getElementById('latitude') != null) {
2008: document.getElementById('latitude').value = pos.lat;
2009: }
2010: //ズーム
2011: if (document.getElementById('zoom') != null) {
2012: document.getElementById('zoom').value = map.getZoom();
2013: }
2014: //タイプ
2015: if (document.getElementById('type') != null) {
2016: for (let k in baseMaps) {
2017: if (map.hasLayer(baseMaps[k])) {
2018: document.getElementById('type').value = baseMaps[k].options.name;
2019: }
2020: }
2021: }
2022: //オーバーレイ
2023: if (document.getElementById('overlays') != null) {
2024: let str = '';
2025: let cnt = 0;
2026: for (let k in overlayMaps) {
2027: if (map.hasLayer(overlayMaps[k])) {
2028: if (cnt > 0) str += ',';
2029: str += overlayMaps[k].options.name;
2030: cnt++;
2031: }
2032: }
2033: document.getElementById('overlays').value = str;
2034: }
2035: {$call}
2036: }
2037:
2038:
2039: EOD;
2040: //地点情報
2041: if ($items != NULL) {
2042: foreach ($items as $i=>$item) {
2043: if ($i > 999) break; //最大999箇所まで
2044: $mark = (string)sprintf('%03d', $i);
2045: //アイコン
2046: $mark2 = ($i <= 26) ? $this->num2alpha($i) : 'Z';
2047: $icon = isset($item['icon']) ? $item['icon'] :
2048: "https://www.google.com/mapfiles/marker{$mark2}.png";
2049: list($icon_width, $icon_height) = getimagesize($icon);
2050: $offx = round($icon_width / 2);
2051: $offy = $icon_height;
2052: $info = isset($item['description']) ? "marker_{$mark}.bindPopup('{$item['description']}', {maxWidth: {$max_width}, offset: [{$offset[0]}, {$offset[1]}] });" : '';
2053:
2054: //アイコン・ラベル
2055: if (isset($item['label']) && ($item['label'] != '')) {
2056: $ss =<<< EOD
2057: let icon_{$mark} = new L.divIcon({
2058: html: '<span style="color:{$item['label_color']}; font-size:{$item['label_size']}px; font-weight:{$item['label_weight']}; white-space:nowrap;">{$item['label']}</span>',
2059: iconSize: [0, 0],
2060: iconAnchor: [{$item['label_size']}, {$item['label_size']}],
2061: });
2062:
2063: EOD;
2064: //通常アイコン
2065: } else {
2066: $ss =<<< EOD
2067: let icon_{$mark} = new L.icon({
2068: iconUrl: '{$icon}',
2069: iconAnchor: [{$offx}, {$offy}]
2070: });
2071: EOD;
2072: }
2073: $code .=<<< EOD
2074: {$ss}
2075: let marker_{$mark} = new L.Marker([{$item['latitude']}, {$item['longitude']}], {icon: icon_{$mark}}).addTo(map);
2076: {$info}
2077:
2078: EOD;
2079: }
2080: }
2081: //追加関数
2082: if ($call2 != NULL) {
2083: $code .=<<< EOD
2084: {$call2}
2085:
2086: EOD;
2087: }
2088: $code .=<<< EOD
2089: }
2090: </script>
2091:
2092: EOD;
2093:
2094: return $code;
2095: }
まずソースとして、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マップの標準的なアルファベットアイコンによってマーキングする。
動的マップ描画
2468: /**
2469: * 地図サービスを利用してJavaScriptマップを描く
2470: *
2471: * @param string $id マップID
2472: * @param float $latitude 中心座標:緯度(世界測地系)
2473: * @param float $longitude 中心座標:経度(世界測地系)
2474: * @param string $type マップタイプ
2475: * Googleの場合 HYBRID/ROADMAP/SATELLITE/TERRAIN
2476: * Yahoo!JAPANの場合 NORMAL/PHOTO/B1/OSM
2477: * @param int $zoom 拡大率
2478: * @param string $call イベント発生時にコールする関数(省略可)
2479: * @param array $items 地点情報(省略可能)
2480: * string title タイトル(Yahoo!では無効)
2481: * string description 情報ウィンドウに表示する内容(HTML文)
2482: * float latitude 緯度
2483: * float longitude 経度
2484: * string icon アイコンURL
2485: * string label アイコン・ラベル(省略可能)
2486: * string label_size アイコン・ラベルのサイズ(省略可能)
2487: * string label_weight アイコン・ラベルの太さ(省略可能)
2488: * string label_color アイコン・ラベルの色(省略可能)
2489: * @param string $api 0:Google Maps JavaScript(省略時)
2490: * 2:地理院地図・OSM(Leaflet使用)
2491: * @param string $call2 追加スクリプト(省略可)
2492: * @param int $max_width 情報ウィンドウの最大幅(省略時:200)
2493: * @param array $offset アイコンから情報ウィンドウのオフセット位置(省略時:0,0)
2494: * @param array $overlays Leaflet用オーバーレイ
2495: * @return string JavaScriptマップのコード
2496: */
2497: function drawJSmap($id, $latitude, $longitude, $type, $zoom, $call=NULL, $items=NULL, $api=0, $call2=NULL, $max_width=200, $offset=NULL, $overlays=NULL) {
2498: //マップタイプの読み替え
2499: static $tbl1 = array('HYBRID'=>'PHOTO', 'ROADMAP'=>'NORMAL', 'SATELLITE'=>'PHOTO', 'TERRAIN'=>'PHOTO');
2500: static $tbl2 = array('NORMAL'=>'ROADMAP', 'PHOTO'=>'SATELLITE', 'B1'=>'ROADMAP', 'OSM'=>'ROADMAP');
2501: static $tbl3 = array('HYBRID'=>'GSISTD', 'ROADMAP'=>'OSM', 'SATELLITE'=>'GSIPHOTO', 'TERRAIN'=>'GSIPHOTO');
2502: $type = strtoupper($type);
2503:
2504: switch ($api) {
2505: //Google Maps JavaScript
2506: case 0;
2507: $type = isset($tbl2[$type]) ? $tbl2[$type] : $type;
2508: $js = $this->drawGMap($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset);
2509: break;
2510: /** 2020年10月31日サービス終了
2511: //Yahoo! JavaScriptマップ
2512: case 1:
2513: $type = isset($tbl1[$type]) ? $tbl1[$type] : $type;
2514: $js = $this->drawYOLPmap($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2);
2515: break;
2516: **/
2517: //地理院地図・OSM(Leaflet使用)
2518: case 2:
2519: $type = isset($tbl3[$type]) ? $tbl3[$type] : $type;
2520: $js = $this->drawLeaflet($id, $latitude, $longitude, $type, $zoom, $call, $items, $call2, $max_width, $offset, $overlays);
2521: break;
2522: }
2523:
2524: return $js;
2525: }
マップタイプについては、相互に読み替えができるようにしてある。
その他の 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日をもってサービスを終了しており利用できない。
(2023年7月14日)検索キーの最小・最大長が指定できるようにした.
(2023年7月2日)国土地理院ジオコーディングAPIを利用できるようにした.