PHPで大圏航路を描く

(1/1)
地球上の2地点間の最短距離を示す経路を大圏航路と呼ぶ。PHPプログラムを使って大圏航路を計算し、GoogleマップやYahoo! JavaScriptマップ上に描画する。あわせて、等角航路を計算してマップ上に描画し、各々の距離計算表を表示する。

(2021年2月20日)PHP8対応,Yahoo! JavaScriptマップ廃止
(2020年3月22日)OSM Nominatim Search APIを利用できるようにした。
(2020年3月14日)地理院地図・OSMに対応した。
(2019年4月25日)フィット機能を追加。IE11動作不具合改善、その他細かい修正を行った。

目次

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

PHPで大圏航路を描く
Googleマップ表示
赤い曲線が大圏航路、黒い直線が等角航路である。

YOLPコンテンツジオコーダAPI(Yahoo!JAPAN)は、世界のキーワードから緯度・経度を求める機能が弱い。たとえば「サンティアゴ」でキーワード検索すると、Google Geocoding API チリの首都を第一候補として返すが、YOLPコンテンツジオコーダAPIはドミニカ共和国の県都しか返さない。
そこで、地図上のマーカーをドラッグし、描画ボタンを押すことで航路を再描画する機能を採り入れた。これは Googleマップ でも利用できる。

サンプル・プログラムのダウンロード

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

準備:pahooGeoCode クラス

0037: class pahooGeoCode {
0038:     var $items;      //検索結果格納用
0039:     var $error;      //エラーフラグ
0040:     var $hits;       //検索ヒット件数
0041:     var $webapi; //直前に呼び出したWebAPI URL
0042: 
0043:     //Google Cloud Platform APIキー
0044:     //https://cloud.google.com/maps-platform/
0045:     //※Google Maps APIを利用しないのなら登録不要
0046:     var $GOOGLE_API_KEY_1 = '**************************';   //HTTPリファラ用
0047:     var $GOOGLE_API_KEY_2 = '**************************';   //IP制限用
0048: 
0049:     //Yahoo! JAPAN Webサービス アプリケーションID
0050:     //https://e.developer.yahoo.co.jp/register
0051:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
0052:     var $YAHOO_APPLICATION_ID = '*****************************';

地図サービスを利用するために、クラスファイル "pahooGeoCode.php" を使用する。組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

Yahoo! JAPAN Webサービスを利用するには Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を参照されたい。
また、地図としてGoogleマップを利用するのであれば、Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を参照されたい。

準備:地図サービスの選択

0034: //地図描画サービスの選択
0035: //    0:Google
0036: //    2:地理院地図・OSM
0037: define('MAPSERVICE', 0);
0038: 
0039: //住所検索サービスの選択
0040: //    0:Google
0041: //    1:Yahoo!JAPAN
0042: //   11:HeartRails Geo API
0043: //   12:OSM Nominatim Search API
0044: define('GEOSERVICE', 1);

表示する地図は、Googleマップ、Yahoo!マップ(YOLP地図)、地理院地図・OSMから選べる。あらかじめ、定数 MAPSERVICE に値を設定すること。
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo APIから選択可能で、定数 GEOSERVICE に値を設定すること。
PHPで大圏航路を描く
Yahoo!マップ表示
PHPで大圏航路を描く
Yahoo!マップ表示
なお、Yahoo!マップで大西洋を横断する航路を描くと、左図のように間違った航路が描かれる。仕様の問題と思われる。
PHPで大圏航路を描く
Yahoo!マップ表示
左図のように大西洋を中央にもってくると、再描画せずとも正しい表示になる。

この問題は、Googleマップでは発生しない。

サンプル・プログラムの流れ

PHPで大圏航路を描く
WebAPIによって2地点の緯度・経度を求め、そこから航路計算を行う。

解説:大圏航路と等角航路

大圏航路
地球を球体とみなすとして、2地点P、Qの最短距離は、P、Qおよび球体の中心を通る平面と球面が交わる大円となる。これを大圏 (たいけん) 航路(Great circle route、大円コース)と呼ぶ。
なお、地球は完全な球体ではないので、厳密には大円と大圏航路には微妙なずれがある。今回は計算を簡便にする目的で、大円が大圏航路であるとしている。
大圏航路と等角航路
視点を変えると、大圏航路は左図の赤線のように直線となる。

これに対し、極点に対して常に一定の角度で進むコースを等角航路(rhumb line)と呼ぶ。天体観測によって緯度は観測できても、正確な時刻を知るためのクロノメーターが無かった時代は、大圏航路より時間がかかるが、等角航路を使っていた。
大圏航路と等角航路
メルカトル図法の世界地図では、大圏航路は曲線(赤線)になるが、等角航路(黒線)は直接となる。しかし、前述のように地球は球体であるから、実際には大圏航路の方が等角航路より短い。
計算式の導出は省くが、緯度(φ)、経度(δ)で表された出発点(φ1,δ1)、到着点(φ2,δ2)の大圏航路の軌跡は
 mimetex 


によって計算できる。
また、地球の半径をRとすると、2地点間の大圏距離は
 mimetex 


によって計算できる。

解説:大圏航路の軌跡

0699: /**
0700:  * 2地点間の大圏航路軌跡を求める
0701:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
0702:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
0703:  * @param   array  $points  軌跡の座標を格納
0704:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
0705:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
0706:  * @return  int座標の数
0707:  *
0708: */
0709: function greatCircleSailing($long_a$lati_a$long_b$lati_b, &$points) {
0710:     $lati_a = deg2rad($lati_a);
0711:     $long_a = deg2rad($long_a);
0712:     $lati_b = deg2rad($lati_b);
0713:     $long_b = deg2rad($long_b);
0714: 
0715:     $l1 = ($long_a >= 0) ? $long_a : 2 * pi() + $long_a;
0716:     $l2 = ($long_b >= 0) ? $long_b : 2 * pi() + $long_b;
0717:     $dd = $l2 - $l1;
0718:     $tt = 0.01;           //経度方向の増分
0719:     if ($dd < 0) {
0720:         $dd = abs($dd);
0721:         $tt = -$tt;
0722:     } else if ($dd > pi()) {
0723:         list($lati_a$lati_b) = array($lati_b$lati_a);
0724:         list($long_a$long_b) = array($long_b$long_a);
0725:         $dd = 2 * pi() - $dd;
0726:     }
0727:     $st = 0.0;
0728:     $cnt = 0;
0729: 
0730:     //軌跡の計算
0731:     $latitude  = $lati_a;
0732:     $longitude = $long_a;
0733:     while ($st < $dd) {
0734:         if ($latitude  >= 0.5 * pi())    $latitude -= pi();
0735:         if ($longitude >= 1.0 * pi())    $longitude = $longitude - pi();
0736:         $points[$cnt]['latitude']  = rad2deg($latitude);
0737:         $points[$cnt]['longitude'] = rad2deg($longitude);
0738:         $longitude += $tt;
0739:         if ($longitude >= pi())     $longitude = $longitude - 2 * pi();
0740:         $latitude = (sin($lati_a) * sin($long_b - $longitude)) / (cos($lati_a) * sin($long_b - $long_a)) + (sin($lati_b) * sin($long_a - $longitude)) / (cos($lati_b) * sin($long_a - $long_b));
0741:         $latitude = atan($latitude);
0742:         if (sin($lati_a) / sin($lati_b) < 0)    $latitude += pi();
0743: 
0744:         $st += abs($tt);
0745:         $cnt++;
0746:     }
0747:     $points[$cnt]['latitude']  = rad2deg($lati_b);
0748:     $points[$cnt]['longitude'] = rad2deg($long_b);
0749:     $cnt++;
0750: 
0751:     return $cnt;
0752: }

メソッド greatCircleSailing は、前述の計算式に則り、大圏航路の軌跡を座標配列として取得する。経度方向に0.01ラジアンずつ増分させ、前述の計算式により緯度を計算してゆくものである。

なお、「PHPで弾道ミサイルの軌道を計算する」で紹介したように、Googleマップでは、メソッド Polyline のプロパティ geodesic を true にすることで、大圏航法による最短距離を近似描画できる。しかし、Yahoo! JavaScriptマップに、この機能がないため、PHP側でメソッドを用意した。Googleマップ描画時にも同じメソッドを使って軌跡を計算している。

解説:大圏航路の距離

0679: /**
0680:  * 2地点間の大圏航路距離を求める
0681:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
0682:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
0683:  * @return  float大圏航路距離
0684:  *
0685: */
0686: function greatCircleDistance($long_a$lati_a$long_b$lati_b) {
0687:     $lati_a = deg2rad($lati_a);
0688:     $long_a = deg2rad($long_a);
0689:     $lati_b = deg2rad($lati_b);
0690:     $long_b = deg2rad($long_b);
0691: 
0692:     //距離の計算
0693:     $ll = abs($long_b - $long_a);
0694:     $distance = 6371.0 * acos(sin($lati_a) * sin($lati_b) + cos($lati_a) * cos($lati_b) * cos($ll));
0695: 
0696:     return $distance;
0697: }

メソッド greatCircleDistance は、前述の計算式に則り、大圏航路の距離を求める。

解説:等角航路の軌跡

0754: /**
0755:  * 2地点間の等角航路軌跡を求める
0756:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
0757:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
0758:  * @param   array  $points  軌跡の座標を格納
0759:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
0760:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
0761:  * @return  float $distance 2地点間の等角航路距離を格納
0762:  * @return  int座標の数
0763:  *
0764: */
0765: function rhumbLine($long_a$lati_a$long_b$lati_b, &$points, &$distance) {
0766:     $lati_a = deg2rad($lati_a);
0767:     $long_a = deg2rad($long_a);
0768:     $lati_b = deg2rad($lati_b);
0769:     $long_b = deg2rad($long_b);
0770:     $n = 200;
0771: 
0772:     //経度方向の増分
0773:     $l1 = ($long_a >= 0) ? $long_a : 2 * pi() + $long_a;
0774:     $l2 = ($long_b >= 0) ? $long_b : 2 * pi() + $long_b;
0775:     $d1 = $l2 - $l1;
0776:     $t1 = 0.01;
0777:     $n = abs($d1) / $t1;
0778:     if ($d1 < 0) {
0779:         $d1 = 2 * pi() + $d1;
0780:         $t1 = -$t1;
0781:     } else if ($d1 > pi()) {
0782:         list($lati_a$lati_b) = array($lati_b$lati_a);
0783:         list($long_a$long_b) = array($long_b$long_a);
0784:         $d1 = 2 * pi() - $d1;
0785:         $n = abs($d1) / $t1;
0786:     }
0787: 
0788:     //緯度方向の増分
0789:     $l1 = $lati_a;
0790:     $l2 = $lati_b;
0791:     $d2 = $l2 - $l1;
0792:     if ($d2 >= pi()) {
0793:         $d2 -= pi();
0794:         $t2 = - $d2 / $n;
0795:     } else {
0796:         $t2 = $d2 / $n;              //緯度方向の増分
0797:     }
0798: 
0799:     //軌跡の計算
0800:     $latitude  = $lati_a;
0801:     $longitude = $long_a;
0802:     $distance  = 0.0;
0803:     for ($cnt = 0; $cnt < $n$cnt++) {
0804:         if ($latitude  >= 0.5 * pi())    $latitude -= pi();
0805:         if ($longitude >= 1.0 * pi())    $longitude = $longitude - 2 * pi();
0806:         $points[$cnt]['latitude']  = rad2deg($latitude);
0807:         $points[$cnt]['longitude'] = rad2deg($longitude);
0808:         $longitude += $t1;
0809:         $latitude  += $t2;
0810:         if ($cnt >= 1) {
0811:             $distance += $this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
0812:         }
0813:     }
0814:     $points[$cnt]['latitude']  = rad2deg($lati_b);
0815:     $points[$cnt]['longitude'] = rad2deg($long_b);
0816:     $distance += $this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
0817:     $cnt++;
0818: 
0819:     return $cnt;
0820: }

メソッド rhumbLine は、等角航路の軌跡を座標配列として取得する。経度方向に0.01ラジアンずつ増分させ、それに応じて緯度方向もリニアに増分させてゆく。

なお、等角航路は複雑な曲線を描くため、距離を求める適当な方程式がない。そこで、隣り合う座標間の大圏距離を積分し、等角航路の距離を同時に算出する。

解説:初期値

0046: //マップの表示サイズ(単位:ピクセル)
0047: define('MAP_WIDTH',  600);
0048: define('MAP_HEIGHT', 400);
0049: //マップID
0050: define('MAPID', 'map_id');
0051: //初期値
0052: define('DEF_LONGITUDE0', 139.766667);        //地図中心(経度)
0053: define('DEF_LATITUDE0',  35.681111);     //    (緯度)
0054: define('DEF_FROM',     '東京駅');         //from(クエリ)
0055: define('DEF_TO',       'ワシントン');       //to (クエリ)
0056: define('DEF_CAT_FROM', 'landmark');        //from(カテゴリ)
0057: define('DEF_CAT_TO',   'world');           //to (カテゴリ)
0058: define('DEF_LONGITUDE1', 139.766667);        //from(経度)
0059: define('DEF_LATITUDE1',  35.681111);     //  (緯度)
0060: define('DEF_LONGITUDE2', -77.0329165);        //to (経度)
0061: define('DEF_LATITUDE2',  38.8878665);        //    (緯度)
0062: define('DEF_TYPE',     'roadmap');         //マップタイプ
0063: define('DEF_ZOOM',     2);                  //ズーム

各種のデフォルト・パラメータは、これらの定数によって設定されている。自由に変更できる。

解説:マーカー設定、フィット機能スクリプト

0313: /**
0314:  * マーカー設定、フィット機能:Yahoo! JavaScriptマップ用スクリプト
0315:  * @param   float $latitude, $longitude 経路の中央点座標(緯度・経度)
0316:  * @param   bool $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
0317:  * @return  string JavaScript
0318: */
0319: function jsDragMarker_YOLPmap($latitude$longitude$drag=FALSE) {
0320:     $submit = $drag ? 'document.myform.submit();' : '';
0321: 
0322: $js =<<< EOT
0323:     //イベント設定
0324:     document.getElementById('fit').addEventListener('click', fitting, false);
0325: 
0326:     //マーカー設定
0327:     marker_A.setDraggable(true);
0328:     marker_A.bind('dragend', function() {
0329:         marker_A.setIcon(icon_A);
0330:         marker_A.setDraggable(true);
0331:         latlng = marker_A.getLatLng();
0332:         document.getElementById('longitude1').value = latlng.lng();
0333:         document.getElementById('latitude1').value  = latlng.lat();
0334:         document.getElementById('from').value = '';
0335:         {$submit}
0336:     });
0337: 
0338:     marker_B.setDraggable(true);
0339:     marker_B.bind('dragend', function() {
0340:         marker_B.setIcon(icon_B);
0341:         marker_B.setDraggable(true);
0342:         latlng = marker_B.getLatLng();
0343:         document.getElementById('longitude2').value = latlng.lng();
0344:         document.getElementById('latitude2').value  = latlng.lat();
0345:         document.getElementById('to').value = '';
0346:         {$submit}
0347:     });
0348: 
0349:     /**
0350:      * 地図を最適サイズにフィットする
0351:      * @param   なし
0352:      * @return  なし
0353:     */
0354:     function fitting() {
0355:         var lat0, lng0, lat1, lng1;
0356:         var lat = Array();
0357:         var lng = Array();
0358:         lat[0] = document.getElementById('latitude1').value;
0359:         lng[0] = document.getElementById('longitude1').value;
0360:         lat[1] = document.getElementById('latitude2').value;
0361:         lng[1] = document.getElementById('longitude2').value;
0362:         lat[2] = {$latitude};
0363:         lng[2] = {$longitude};
0364: 
0365:         //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
0366:         if (lat[0] <= lat[1]) {
0367:             lat0 = lat[0];
0368:             lat1 = lat[1];
0369:         } else {
0370:             lat0 = lat[1];
0371:             lat1 = lat[0];
0372:         }
0373:         if (lat[2] >= 0) {
0374:             if (lat[0] <= lat[2]) {
0375:                 lng0 = lng[0];
0376:                 lng1 = lng[1];
0377:             } else {
0378:                 lng0 = lng[1];
0379:                 lng1 = lng[0];
0380:             }
0381:         } else {
0382:             if (lat[0] <= lat[2]) {
0383:                 lng0 = lng[1];
0384:                 lng1 = lng[0];
0385:             } else {
0386:                 lng0 = lng[0];
0387:                 lng1 = lng[1];
0388:             }
0389:         }
0390: 
0391:         var bounds = new Y.LatLngBounds(new Y.LatLng(lat0, lng0), new Y.LatLng(lat1, lng1));
0392:         ymap.drawBounds(bounds);    //地図表示の最適化
0393:     }
0394: 
0395: EOT;
0396:     return $js;
0397: }

ユーザー関数 jsDragMarker_YOLPmap は、Yahoo! JavaScriptマップ上のマーカーをドラッグし、別の地点に移動するJavaScriptを生成する。
まず、2つのマーカーについて、setDraggable メソッドを使ってドラッグ可能になるよう設定する。次に、ドラッグ終了時に発生する dragend イベントをフックし、マーカーを再描画するとともに、終了時の座標を取得する。
なお、引数 $drag=TRUE に設定すると、ドラッグ終了時に航路を再描画する。サーに負荷をかけないように、デフォルトではFALSEにしている。

ユーザー関数 fitting は、「フィット」ボタンをクリックした時に呼び出される。すべてのマーカーが地図上に収まるように、ズームと中心点を最適化する。
2つのマーカーと等角航路の中央点から、南西端と北東端を求め、これをGooleマップAPIの fitBounds メソッドに渡すことで実現している。

0193: /**
0194:  * マーカー設定、フィット機能:Googleマップ用スクリプト
0195:  * @param   float $latitude, $longitude 経路の中央点座標(緯度・経度)
0196:  * @param   bool $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
0197:  * @return  string JavaScript
0198: */
0199: function jsDragMarker_Gmap($latitude$longitude$drag=FALSE) {
0200:     $submit = $drag ? 'document.myform.submit();' : '';
0201: 
0202: $js =<<< EOT
0203:     //イベント設定
0204:     document.getElementById('fit').addEventListener('click', fitting, false);
0205: 
0206:     //マーカー設定
0207:     marker_A.setDraggable(true);
0208:     marker_A.addListener('dragend', function() {
0209:         marker_A.setIcon(icon_A);
0210:         marker_A.setDraggable(true);
0211:         latlng = marker_A.getPosition();
0212:         document.getElementById('longitude1').value = latlng.lng();
0213:         document.getElementById('latitude1').value  = latlng.lat();
0214:         document.getElementById('from').value = '';
0215:         {$submit}
0216:     });
0217: 
0218:     marker_B.setDraggable(true);
0219:     marker_B.addListener('dragend', function() {
0220:         marker_B.setIcon(icon_B);
0221:         marker_B.setDraggable(true);
0222:         latlng = marker_B.getPosition();
0223:         document.getElementById('longitude2').value = latlng.lng();
0224:         document.getElementById('latitude2').value  = latlng.lat();
0225:         document.getElementById('to').value = '';
0226:         {$submit}
0227:     });
0228: 
0229:     /**
0230:      * 地図を最適サイズにフィットする
0231:      * @param   なし
0232:      * @return  なし
0233:     */
0234:     function fitting() {
0235:         var lat0, lng0, lat1, lng1;
0236:         var lat = Array();
0237:         var lng = Array();
0238:         lat[0] = document.getElementById('latitude1').value;
0239:         lng[0] = document.getElementById('longitude1').value;
0240:         lat[1] = document.getElementById('latitude2').value;
0241:         lng[1] = document.getElementById('longitude2').value;
0242:         lat[2] = {$latitude};
0243:         lng[2] = {$longitude};
0244: 
0245:         //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
0246:         if (lat[0] <= lat[1]) {
0247:             lat0 = lat[0];
0248:             lat1 = lat[1];
0249:         } else {
0250:             lat0 = lat[1];
0251:             lat1 = lat[0];
0252:         }
0253:         if (lat[2] >= 0) {
0254:             if (lat[0] <= lat[2]) {
0255:                 lng0 = lng[0];
0256:                 lng1 = lng[1];
0257:             } else {
0258:                 lng0 = lng[1];
0259:                 lng1 = lng[0];
0260:             }
0261:         } else {
0262:             if (lat[0] <= lat[2]) {
0263:                 lng0 = lng[1];
0264:                 lng1 = lng[0];
0265:             } else {
0266:                 lng0 = lng[0];
0267:                 lng1 = lng[1];
0268:             }
0269:         }
0270: 
0271:         var bounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat0, lng0), new google.maps.LatLng(lat1, lng1));
0272:         map.fitBounds(bounds);                      //地図表示の最適化
0273:     }
0274: 
0275: EOT;
0276:     return $js;
0277: }

ユーザー関数 jsDragMarker_Gmap は、Googleマップ上のマーカーをドラッグし、別の地点に移動するJavaScriptを生成するもので、前述の jsDragMarker_YOLPmap と同じ処理。Googleマップに合わせてメソッドなどを変更している。

0429: /**
0430:  * マーカー設定、フィット機能スクリプト:Leafletマップ用
0431:  * @param   float $latitude, $longitude 経路の中央点座標(緯度・経度)
0432:  * @param   bool  $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
0433:  * @return  string JavaScript
0434: */
0435: function jsDragMarker_Leaflet($latitude$longitude$drag=FALSE) {
0436:     //Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
0437:     if ($longitude < 0)     $longitude = 360 + $longitude;
0438:     $submit = $drag ? 'document.myform.submit();' : '';
0439: 
0440: $js =<<< EOT
0441:     //イベント設定
0442:     document.getElementById('fit').addEventListener('click', fitting, false);
0443: 
0444:     //マーカー設定
0445:     marker_A.dragging.enable();
0446:     marker_A.on('dragend', function(event) {
0447:         var marker = event.target;
0448:         var position = marker_A.getLatLng();
0449:         marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:true});
0450:         map.panTo(new L.LatLng(position.lat, position.lng))
0451:         document.getElementById('longitude1').value = position.lng;
0452:         document.getElementById('latitude1').value  = position.lat;
0453:         document.getElementById('from').value = '';
0454:         {$submit}
0455:     });
0456: 
0457:     marker_B.dragging.enable();
0458:     marker_B.on('dragend', function(event) {
0459:         var marker = event.target;
0460:         var position = marker.getLatLng();
0461:         marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:true});
0462:         map.panTo(new L.LatLng(position.lat, position.lng))
0463:         document.getElementById('longitude2').value = position.lng;
0464:         document.getElementById('latitude2').value  = position.lat;
0465:         document.getElementById('to').value = '';
0466:         {$submit}
0467:     });
0468: 
0469:     /**
0470:      * 地図を最適サイズにフィットする
0471:      * @param   なし
0472:      * @return  なし
0473:     */
0474:     function fitting() {
0475:         var lat0, lng0, lat1, lng1;
0476:         var lat = Array();
0477:         var lng = Array();
0478:         lat[0] = document.getElementById('latitude1').value;
0479:         lng[0] = document.getElementById('longitude1').value;
0480:         lat[1] = document.getElementById('latitude2').value;
0481:         lng[1] = document.getElementById('longitude2').value;
0482:         lat[2] = {$latitude};
0483:         lng[2] = {$longitude};
0484: 
0485:         //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
0486:         if (lat[0] <= lat[1]) {
0487:             lat0 = lat[0];
0488:             lat1 = lat[1];
0489:         } else {
0490:             lat0 = lat[1];
0491:             lat1 = lat[0];
0492:         }
0493:         if (lat[2] >= 0) {
0494:             if (lat[0] <= lat[2]) {
0495:                 lng0 = lng[0];
0496:                 lng1 = lng[1];
0497:             } else {
0498:                 lng0 = lng[1];
0499:                 lng1 = lng[0];
0500:             }
0501:         } else {
0502:             if (lat[0] <= lat[2]) {
0503:                 lng0 = lng[1];
0504:                 lng1 = lng[0];
0505:             } else {
0506:                 lng0 = lng[0];
0507:                 lng1 = lng[1];
0508:             }
0509:         }
0510: 
0511:         //Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
0512:         //bug:Leafletマップではfitが効かない
0513:         if (lng0 < 0)   lng0 = 360 + lng0;
0514:         if (lng1 < 0)   lng1 = 360 + lng1;
0515:         var bounds = L.latLngBounds([lat0, lng0], [lat1, lng1])
0516:         map.fitBounds(bounds);      //地図表示の最適化
0517:     }
0518: EOT;
0519:     return $js;
0520: }

ユーザー関数 jsDragMarker_Leaflet は、地理院地図・OSMマップ上のマーカーをドラッグし、別の地点に移動するJavaScriptを生成するもので、前述の jsDragMarker_YOLPmap と同じ処理。Leafletに合わせてメソッドなどを変更している。

解説:航路描画スクリプト

0399: /**
0400:  * 航路描画スクリプト:Yahoo! JavaScriptマップ
0401:  * @param   int    $id     識別ID
0402:  * @param   array  $items  航路の座標
0403:  * @param   string $color  描画色(RGB)
0404:  * @param   int    $weight 線の太さ(省略時=1)
0405:  * @return  string JavaScript
0406: */
0407: function jsDrawLine_YOLPmap($id$items$color$weight=1) {
0408: $js =<<< EOT
0409:     var latlng{$id} = [
0410: 
0411: EOT;
0412:     foreach ($items as $item) {
0413: $js .=<<< EOT
0414:         new Y.LatLng({$item['latitude']}, {$item['longitude']}),
0415: 
0416: EOT;
0417:     }
0418: $js .=<<< EOT
0419:     ];
0420:     var style{$id} = new Y.Style("{$color}", {$weight}, 1);
0421:     var polyline{$id} = new Y.Polyline(latlng{$id}, {strokeStyle: style{$id}});
0422:     ymap.addFeature(polyline{$id});
0423: 
0424: EOT;
0425:     return $js;
0426: }

ユーザー関数 jsDrawLine_YOLPmap は、Yahoo! JavaScriptマップ上で、計算した大圏航路または等角高度の座標を繋ぐ直線を描くJavaScriptを生成する。

0279: /**
0280:  * 航路描画スクリプト:Googleマップ用スクリプト
0281:  * @param   int    $id     識別ID
0282:  * @param   array  $items  航路の座標
0283:  * @param   string $color  描画色(RGB)
0284:  * @param   int    $weight 線の太さ(省略時=1)
0285:  * @return  string JavaScript
0286: */
0287: function jsDrawLine_Gmap($id$items$color$weight=1) {
0288: $js =<<< EOT
0289:     var pline{$id} = new google.maps.Polyline({
0290:         map: map,
0291:         path:[
0292: 
0293: EOT;
0294:     foreach ($items as $item) {
0295: $js .=<<< EOT
0296:         new google.maps.LatLng({$item['latitude']}, {$item['longitude']}),
0297: 
0298: EOT;
0299:     }
0300: $js .=<<< EOT
0301:     ],
0302:     strokeColor: "{$color}",
0303:     strokeOpacity: 1,
0304:     strokeWeight: {$weight},
0305:     zIndex: 1
0306:     });
0307: 
0308: EOT;
0309:     return $js;
0310: }

ユーザー関数 jsDrawLine_Gmap は、Googleマップ上で、計算した大圏航路または等角高度の座標を繋ぐ直線を描くJavaScriptを生成するもので、前述の jsDrawLine_YOLPmap と同じ処理。

0522: /**
0523:  * 航路描画スクリプト:Leafletマップ用
0524:  * @param   int    $id     識別ID
0525:  * @param   array  $items  航路の座標
0526:  * @param   string $color  描画色(RGB)
0527:  * @param   int    $weight 線の太さ(省略時=1)
0528:  * @return  string JavaScript
0529: */
0530: function jsDrawLine_Leaflet($id$items$color$weight=1) {
0531:     $cnt = 0;
0532: $js =<<< EOT
0533:     var pline{$id}_{$cnt} = L.polyline([
0534: 
0535: EOT;
0536:     foreach ($items as $key=>$item) {
0537:         if ($key > 0) {
0538:             //Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
0539:             if ((($items[$key - 1]['longitude'] > 0) && ($items[$key]['longitude'] < 0)) || (($items[$key - 1]['longitude'] < 0) && ($items[$key]['longitude'] > 0))) {
0540:                 $cnt++;
0541: $js .=<<< EOT
0542:     ], {
0543:     "color": "#{$color}",
0544:     "opacity": 1.0,
0545:     "weight": {$weight},
0546: }).addTo(map);
0547:     var pline{$id}_{$cnt} = L.polyline([
0548: 
0549: EOT;
0550:             }
0551:         }
0552:         $long = ($item['longitude'] < 0) ? 360 + $item['longitude'] : $item['longitude'];
0553: $js .=<<< EOT
0554:         [{$item['latitude']}, {$long}],
0555: 
0556: EOT;
0557:     }
0558: $js .=<<< EOT
0559:     ], {
0560:     "color": "#{$color}",
0561:     "opacity": 1.0,
0562:     "weight": {$weight},
0563: }).addTo(map);
0564: 
0565: EOT;
0566:     return $js;
0567: }

ユーザー関数 jsDrawLine_Leaflet は、地理院地図・OSM上で、計算した大圏航路または等角高度の座標を繋ぐ直線を描くJavaScriptを生成するもので、前述の jsDrawLine_YOLPmap と同じ処理。
なお、Lefletのメソッド polyline を使って線分を繋げていくと、東経と西経を

跨ぐ地点(経度の符合が変わる地点)を境目に、線分が折り返してしまう。そこで、境目で線分を、いったん断絶させるようにしている。航路を拡大してみると、この部分で切れていることが見えてしまうが、ご了承いただきたい。

また、マーカーの位置を調整するため、前述のユーザー関数 jsDragMarker_Leaflet を含め、マイナス表記の西経を360度表記に変換したりしており、フィット機能が正常に働かないため、これを省いている。

解説:距離計算表

0570: /**
0571:  * 距離計算表を作成
0572:  * @param   array  $items地点・距離情報
0573:  * @param   object $pgc   pahooGeoCodeクラス
0574:  * @return  string 距離計算表(HTML)
0575: */
0576: function makeDistanceTable($items$pgc) {
0577:     $d11 = sprintf('%.1f', round($items['dist1'], 1));
0578:     $d12 = sprintf('%.1f', round($pgc->km2mi($items['dist1']), 1));
0579:     $d13 = sprintf('%.1f', round($pgc->km2nm($items['dist1']), 1));
0580:     $d21 = sprintf('%.1f', round($items['dist2'], 1));
0581:     $d22 = sprintf('%.1f', round($pgc->km2mi($items['dist2']), 1));
0582:     $d23 = sprintf('%.1f', round($pgc->km2nm($items['dist2']), 1));
0583:     $ww  = MAP_WIDTH / 4;
0584: 
0585: $html =<<< EOT
0586: <table class="plists">
0587: <caption>距離計算</caption>
0588: <tr>
0589: <th style="width:{$ww}px;">&nbsp;</th>
0590: <th style="width:{$ww}px;">キロメートル</th>
0591: <th style="width:{$ww}px;">マイル</th>
0592: <th style="width:{$ww}px;">海里</th>
0593: </tr>
0594: <tr>
0595: <th>大圏航路</th>
0596: <td>{$d11}</td>
0597: <td>{$d12}</td>
0598: <td>{$d13}</td>
0599: </tr>
0600: <tr>
0601: <th>等角航路</th>
0602: <td>{$d21}</td>
0603: <td>{$d22}</td>
0604: <td>{$d23}</td>
0605: </tr>
0606: </table>
0607: 
0608: EOT;
0609:     return $html;
0610: }

ユーザー関数 makeDistanceTable は、大圏航法、等角後方の各々の距離について、キロメートル、マイル、海里の3つの単位で表示する。

活用例

地図上で2点間の直線距離を測」(みんなの知識 ちょっと便利帳)では、このサンプル・プログラムを活用し、見やすく表示している。ありがとうございます。

参考サイト

(この項おわり)
header