サンプル・プログラム:実行例

目次
サンプル・プログラム
drawArea.php | サンプル・プログラム本体 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
サンプル・プログラムの流れ

- 中心点($center)または対角点($diagonal)が指定されていれば、pahooGeoCode::searchPoint3 メソッドを呼び出し、緯度・経度を求める。
- 対角点はないが距離($distance)があれば、pahooGeoCode::getPointDistance メソッドを呼び出し、中心から北西へ指定距離だけ離れた地点の緯度・経度を求め、これを対角点とする。
- 対角点があるか、または距離がなければ、pahooGeoCode::rhumbLine メソッドを呼び出し、中心と対角点の間の距離(等角航路距離)を計算する。
- 以上の計算が終わったら、中心点の存在する象限に応じて矩形の南西端と北東端を計算する。
- 地図描画のために必要な変数を代入し、定数 MAPSERVICE の値に応じ、Googleマップ、Leafletの描画スクリプトを生成する。マップごとに、さらに円描画か矩形描画かを場合分けしている。
- 生成したスクリプトを pahooGeoCode::drawJSMap メソッドに渡し、画面上に地図と図形を表示する。
準備: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の登録方法」を参照されたい。
準備:地図サービス(WebAPI)の選択
47: //地図描画サービスの選択
48: // 0:Google
49: // 2:地理院地図・OSM
50: define('MAPSERVICE', 2);
51:
52: //住所検索サービスの選択
53: // 0:Google
54: // 1:Yahoo!JAPAN
55: // 11:HeartRails Geo API
56: define('GEOSERVICE', 11);
57:
58: //逆ジオコーディングサービスの選択
59: // 0:Google
60: // 1:Yahoo!JAPAN
61: // 11:HeartRails Geo API
62: // 21:簡易ジオコーディングサービス
63: define('REVGEOSERVICE', 21);
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo APIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、Google、Yahoo!JAPAN、HeartRails Geo API、簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。

準備:カラーピッカー Spectrum
また、jQuery も利用するので、その場所を定数 JQUERY に代入しておくこと。
123: <script src="{$jquery}"></script>
124: <script src="{$spectrum}spectrum.js"></script>
125: <script>
126: $(function() {
127: $("#border_color").spectrum({
128: showAlpha: false,
129: showPalette: true,
130: palette: [
131: ["#0000FF", "#FF0000", "#FF00FF"],
132: ["#00FF00", "#00FFFF", "#FFFF00"]
133: ],
134: maxSelectionSize: 4,
135: preferredFormat: "hex",
136: showInput: true
137: });
138: $("#fill_color").spectrum({
139: showAlpha: true,
140: showPalette: true,
141: palette: [
142: ["rgba(0, 0, 255, 0.3)", "rgba(255, 0, 0, 0.3)", "rgba(255, 0, 255, 0.3)"],
143: ["rgba(0, 255, 0, 0.3)", "rgba(0, 255, 255, 0.3)", "rgba(255, 255, 0, 0.3)"],
144: ],
145: maxSelectionSize: 4,
146: preferredFormat: "rgb",
147: showInput: true
148: });
149: });
準備:初期値
65: //マップの表示サイズ(単位:ピクセル)
66: define('MAP_WIDTH', 600);
67: define('MAP_HEIGHT', 600);
68: //マップID
69: define('MAPID', 'map_id');
70: //アイコン(中心)
71: define('ICON_CENTER', 'https://maps.google.co.jp/mapfiles/ms/icons/red-dot.png');
72: //アイコン(対角)
73: define('ICON_DIAGONAL', 'https://labs.google.com/ridefinder/images/mm_20_red.png');
74: //初期値
75: define('DEF_LONGITUDE0', 139.766667); //地図中心(経度)
76: define('DEF_LATITUDE0', 35.681111); // (緯度)
77: define('DEF_CENTER', ''); //検索クエリ(center)
78: define('DEF_DIAGONAL', ''); // (diagonal)
79: define('DEF_CAT_CENTER', 'address'); //領域中心(カテゴリ)
80: define('DEF_CAT_DIAGONAL', 'address'); //領域対角(カテゴリ)
81: define('DEF_LONGITUDE1', 139.766667); //領域中心(経度)
82: define('DEF_LATITUDE1', 35.681111); // (緯度)
83: define('DEF_LONGITUDE2', 139.700278); //領域対角(経度)
84: define('DEF_LATITUDE2', 35.690833); // (緯度)
85: define('DEF_TYPE', 'roadmap'); //マップタイプ
86: define('DEF_ZOOM', 12); //ズーム
87: define('DEF_FIGURE', 'circle'); //図形タイプ
88: define('DEF_BORDER_COLOR', '#FF0000'); //境界色
89: define('DEF_FILL_COLOR', '#FF0000'); //領域色
90: define('DEF_OPACITY', 0.3); //透明度
Googleマップでの図形描画
332: /**
333: * マーカー設定、フィット機能スクリプト:Googleマップ用
334: * @param float $latitude, $longitude 経路の中央点座標(緯度・経度)
335: * @param bool $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
336: * @return string JavaScript
337: */
338: function jsDragMarker_Gmap($latitude, $longitude, $drag=FALSE) {
339: $submit = $drag ? 'document.myform.submit();' : '';
340:
341: $js =<<< EOT
342: //イベント設定
343: document.getElementById('fit').addEventListener('click', fitting, false);
344:
345: //マーカー設定
346: marker_A.setDraggable(true);
347: marker_A.addListener('dragend', function() {
348: marker_A.setIcon(icon_A);
349: marker_A.setDraggable(true);
350: latlng = marker_A.getPosition();
351: document.getElementById('longitude1').value = latlng.lng();
352: document.getElementById('latitude1').value = latlng.lat();
353: document.getElementById('center').value = '';
354: document.getElementById('distance').value = '';
355: {$submit}
356: });
357:
358: marker_B.setDraggable(true);
359: marker_B.addListener('dragend', function() {
360: marker_B.setIcon(icon_B);
361: marker_B.setDraggable(true);
362: latlng = marker_B.getPosition();
363: document.getElementById('longitude2').value = latlng.lng();
364: document.getElementById('latitude2').value = latlng.lat();
365: document.getElementById('diagonal').value = '';
366: document.getElementById('distance').value = '';
367: {$submit}
368: });
369:
370: /**
371: * 地図を最適サイズにフィットする
372: * @param なし
373: * @return なし
374: */
375: function fitting() {
376: var lat0, lng0, lat1, lng1;
377: var lat = Array();
378: var lng = Array();
379: lat[0] = document.getElementById('latitude1').value;
380: lng[0] = document.getElementById('longitude1').value;
381: lat[1] = document.getElementById('latitude2').value;
382: lng[1] = document.getElementById('longitude2').value;
383: lat[2] = {$latitude};
384: lng[2] = {$longitude};
385:
386: //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
387: if (lat[0] <= lat[1]) {
388: lat0 = lat[0];
389: lat1 = lat[1];
390: } else {
391: lat0 = lat[1];
392: lat1 = lat[0];
393: }
394: if (lat[2] >= 0) {
395: if (lat[0] <= lat[2]) {
396: lng0 = lng[0];
397: lng1 = lng[1];
398: } else {
399: lng0 = lng[1];
400: lng1 = lng[0];
401: }
402: } else {
403: if (lat[0] <= lat[2]) {
404: lng0 = lng[1];
405: lng1 = lng[0];
406: } else {
407: lng0 = lng[0];
408: lng1 = lng[1];
409: }
410: }
411:
412: var bounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat0, lng0), new google.maps.LatLng(lat1, lng1));
413: map.fitBounds(bounds); //地図表示の最適化
414: }
415:
416: EOT;
417: return $js;
418: }

中心点と対角点を示すマーカーはドラッグ可能とし、ドラッグ・イベントをフックし、緯度・経度をformオブジェクトに代入するようにする。

フィット機能は、図形の南西端と北東端を求め、GoogleマップAPIのメソッド fitBounds を呼び出すことで実現している。
420: /**
421: * 円描画スクリプト:Googleマップ用
422: * @param int $id 識別ID
423: * @param float $lat, $lng 中心座標(緯度,経度)
424: * @param float $radius 半径(キロメートル)
425: * @param string $color 描画色(RGB)
426: * @param string $fillcolor 塗りつぶし色(RGB)
427: * @param float $opacity 塗りつぶしの不透明度(0.0~1.0;省略時=1.0)
428: * @param int $weight 線の太さ(省略時=1)
429: * @return string JavaScript
430: */
431: function jsDrawCircle_Gmap($id, $lat, $lng, $radius, $color, $fillcolor, $opacity=1.0, $weight=1) {
432: $radius *= 1000;
433: $js =<<< EOT
434: var pcircle{$id} = new google.maps.Circle({
435: map: map,
436: center: new google.maps.LatLng({$lat}, {$lng}),
437: radius: {$radius},
438: strokeColor: "#{$color}",
439: strokeOpacity: 1.0,
440: fillColor: "#{$fillcolor}",
441: fillOpacity: {$opacity},
442: strokeWeight: {$weight},
443: zIndex: 1
444: });
445:
446: EOT;
447: return $js;
448: }
450: /**
451: * 矩形描画スクリプト:Googleマップ用
452: * @param int $id 識別ID
453: * @param float $lat1, $lng1 北東端の緯度・経度
454: * @param float $lat2, $lng2 南西端の緯度・経度
455: * @param string $color 描画色(RGB)
456: * @param string $fillcolor 塗りつぶし色(RGB)
457: * @param float $opacity 塗りつぶしの不透明度(0.0~1.0;省略時=1.0)
458: * @param int $weight 線の太さ(省略時=1)
459: * @return string JavaScript
460: */
461: function jsDrawRectangle_Gmap($id, $lat1, $lng1, $lat2, $lng2, $color, $fillcolor, $opacity=1.0, $weight=1) {
462: $js =<<< EOT
463: var prect{$id} = new google.maps.Rectangle({
464: map: map,
465: bounds: {
466: north: {$lat1},
467: south: {$lat2},
468: east: {$lng1},
469: west: {$lng2}
470: },
471: strokeColor: "#{$color}",
472: strokeOpacity: 1.0,
473: fillColor: "#{$fillcolor}",
474: fillOpacity: {$opacity},
475: strokeWeight: {$weight},
476: zIndex: 1
477: });
478:
479: EOT;
480: return $js;
481: }
Leaflet での図形描画
631: /**
632: * マーカー設定、フィット機能スクリプト:Leafletマップ用
633: * @param float $latitude, $longitude 経路の中央点座標(緯度・経度)
634: * @param bool $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
635: * @return string JavaScript
636: */
637: function jsDragMarker_Leaflet($latitude, $longitude, $drag=FALSE) {
638: $submit = $drag ? 'document.myform.submit();' : '';
639:
640: $js =<<< EOT
641: //イベント設定
642: document.getElementById('fit').addEventListener('click', fitting, false);
643:
644: //マーカー設定
645: marker_A.dragging.enable();
646: marker_A.on('dragend', function(event) {
647: var marker = event.target;
648: var position = marker.getLatLng();
649: marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:'true'});
650: map.panTo(new L.LatLng(position.lat, position.lng))
651: document.getElementById('longitude1').value = position.lng;
652: document.getElementById('latitude1').value = position.lat;
653: document.getElementById('center').value = '';
654: document.getElementById('distance').value = '';
655: {$submit}
656: });
657:
658: marker_B.dragging.enable();
659: marker_B.on('dragend', function(event) {
660: var marker = event.target;
661: var position = marker.getLatLng();
662: marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:'true'});
663: map.panTo(new L.LatLng(position.lat, position.lng))
664: document.getElementById('longitude2').value = position.lng;
665: document.getElementById('latitude2').value = position.lat;
666: document.getElementById('diagonal').value = '';
667: document.getElementById('distance').value = '';
668: {$submit}
669: });
670:
671: /**
672: * 地図を最適サイズにフィットする
673: * @param なし
674: * @return なし
675: */
676: function fitting() {
677: var lat0, lng0, lat1, lng1;
678: var lat = Array();
679: var lng = Array();
680: lat[0] = document.getElementById('latitude1').value;
681: lng[0] = document.getElementById('longitude1').value;
682: lat[1] = document.getElementById('latitude2').value;
683: lng[1] = document.getElementById('longitude2').value;
684: lat[2] = {$latitude};
685: lng[2] = {$longitude};
686:
687: //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
688: if (lat[0] <= lat[1]) {
689: lat0 = lat[0];
690: lat1 = lat[1];
691: } else {
692: lat0 = lat[1];
693: lat1 = lat[0];
694: }
695: if (lat[2] >= 0) {
696: if (lat[0] <= lat[2]) {
697: lng0 = lng[0];
698: lng1 = lng[1];
699: } else {
700: lng0 = lng[1];
701: lng1 = lng[0];
702: }
703: } else {
704: if (lat[0] <= lat[2]) {
705: lng0 = lng[1];
706: lng1 = lng[0];
707: } else {
708: lng0 = lng[0];
709: lng1 = lng[1];
710: }
711: }
712:
713: var bounds = L.latLngBounds([lat0, lng0], [lat1, lng1])
714: map.fitBounds(bounds); //地図表示の最適化
715: }
716: EOT;
717: return $js;
718: }

中心点と対角点を示すマーカーはドラッグ可能とし、ドラッグ・イベントをフックし、緯度・経度をformオブジェクトに代入するようにする。

フィット機能は、図形の南西端と北東端を求め、Leafletのメソッド fitBounds を呼び出すことで実現している。
720: /**
721: * 円描画スクリプト:Leafletマップ用
722: * @param int $id 識別ID
723: * @param float $lat, $lng 中心座標(緯度,経度)
724: * @param float $radius 半径(キロメートル)
725: * @param string $color 描画色(RGB)
726: * @param string $fillcolor 塗りつぶし色(RGB)
727: * @param float $opacity 塗りつぶしの不透明度(0.0~1.0;省略時=1.0)
728: * @param int $weight 線の太さ(省略時=1)
729: * @return string JavaScript
730: */
731: function jsDrawCircle_Leaflet($id, $lat, $lng, $radius, $color, $fillcolor, $opacity=1.0, $weight=1) {
732: $radius *= 1000;
733: $js =<<< EOT
734: var pcircle{$id} = new L.Circle(
735: [{$lat}, {$lng}],
736: {
737: radius: {$radius},
738: color: "#{$color}",
739: weight: {$weight},
740: fillColor: "#{$fillcolor}",
741: fillOpacity: "{$opacity}"
742: }
743: ).addTo(map);
744:
745: EOT;
746: return $js;
747: }
749: /**
750: * 矩形描画スクリプト:Leafletマップ用
751: * @param int $id 識別ID
752: * @param float $lat1, $lng1 北東端の緯度・経度
753: * @param float $lat2, $lng2 南西端の緯度・経度
754: * @param string $color 描画色(RGB)
755: * @param string $fillcolor 塗りつぶし色(RGB)
756: * @param float $opacity 塗りつぶしの不透明度(0.0~1.0;省略時=1.0)
757: * @param int $weight 線の太さ(省略時=1)
758: * @return string JavaScript
759: */
760: function jsDrawRectangle_Leaflet($id, $lat1, $lng1, $lat2, $lng2, $color, $fillcolor, $opacity=1.0, $weight=1) {
761: $js =<<< EOT
762: var prect{$id} = new L.Polygon(
763: [
764: [{$lat1}, {$lng1}],
765: [{$lat1}, {$lng2}],
766: [{$lat2}, {$lng2}],
767: [{$lat2}, {$lng1}]
768: ], {
769: color: "#{$color}",
770: weight: {$weight},
771: fillColor: "#{$fillcolor}",
772: fillOpacity: "{$opacity}"
773: }
774: ).addTo(map);
775:
776: EOT;
777: return $js;
778: }
解説:カラーコード、透明度
279: /**
280: * カラーコード、透明度を取得する
281: * @param string $key パラメータ名(省略不可)
282: * @param string $color 16進カラーコードの初期値(省略時:#FFFFFF)
283: * @param float $opacity 透明度の初期値(省略時:1.0)
284: * @return bool array(#を除く16進カラーコード,透明度0.0~1.0)/FALSE=入力値が不正
285: */
286: function getColor($key, $color='#FFFFFF', $opacity=1.0) {
287: $pat = array(
288: 0 => '/\#([0-9a-f]+)/i',
289: 1 => '/rgb\(\s*([0-9]+)\s*\,\s*([0-9]+)\s*\,\s*([0-9]+)\)/i',
290: 2 => '/rgba\(\s*([0-9]+)\s*\,\s*([0-9]+)\s*\,\s*([0-9]+)\s*\,\s*([0-9\.]+)\)/i'
291: );
292: $str = getParam($key, FALSE, $color);
293: if (preg_match($pat[0], $str, $arr) > 0) {
294: $color = $arr[1];
295: $res = array($color, $opacity);
296: } else if (preg_match($pat[1], $str, $arr) > 0) {
297: $color = sprintf('%02X%02X%02X', $arr[1], $arr[2], $arr[3]);
298: $res = array($color, $opacity);
299: } else if (preg_match($pat[2], $str, $arr) > 0) {
300: $color = sprintf('%02X%02X%02X', $arr[1], $arr[2], $arr[3]);
301: $opacity = (double)$arr[4];
302: $res = array($color, $opacity);
303: } else {
304: $res = FALSE;
305: }
306:
307: return $res;
308: }
カラーコードはrgbで始まる3つの16進数なので、関数 preg_match を使って分解、取得する。透明度(アルファチャネル)がある場合はrgbaで始まる3つの16進数と、最後に透明度を示す10進小数があるので、同様に取得する。
310: /**
311: * カラーコード、透明度をrgb文字列に変換
312: * @param string $color 16進カラーコードの初期値
313: * @param float $opacity 透明度の初期値(省略可能)
314: * @return string rgb文字列($opacity省略時はrgb、それ以外はrgba)
315: */
316: function color2rgb($color, $opacity='') {
317: $pat = '/([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i';
318: if (preg_match($pat, $color, $arr) > 0) {
319: $res = sprintf('%d, %d, %d', hexdec($arr[1]), hexdec($arr[2]), hexdec($arr[3]));
320: if ($opacity == '') {
321: $res = sprintf('rgb(%s)', $res);
322: } else {
323: $res = sprintf('rgba(%s, %f)', $res, $opacity);
324: }
325: } else {
326: $res = FALSE;
327: }
328: return $res;
329: }
活用例
参考サイト
- WebAPIの登録方法:ぱふぅ家のホームページ
- Google Maps JavaScript API
- Leaflet
- 地理院タイル:国土地理院
- OpenStreetMap
- カラーピッカー Spectrum
- PHPで住所・ランドマークから緯度・経度を求める:ぱふぅ家のホームページ
- PHPで緯度・経度から住所を求める:ぱふぅ家のホームページ
- 地図上に、任意の円や矩形を描く:みんなの知識 ちょっと便利帳
Googleマップは、WebAPI機能として円や矩形を描く機能を備えている。また、地理院地図、オープンストリートマップについては、「PHPで住所・ランドマークから最寄り駅を求める」で紹介したJavaScriptライブラリ Leaflet を使うことで同様の処理が可能となる。
そこで、PHPで中心点と対角点または距離を指定し、中心点と対角点は住所などでの検索を可能とし、地図上に円や矩形を描くプログラムを作っていくことにする。
(2022年4月24日)PHP8対応,リファラ・チェック改良