PHPで大圏航路を描く

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

(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 クラス

  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 = '*****************************';

地図サービスを利用するために、クラスファイル "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の登録方法」を参照されたい。

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

  34: //地図描画サービスの選択
  35: //    0:Google
  36: //    2:地理院地図・OSM
  37: define('MAPSERVICE', 0);
  38: 
  39: //住所検索サービスの選択
  40: //    0:Google
  41: //    1:Yahoo!JAPAN
  42: //   11:HeartRails Geo API
  43: //   12:OSM Nominatim Search API
  44: define('GEOSERVICE', 1);

表示する地図は、Googleマップ、地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVICE に値を設定すること。
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo APIから選択可能で、定数 GEOSERVICE に値を設定すること。

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

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

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

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

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

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

によって計算できる。

解説:大圏航路の軌跡

 708: /**
 709:  * 2地点間の大圏航路軌跡を求める
 710:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
 711:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
 712:  * @param   array  $points  軌跡の座標を格納
 713:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
 714:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
 715:  * @return  int 座標の数
 716:  *
 717: */
 718: function greatCircleSailing($long_a, $lati_a, $long_b, $lati_b, &$points) {
 719:     $lati_a = deg2rad($lati_a);
 720:     $long_a = deg2rad($long_a);
 721:     $lati_b = deg2rad($lati_b);
 722:     $long_b = deg2rad($long_b);
 723: 
 724:     $l1 = ($long_a >0? $long_a : 2 * pi() + $long_a;
 725:     $l2 = ($long_b >0? $long_b : 2 * pi() + $long_b;
 726:     $dd = $l2 - $l1;
 727:     $tt = 0.01;         //経度方向の増分
 728:     if ($dd < 0) {
 729:         $dd = abs($dd);
 730:         $tt = -$tt;
 731:     } else if ($dd > pi()) {
 732:         list($lati_a, $lati_b) = array($lati_b, $lati_a);
 733:         list($long_a, $long_b) = array($long_b, $long_a);
 734:         $dd = 2 * pi() - $dd;
 735:     }
 736:     $st = 0.0;
 737:     $cnt = 0;
 738: 
 739:     //軌跡の計算
 740:     $latitude  = $lati_a;
 741:     $longitude = $long_a;
 742:     while ($st < $dd) {
 743:         if ($latitude  >0.5 * pi())   $latitude -pi();
 744:         if ($longitude >1.0 * pi())   $longitude = $longitude - pi();
 745:         $points[$cnt]['latitude']  = rad2deg($latitude);
 746:         $points[$cnt]['longitude'] = rad2deg($longitude);
 747:         $longitude +$tt;
 748:         if ($longitude >pi())     $longitude = $longitude - 2 * pi();
 749:         $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));
 750:         $latitude = atan($latitude);
 751:         if (sin($lati_a) / sin($lati_b< 0$latitude +pi();
 752: 
 753:         $st +abs($tt);
 754:         $cnt++;
 755:     }
 756:     $points[$cnt]['latitude']  = rad2deg($lati_b);
 757:     $points[$cnt]['longitude'] = rad2deg($long_b);
 758:     $cnt++;
 759: 
 760:     return $cnt;
 761: }

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

なお、「PHPで弾道ミサイルの軌道を計算する」で紹介したように、Googleマップでは、メソッド Polyline のプロパティ geodesic を true にすることで、大圏航法による最短距離を近似描画できる。

解説:大圏航路の距離

 688: /**
 689:  * 2地点間の大圏航路距離を求める
 690:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
 691:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
 692:  * @return  float 大圏航路距離
 693:  *
 694: */
 695: function greatCircleDistance($long_a, $lati_a, $long_b, $lati_b) {
 696:     $lati_a = deg2rad($lati_a);
 697:     $long_a = deg2rad($long_a);
 698:     $lati_b = deg2rad($lati_b);
 699:     $long_b = deg2rad($long_b);
 700: 
 701:     //距離の計算
 702:     $ll = abs($long_b - $long_a);
 703:     $distance = 6371.0 * acos(sin($lati_a* sin($lati_b+ cos($lati_a* cos($lati_b* cos($ll));
 704: 
 705:     return $distance;
 706: }

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

解説:等角航路の軌跡

 763: /**
 764:  * 2地点間の等角航路軌跡を求める
 765:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
 766:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
 767:  * @param   array  $points  軌跡の座標を格納
 768:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
 769:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
 770:  * @return  float $distance 2地点間の等角航路距離を格納
 771:  * @return  int 座標の数
 772:  *
 773: */
 774: function rhumbLine($long_a, $lati_a, $long_b, $lati_b, &$points, &$distance) {
 775:     $lati_a = deg2rad($lati_a);
 776:     $long_a = deg2rad($long_a);
 777:     $lati_b = deg2rad($lati_b);
 778:     $long_b = deg2rad($long_b);
 779:     $n = 200;
 780: 
 781:     //経度方向の増分
 782:     $l1 = ($long_a >0? $long_a : 2 * pi() + $long_a;
 783:     $l2 = ($long_b >0? $long_b : 2 * pi() + $long_b;
 784:     $d1 = $l2 - $l1;
 785:     $t1 = 0.01;
 786:     $n = abs($d1) / $t1;
 787:     if ($d1 < 0) {
 788:         $d1 = 2 * pi() + $d1;
 789:         $t1 = -$t1;
 790:     } else if ($d1 > pi()) {
 791:         list($lati_a, $lati_b) = array($lati_b, $lati_a);
 792:         list($long_a, $long_b) = array($long_b, $long_a);
 793:         $d1 = 2 * pi() - $d1;
 794:         $n = abs($d1) / $t1;
 795:     }
 796: 
 797:     //緯度方向の増分
 798:     $l1 = $lati_a;
 799:     $l2 = $lati_b;
 800:     $d2 = $l2 - $l1;
 801:     if ($d2 >pi()) {
 802:         $d2 -pi();
 803:         $t2 = - $d2 / $n;
 804:     } else {
 805:         $t2 = $d2 / $n;             //緯度方向の増分
 806:     }
 807: 
 808:     //軌跡の計算
 809:     $latitude  = $lati_a;
 810:     $longitude = $long_a;
 811:     $distance  = 0.0;
 812:     for ($cnt = 0$cnt < $n$cnt++) {
 813:         if ($latitude  >0.5 * pi())   $latitude -pi();
 814:         if ($longitude >1.0 * pi())   $longitude = $longitude - 2 * pi();
 815:         $points[$cnt]['latitude']  = rad2deg($latitude);
 816:         $points[$cnt]['longitude'] = rad2deg($longitude);
 817:         $longitude +$t1;
 818:         $latitude  +$t2;
 819:         if ($cnt >1) {
 820:             $distance +$this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
 821:         }
 822:     }
 823:     $points[$cnt]['latitude']  = rad2deg($lati_b);
 824:     $points[$cnt]['longitude'] = rad2deg($long_b);
 825:     $distance +$this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
 826:     $cnt++;
 827: 
 828:     return $cnt;
 829: }

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

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

解説:初期値

  46: //マップの表示サイズ(単位:ピクセル)
  47: define('MAP_WIDTH',  600);
  48: define('MAP_HEIGHT', 400);
  49: //マップID
  50: define('MAPID', 'map_id');
  51: //初期値
  52: define('DEF_LONGITUDE0', 139.766667);       //地図中心(経度)
  53: define('DEF_LATITUDE0',  35.681111);        //    (緯度)
  54: define('DEF_FROM',      '東京駅');          //from(クエリ)
  55: define('DEF_TO',        'ワシントン');      //to (クエリ)
  56: define('DEF_CAT_FROM',  'landmark');        //from(カテゴリ)
  57: define('DEF_CAT_TO',    'world');           //to (カテゴリ)
  58: define('DEF_LONGITUDE1', 139.766667);       //from(経度)
  59: define('DEF_LATITUDE1',  35.681111);        //  (緯度)
  60: define('DEF_LONGITUDE2', -77.0329165);      //to (経度)
  61: define('DEF_LATITUDE2',  38.8878665);       //    (緯度)
  62: define('DEF_TYPE',      'roadmap');         //マップタイプ
  63: define('DEF_ZOOM',      2);                 //ズーム

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

 193: /**
 194:  * マーカー設定、フィット機能:Googleマップ用スクリプト
 195:  * @param   float $latitude, $longitude 経路の中央点座標(緯度・経度)
 196:  * @param   bool $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
 197:  * @return  string JavaScript
 198: */
 199: function jsDragMarker_Gmap($latitude, $longitude, $drag=FALSE) {
 200:     $submit = $drag ? 'document.myform.submit();' : '';
 201: 
 202:     $js =<<< EOT
 203:     //イベント設定
 204:     document.getElementById('fit').addEventListener('click', fitting, false);
 205: 
 206:     //マーカー設定
 207:     marker_A.setDraggable(true);
 208:     marker_A.addListener('dragend', function() {
 209:         marker_A.setIcon(icon_A);
 210:         marker_A.setDraggable(true);
 211:         latlng = marker_A.getPosition();
 212:         document.getElementById('longitude1').value = latlng.lng();
 213:         document.getElementById('latitude1').value  = latlng.lat();
 214:         document.getElementById('from').value = '';
 215:         {$submit}
 216:     });
 217: 
 218:     marker_B.setDraggable(true);
 219:     marker_B.addListener('dragend', function() {
 220:         marker_B.setIcon(icon_B);
 221:         marker_B.setDraggable(true);
 222:         latlng = marker_B.getPosition();
 223:         document.getElementById('longitude2').value = latlng.lng();
 224:         document.getElementById('latitude2').value  = latlng.lat();
 225:         document.getElementById('to').value = '';
 226:         {$submit}
 227:     });
 228: 
 229:     /**
 230:      * 地図を最適サイズにフィットする
 231:      * @param   なし
 232:      * @return  なし
 233:     */
 234:     function fitting() {
 235:         var lat0, lng0, lat1, lng1;
 236:         var lat = Array();
 237:         var lng = Array();
 238:         lat[0] = document.getElementById('latitude1').value;
 239:         lng[0] = document.getElementById('longitude1').value;
 240:         lat[1] = document.getElementById('latitude2').value;
 241:         lng[1] = document.getElementById('longitude2').value;
 242:         lat[2] = {$latitude};
 243:         lng[2] = {$longitude};
 244: 
 245:         //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
 246:         if (lat[0] <= lat[1]) {
 247:             lat0 = lat[0];
 248:             lat1 = lat[1];
 249:         } else {
 250:             lat0 = lat[1];
 251:             lat1 = lat[0];
 252:         }
 253:         if (lat[2] >= 0) {
 254:             if (lat[0] <= lat[2]) {
 255:                 lng0 = lng[0];
 256:                 lng1 = lng[1];
 257:             } else {
 258:                 lng0 = lng[1];
 259:                 lng1 = lng[0];
 260:             }
 261:         } else {
 262:             if (lat[0] <= lat[2]) {
 263:                 lng0 = lng[1];
 264:                 lng1 = lng[0];
 265:             } else {
 266:                 lng0 = lng[0];
 267:                 lng1 = lng[1];
 268:             }
 269:         }
 270: 
 271:         var bounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat0, lng0), new google.maps.LatLng(lat1, lng1));
 272:         map.fitBounds(bounds);                      //地図表示の最適化
 273:     }
 274: 
 275: EOT;
 276:     return $js;
 277: }

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

 429: /**
 430:  * マーカー設定、フィット機能スクリプト:Leafletマップ用
 431:  * @param   float $latitude, $longitude 経路の中央点座標(緯度・経度)
 432:  * @param   bool  $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
 433:  * @return  string JavaScript
 434: */
 435: function jsDragMarker_Leaflet($latitude, $longitude, $drag=FALSE) {
 436:     //Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
 437:     if ($longitude < 0)      $longitude = 360 + $longitude;
 438:     $submit = $drag ? 'document.myform.submit();' : '';
 439: 
 440:     $js =<<< EOT
 441:     //イベント設定
 442:     document.getElementById('fit').addEventListener('click', fitting, false);
 443: 
 444:     //マーカー設定
 445:     marker_A.dragging.enable();
 446:     marker_A.on('dragend', function(event) {
 447:         var marker = event.target;
 448:         var position = marker_A.getLatLng();
 449:         marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:true});
 450:         map.panTo(new L.LatLng(position.lat, position.lng))
 451:         document.getElementById('longitude1').value = position.lng;
 452:         document.getElementById('latitude1').value  = position.lat;
 453:         document.getElementById('from').value = '';
 454:         {$submit}
 455:     });
 456: 
 457:     marker_B.dragging.enable();
 458:     marker_B.on('dragend', function(event) {
 459:         var marker = event.target;
 460:         var position = marker.getLatLng();
 461:         marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:true});
 462:         map.panTo(new L.LatLng(position.lat, position.lng))
 463:         document.getElementById('longitude2').value = position.lng;
 464:         document.getElementById('latitude2').value  = position.lat;
 465:         document.getElementById('to').value = '';
 466:         {$submit}
 467:     });
 468: 
 469:     /**
 470:      * 地図を最適サイズにフィットする
 471:      * @param   なし
 472:      * @return  なし
 473:     */
 474:     function fitting() {
 475:         var lat0, lng0, lat1, lng1;
 476:         var lat = Array();
 477:         var lng = Array();
 478:         lat[0] = document.getElementById('latitude1').value;
 479:         lng[0] = document.getElementById('longitude1').value;
 480:         lat[1] = document.getElementById('latitude2').value;
 481:         lng[1] = document.getElementById('longitude2').value;
 482:         lat[2] = {$latitude};
 483:         lng[2] = {$longitude};
 484: 
 485:         //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
 486:         if (lat[0] <= lat[1]) {
 487:             lat0 = lat[0];
 488:             lat1 = lat[1];
 489:         } else {
 490:             lat0 = lat[1];
 491:             lat1 = lat[0];
 492:         }
 493:         if (lat[2] >= 0) {
 494:             if (lat[0] <= lat[2]) {
 495:                 lng0 = lng[0];
 496:                 lng1 = lng[1];
 497:             } else {
 498:                 lng0 = lng[1];
 499:                 lng1 = lng[0];
 500:             }
 501:         } else {
 502:             if (lat[0] <= lat[2]) {
 503:                 lng0 = lng[1];
 504:                 lng1 = lng[0];
 505:             } else {
 506:                 lng0 = lng[0];
 507:                 lng1 = lng[1];
 508:             }
 509:         }
 510: 
 511:         //Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
 512:         //bug:Leafletマップではfitが効かない
 513:         if (lng0 < 0)   lng0 = 360 + lng0;
 514:         if (lng1 < 0)   lng1 = 360 + lng1;
 515:         var bounds = L.latLngBounds([lat0, lng0], [lat1, lng1])
 516:         map.fitBounds(bounds);      //地図表示の最適化
 517:     }
 518: EOT;
 519:     return $js;
 520: }

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

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

 279: /**
 280:  * 航路描画スクリプト:Googleマップ用スクリプト
 281:  * @param   int    $id     識別ID
 282:  * @param   array  $items  航路の座標
 283:  * @param   string $color  描画色(RGB)
 284:  * @param   int    $weight 線の太さ(省略時=1)
 285:  * @return  string JavaScript
 286: */
 287: function jsDrawLine_Gmap($id, $items, $color, $weight=1) {
 288:     $js =<<< EOT
 289:     var pline{$id} = new google.maps.Polyline({
 290:         map: map,
 291:         path:[
 292: 
 293: EOT;
 294:     foreach ($items as $item) {
 295:         $js .=<<< EOT
 296:         new google.maps.LatLng({$item['latitude']}, {$item['longitude']}),
 297: 
 298: EOT;
 299:     }
 300:     $js .=<<< EOT
 301:     ],
 302:     strokeColor: "{$color}",
 303:     strokeOpacity: 1,
 304:     strokeWeight: {$weight},
 305:     zIndex: 1
 306:     });
 307: 
 308: EOT;
 309:     return $js;
 310: }

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

 522: /**
 523:  * 航路描画スクリプト:Leafletマップ用
 524:  * @param   int    $id     識別ID
 525:  * @param   array  $items  航路の座標
 526:  * @param   string $color  描画色(RGB)
 527:  * @param   int    $weight 線の太さ(省略時=1)
 528:  * @return  string JavaScript
 529: */
 530: function jsDrawLine_Leaflet($id, $items, $color, $weight=1) {
 531:     $cnt = 0;
 532:     $js =<<< EOT
 533:     var pline{$id}_{$cnt} = L.polyline([
 534: 
 535: EOT;
 536:     foreach ($items as $key=>$item) {
 537:         if ($key > 0) {
 538:             //Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
 539:             if ((($items[$key - 1]['longitude'> 0&& ($items[$key]['longitude'< 0)) || (($items[$key - 1]['longitude'< 0&& ($items[$key]['longitude'> 0))) {
 540:                 $cnt++;
 541:                 $js .=<<< EOT
 542:     ], {
 543:     "color": "#{$color}",
 544:     "opacity": 1.0,
 545:     "weight": {$weight},
 546: }).addTo(map);
 547:     var pline{$id}_{$cnt} = L.polyline([
 548: 
 549: EOT;
 550:             }
 551:         }
 552:         $long = ($item['longitude'< 0? 360 + $item['longitude': $item['longitude'];
 553:         $js .=<<< EOT
 554:         [{$item['latitude']}, {$long}],
 555: 
 556: EOT;
 557:     }
 558:     $js .=<<< EOT
 559:     ], {
 560:     "color": "#{$color}",
 561:     "opacity": 1.0,
 562:     "weight": {$weight},
 563: }).addTo(map);
 564: 
 565: EOT;
 566:     return $js;
 567: }

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

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

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

解説:距離計算表

 570: /**
 571:  * 距離計算表を作成
 572:  * @param   array  $items 地点・距離情報
 573:  * @param   object $pgc   pahooGeoCodeクラス
 574:  * @return  string 距離計算表(HTML)
 575: */
 576: function makeDistanceTable($items, $pgc) {
 577:     $d11 = sprintf('%.1f', round($items['dist1'], 1));
 578:     $d12 = sprintf('%.1f', round($pgc->km2mi($items['dist1']), 1));
 579:     $d13 = sprintf('%.1f', round($pgc->km2nm($items['dist1']), 1));
 580:     $d21 = sprintf('%.1f', round($items['dist2'], 1));
 581:     $d22 = sprintf('%.1f', round($pgc->km2mi($items['dist2']), 1));
 582:     $d23 = sprintf('%.1f', round($pgc->km2nm($items['dist2']), 1));
 583:     $ww  = MAP_WIDTH / 4;
 584: 
 585:     $html =<<< EOT
 586: <table class="plists">
 587: <caption>距離計算</caption>
 588: <tr>
 589: <th style="width:{$ww}px;">&nbsp;</th>
 590: <th style="width:{$ww}px;">キロメートル</th>
 591: <th style="width:{$ww}px;">マイル</th>
 592: <th style="width:{$ww}px;">海里</th>
 593: </tr>
 594: <tr>
 595: <th>大圏航路</th>
 596: <td>{$d11}</td>
 597: <td>{$d12}</td>
 598: <td>{$d13}</td>
 599: </tr>
 600: <tr>
 601: <th>等角航路</th>
 602: <td>{$d21}</td>
 603: <td>{$d22}</td>
 604: <td>{$d23}</td>
 605: </tr>
 606: </table>
 607: 
 608: EOT;
 609:     return $html;
 610: }

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

活用例

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

参考サイト

(この項おわり)
header