PHPで大圏航路を描く

(1/1)
地球上の 2 地点間の最短距離を示す経路を大圏航路と呼ぶ。PHP プログラムを使って大圏航路を計算し、Google マップや 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 クラス

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

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

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

表示する地図は、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 


によって計算できる。

解説:大圏航路の軌跡

0663: /**
0664:  * 2地点間の大圏航路軌跡を求める
0665:  * @param float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
0666:  * @param float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
0667:  * @param array  $points  軌跡の座標を格納
0668:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
0669:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
0670:  * @return int 座標の数
0671:  *
0672: */
0673: function greatCircleSailing($long_a$lati_a$long_b$lati_b, &$points) {
0674:     $lati_a = deg2rad($lati_a);
0675:     $long_a = deg2rad($long_a);
0676:     $lati_b = deg2rad($lati_b);
0677:     $long_b = deg2rad($long_b);
0678: 
0679:     $l1 = ($long_a >= 0) ? $long_a : 2 * pi() + $long_a;
0680:     $l2 = ($long_b >= 0) ? $long_b : 2 * pi() + $long_b;
0681:     $dd = $l2 - $l1;
0682:     $tt = 0.01;           //経度方向の増分
0683:     if ($dd < 0) {
0684:         $dd = abs($dd);
0685:         $tt = -$tt;
0686:     } else if ($dd > pi()) {
0687:         list($lati_a$lati_b) = array($lati_b$lati_a);
0688:         list($long_a$long_b) = array($long_b$long_a);
0689:         $dd = 2 * pi() - $dd;
0690:     }
0691:     $st = 0.0;
0692:     $cnt = 0;
0693: 
0694:     //軌跡の計算
0695:     $latitude  = $lati_a;
0696:     $longitude = $long_a;
0697:     while ($st < $dd) {
0698:         if ($latitude  >= 0.5 * pi())    $latitude -= pi();
0699:         if ($longitude >= 1.0 * pi())    $longitude = $longitude - pi();
0700:         $points[$cnt]['latitude']  = rad2deg($latitude);
0701:         $points[$cnt]['longitude'] = rad2deg($longitude);
0702:         $longitude += $tt;
0703:         if ($longitude >= pi())     $longitude = $longitude - 2 * pi();
0704:         $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));
0705:         $latitude = atan($latitude);
0706:         if (sin($lati_a) / sin($lati_b) < 0)    $latitude += pi();
0707: 
0708:         $st += abs($tt);
0709:         $cnt++;
0710:     }
0711:     $points[$cnt]['latitude']  = rad2deg($lati_b);
0712:     $points[$cnt]['longitude'] = rad2deg($long_b);
0713:     $cnt++;
0714: 
0715:     return $cnt;
0716: }

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

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

解説:大圏航路の距離

0643: /**
0644:  * 2地点間の大圏航路距離を求める
0645:  * @param float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
0646:  * @param float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
0647:  * @return float 大圏航路距離
0648:  *
0649: */
0650: function greatCircleDistance($long_a$lati_a$long_b$lati_b) {
0651:     $lati_a = deg2rad($lati_a);
0652:     $long_a = deg2rad($long_a);
0653:     $lati_b = deg2rad($lati_b);
0654:     $long_b = deg2rad($long_b);
0655: 
0656:     //距離の計算
0657:     $ll = abs($long_b - $long_a);
0658:     $distance = 6371.0 * acos(sin($lati_a) * sin($lati_b) + cos($lati_a) * cos($lati_b) * cos($ll));
0659: 
0660:     return $distance;
0661: }

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

解説:等角航路の軌跡

0718: /**
0719:  * 2地点間の等角航路軌跡を求める
0720:  * @param float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
0721:  * @param float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
0722:  * @param array  $points  軌跡の座標を格納
0723:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
0724:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
0725:  * @return float $distance 2地点間の等角航路距離を格納
0726:  * @return int 座標の数
0727:  *
0728: */
0729: function rhumbLine($long_a$lati_a$long_b$lati_b, &$points, &$distance) {
0730:     $lati_a = deg2rad($lati_a);
0731:     $long_a = deg2rad($long_a);
0732:     $lati_b = deg2rad($lati_b);
0733:     $long_b = deg2rad($long_b);
0734:     $n = 200;
0735: 
0736:     //経度方向の増分
0737:     $l1 = ($long_a >= 0) ? $long_a : 2 * pi() + $long_a;
0738:     $l2 = ($long_b >= 0) ? $long_b : 2 * pi() + $long_b;
0739:     $d1 = $l2 - $l1;
0740:     $t1 = 0.01;
0741:     $n = abs($d1) / $t1;
0742:     if ($d1 < 0) {
0743:         $d1 = 2 * pi() + $d1;
0744:         $t1 = -$t1;
0745:     } else if ($d1 > pi()) {
0746:         list($lati_a$lati_b) = array($lati_b$lati_a);
0747:         list($long_a$long_b) = array($long_b$long_a);
0748:         $d1 = 2 * pi() - $d1;
0749:         $n = abs($d1) / $t1;
0750:     }
0751: 
0752:     //緯度方向の増分
0753:     $l1 = $lati_a;
0754:     $l2 = $lati_b;
0755:     $d2 = $l2 - $l1;
0756:     if ($d2 >= pi()) {
0757:         $d2 -= pi();
0758:         $t2 = - $d2 / $n;
0759:     } else {
0760:         $t2 = $d2 / $n;              //緯度方向の増分
0761:     }
0762: 
0763:     //軌跡の計算
0764:     $latitude  = $lati_a;
0765:     $longitude = $long_a;
0766:     $distance  = 0.0;
0767:     for ($cnt = 0; $cnt < $n$cnt++) {
0768:         if ($latitude  >= 0.5 * pi())    $latitude -= pi();
0769:         if ($longitude >= 1.0 * pi())    $longitude = $longitude - 2 * pi();
0770:         $points[$cnt]['latitude']  = rad2deg($latitude);
0771:         $points[$cnt]['longitude'] = rad2deg($longitude);
0772:         $longitude += $t1;
0773:         $latitude  += $t2;
0774:         if ($cnt >= 1) {
0775:             $distance += $this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
0776:         }
0777:     }
0778:     $points[$cnt]['latitude']  = rad2deg($lati_b);
0779:     $points[$cnt]['longitude'] = rad2deg($long_b);
0780:     $distance += $this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
0781:     $cnt++;
0782: 
0783:     return $cnt;
0784: }

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

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

解説:初期値

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

解説:距離計算表

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

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

活用例

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

参考サイト

(この項おわり)
header