PHPで大圏航路を描く

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

(2025年9月19日)マーカードラッグ後に再描画するとマーカーの位置が戻ってしまう不具合を解消.
(2025年8月30日).pahooEnv導入
(2025年7月20日)GoogleMaps JavaScript APIのAdvancedMarkerに対応
(2025年6月14日)GoogleMaps JavaScript APIの変更に対応した.

目次

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

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

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

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

圧縮ファイルの内容
greatCircleSailing.phpサンプル・プログラム本体
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」などを参照。include_path が通ったディレクトリに配置すること。
greatCircleSailing.php 更新履歴
バージョン 更新日 内容
1.6.1 2025/09/19 マーカードラッグ後に再描画で位置ズレを解消
1.6.0 2025/08/30 .pahooEnv導入
1.5.0 2025/07/20 GoogleMapsAPIのAdvancedMarkerに対応
1.4 2021/02/20 PHP8対応,Yahoo! JavaScriptマップ廃止
1.3 2020/03/22 OSM Nominatim Search API追加
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.8.0 2025/08/10 アクセスキーなどを ".pahooEnd" に分離
6.7.1 2025/07/26 jsLine_Gmap() - bug-fix
6.7.0 2025/07/20 drawJSmap,drawGMap -- 引数 $markerLevel 追加
6.6.0 2025/07/19 drawJSmap,drawGMap,drawLeaflet -- マップ中心マーカー表示引数を追加
6.5.0 2025/06/14 GoogleMaps JavaScript APIの変更に対応
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加

準備:PHP の https対応

クラウド連携や相手先サイトのデータを読み込むのに https通信を使うため、PHPに OpenSSLモジュールが組み込まれている必要がある。関数  phpinfo  を使って、下図のように表示されればOKだ。
OpenSSL - PHP
そうでない場合は、次の手順に従ってOpenSSLを有効化し、PHPを再起動させる必要がある。

Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dll
Linuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。

準備:pahooGeoCode クラス

pahooGeoCode.php

  40: class pahooGeoCode {
  41:     public $items;      // 検索結果格納用
  42:     public $error;      // エラー・フラグ
  43:     public $errmsg;     // エラー・メッセージ
  44:     public $hits;       // 検索ヒット件数
  45:     public $webapi// 直前に呼び出したWebAPI URL
  46: 
  47:     // -- 以下のデータは .env ファイルに記述可能
  48:     // Google Cloud Platform APIキー
  49:     // https://cloud.google.com/maps-platform/
  50:     // ※Google Maps APIを利用しないのなら登録不要
  51:     public $GOOGLE_API_KEY_1 = '';      // HTTPリファラ用
  52:     public $GOOGLE_API_KEY_2 = '';      // IP制限用
  53:     public $GOOGLE_MAP_ID    = '';      // GoogleMaps ID
  54: 
  55:     // Yahoo! JAPAN Webサービス アプリケーションID
  56:     // https://e.developer.yahoo.co.jp/register
  57:     // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  58:     public $YAHOO_APPLICATION_ID = '';
  59: 
  60:     // OSM Nominatim Search API利用時に知らせるメールアドレス
  61:     // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
  62:     // ※OSM Nominatim Search APIを利用しないのなら登録不要
  63:     public $NOMINATIM_EMAIL = '';
  64: 
  65:     // IP2Location.io APIキー
  66:     // https://www.ip2location.io/
  67:     // ※IP2Location.ioを利用しないのなら登録不要
  68:     public $IP2LOCATION_API_KEY = '';

GoogleマップやLeafletなどによる地図描画や住所検索を行うためのクラスが pahooGeoCode である。同梱のクラス・ファイル "pahooGeoCode.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooGeoCodeクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

地図や住所検索として Google を利用するのであれば Google Cloud Platform APIキーマップID が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、IP2Location.ioを利用するのであれば「PHPでIPアドレスやホスト名から住所を求める」を、それぞれ参照されたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

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

greatCircleSailing.php

  54: // 地図描画サービスの選択
  55: //    0:Google
  56: //    2:地理院地図・OSM
  57: define('MAPSERVICE', 2);
  58: 
  59: // 住所検索サービスの選択
  60: //    0:Google
  61: //    1:Yahoo!JAPAN
  62: //   11:HeartRails Geo API
  63: //   12:OSM Nominatim Search API
  64: 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)の大圏航路の軌跡は
$$ \displaystyle tan(\delta) = \frac{sin(\delta_1) sin(\phi_2 - \phi)}{cos(\delta_1) sin(\phi_2 - \phi_1)} + \frac{sin(\delta_2) sin(\phi_1 - \phi)}{cos(\delta_2) sin(\phi_1 - \phi_2)} $$
によって計算できる。
また、地球の半径をRとすると、2地点間の大圏距離は
$$ \displaystyle R cos^{-1}(sin(\phi_1) sin(\delta_2) + cos(\phi_1) cos(\phi_2) cos(abs(\delta_2 - \delta_1)) $$
によって計算できる。

解説:大圏航路の軌跡

pahooGeoCode.php

 734: /**
 735:  * 2地点間の大圏航路軌跡を求める
 736:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
 737:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
 738:  * @param   array  $points  軌跡の座標を格納
 739:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
 740:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
 741:  * @return  int 座標の数
 742:  *
 743: */
 744: function greatCircleSailing($long_a, $lati_a, $long_b, $lati_b, &$points) {
 745:     $lati_a = deg2rad($lati_a);
 746:     $long_a = deg2rad($long_a);
 747:     $lati_b = deg2rad($lati_b);
 748:     $long_b = deg2rad($long_b);
 749: 
 750:     $l1 = ($long_a >0? $long_a : 2 * pi() + $long_a;
 751:     $l2 = ($long_b >0? $long_b : 2 * pi() + $long_b;
 752:     $dd = $l2 - $l1;
 753:     $tt = 0.01;         // 経度方向の増分
 754:     if ($dd < 0) {
 755:         $dd = abs($dd);
 756:         $tt = -$tt;
 757:     } else if ($dd > pi()) {
 758:         list($lati_a, $lati_b) = array($lati_b, $lati_a);
 759:         list($long_a, $long_b) = array($long_b, $long_a);
 760:         $dd = 2 * pi() - $dd;
 761:     }
 762:     $st = 0.0;
 763:     $cnt = 0;
 764: 
 765:     // 軌跡の計算
 766:     $latitude  = $lati_a;
 767:     $longitude = $long_a;
 768:     while ($st < $dd) {
 769:         if ($latitude  >0.5 * pi())   $latitude -pi();
 770:         if ($longitude >1.0 * pi())   $longitude = $longitude - pi();
 771:         $points[$cnt]['latitude']  = rad2deg($latitude);
 772:         $points[$cnt]['longitude'] = rad2deg($longitude);
 773:         $longitude +$tt;
 774:         if ($longitude >pi())     $longitude = $longitude - 2 * pi();
 775:         $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));
 776:         $latitude = atan($latitude);
 777:         if (sin($lati_a) / sin($lati_b< 0$latitude +pi();
 778: 
 779:         $st +abs($tt);
 780:         $cnt++;
 781:     }
 782:     $points[$cnt]['latitude']  = rad2deg($lati_b);
 783:     $points[$cnt]['longitude'] = rad2deg($long_b);
 784:     $cnt++;
 785: 
 786:     return $cnt;
 787: }

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

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

解説:大圏航路の距離

pahooGeoCode.php

 714: /**
 715:  * 2地点間の大圏航路距離を求める
 716:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
 717:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
 718:  * @return  float 大圏航路距離
 719:  *
 720: */
 721: function greatCircleDistance($long_a, $lati_a, $long_b, $lati_b) {
 722:     $lati_a = deg2rad($lati_a);
 723:     $long_a = deg2rad($long_a);
 724:     $lati_b = deg2rad($lati_b);
 725:     $long_b = deg2rad($long_b);
 726: 
 727:     // 距離の計算
 728:     $ll = abs($long_b - $long_a);
 729:     $distance = 6371.0 * acos(sin($lati_a* sin($lati_b+ cos($lati_a* cos($lati_b* cos($ll));
 730: 
 731:     return $distance;
 732: }

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

解説:等角航路の軌跡

pahooGeoCode.php

 789: /**
 790:  * 2地点間の等角航路軌跡を求める
 791:  * @param   float $long_a, $lati_a  A地点の経度,緯度(世界測地系)
 792:  * @param   float $long_b, $lati_b  B地点の経度,緯度(世界測地系)
 793:  * @param   array  $points  軌跡の座標を格納
 794:  *                      [$n]['longitude'] 軌跡の経度(世界測地系)
 795:  *                      [$n]['latitude']  軌跡の緯度(世界測地系)
 796:  * @return  float $distance 2地点間の等角航路距離を格納
 797:  * @return  int 座標の数
 798:  *
 799: */
 800: function rhumbLine($long_a, $lati_a, $long_b, $lati_b, &$points, &$distance) {
 801:     $lati_a = deg2rad($lati_a);
 802:     $long_a = deg2rad($long_a);
 803:     $lati_b = deg2rad($lati_b);
 804:     $long_b = deg2rad($long_b);
 805:     $n = 200;
 806: 
 807:     // 経度方向の増分
 808:     $l1 = ($long_a >0? $long_a : 2 * pi() + $long_a;
 809:     $l2 = ($long_b >0? $long_b : 2 * pi() + $long_b;
 810:     $d1 = $l2 - $l1;
 811:     $t1 = 0.01;
 812:     $n = abs($d1) / $t1;
 813:     if ($d1 < 0) {
 814:         $d1 = 2 * pi() + $d1;
 815:         $t1 = -$t1;
 816:     } else if ($d1 > pi()) {
 817:         list($lati_a, $lati_b) = array($lati_b, $lati_a);
 818:         list($long_a, $long_b) = array($long_b, $long_a);
 819:         $d1 = 2 * pi() - $d1;
 820:         $n = abs($d1) / $t1;
 821:     }
 822: 
 823:     // 緯度方向の増分
 824:     $l1 = $lati_a;
 825:     $l2 = $lati_b;
 826:     $d2 = $l2 - $l1;
 827:     if ($d2 >pi()) {
 828:         $d2 -pi();
 829:         $t2 = - $d2 / $n;
 830:     } else {
 831:         $t2 = $d2 / $n;             // 緯度方向の増分
 832:     }
 833: 
 834:     // 軌跡の計算
 835:     $latitude  = $lati_a;
 836:     $longitude = $long_a;
 837:     $distance  = 0.0;
 838:     for ($cnt = 0$cnt < $n$cnt++) {
 839:         if ($latitude  >0.5 * pi())   $latitude -pi();
 840:         if ($longitude >1.0 * pi())   $longitude = $longitude - 2 * pi();
 841:         $points[$cnt]['latitude']  = rad2deg($latitude);
 842:         $points[$cnt]['longitude'] = rad2deg($longitude);
 843:         $longitude +$t1;
 844:         $latitude  +$t2;
 845:         if ($cnt >1) {
 846:             $distance +$this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
 847:         }
 848:     }
 849:     $points[$cnt]['latitude']  = rad2deg($lati_b);
 850:     $points[$cnt]['longitude'] = rad2deg($long_b);
 851:     $distance +$this->greatCircleDistance($points[$cnt - 1]['longitude'], $points[$cnt - 1]['latitude'], $points[$cnt]['longitude'], $points[$cnt]['latitude']);
 852:     $cnt++;
 853: 
 854:     return $cnt;
 855: }

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

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

解説:初期値

greatCircleSailing.php

  66: // マップの表示サイズ(単位:ピクセル)
  67: define('MAP_WIDTH',  600);
  68: define('MAP_HEIGHT', 400);
  69: // マップID
  70: define('MAPID', 'map_id');
  71: // 初期値
  72: define('DEF_LONGITUDE0', 139.766667);       // 地図中心(経度)
  73: define('DEF_LATITUDE0',  35.681111);        //    (緯度)
  74: define('DEF_FROM',      '東京駅');          // from(クエリ)
  75: define('DEF_TO',        'ワシントン');      // to (クエリ)
  76: define('DEF_CAT_FROM',  'landmark');        // from(カテゴリ)
  77: define('DEF_CAT_TO',    'world');           // to (カテゴリ)
  78: define('DEF_LONGITUDE1', 139.766667);       // from(経度)
  79: define('DEF_LATITUDE1',  35.681111);        //  (緯度)
  80: define('DEF_LONGITUDE2', -77.0329165);      // to (経度)
  81: define('DEF_LATITUDE2',  38.8878665);       //    (緯度)
  82: define('DEF_TYPE',      'roadmap');         // マップタイプ
  83: define('DEF_ZOOM',      2);                 // ズーム

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

解説:Googleマップ上でマーカーをドラッグする

greatCircleSailing.php

 171: /**
 172:  * マーカー設定、フィット機能:Googleマップ用スクリプト
 173:  * @param   float $latitude, $longitude 経路の中央点座標(緯度・経度)
 174:  * @param   bool $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
 175:  * @return  string JavaScript
 176: */
 177: function jsDragMarker_Gmap($latitude, $longitude, $drag=FALSE) {
 178:     $submit = $drag ? 'document.myform.submit();' : '';
 179: 
 180:     $js =<<< EOT
 181:     // イベント設定
 182:     document.getElementById('fit').addEventListener('click', fitting, false);
 183: 
 184:     // マーカー設定
 185:     marker_001.setDraggable(true);
 186:     google.maps.event.addListener(marker_001, 'dragend', function() {
 187:         let latlng = marker_001.getPosition();
 188:         document.getElementById('longitude1').value = latlng.lng();
 189:         document.getElementById('latitude1').value  = latlng.lat();
 190:         document.getElementById('from').value = '';
 191:         {$submit}
 192:     });
 193: 
 194:     marker_002.setDraggable(true);
 195:     google.maps.event.addListener(marker_002, 'dragend', function() {
 196:         let latlng = marker_002.getPosition();
 197:         document.getElementById('longitude2').value = latlng.lng();
 198:         document.getElementById('latitude2').value  = latlng.lat();
 199:         document.getElementById('to').value = '';
 200:         {$submit}
 201:     });
 202: 
 203:     /**
 204:      * 地図を最適サイズにフィットする
 205:      * @param   なし
 206:      * @return  なし
 207:     */
 208:     function fitting() {
 209:         var lat0, lng0, lat1, lng1;
 210:         var lat = Array();
 211:         var lng = Array();
 212:         lat[0] = document.getElementById('latitude1').value;
 213:         lng[0] = document.getElementById('longitude1').value;
 214:         lat[1] = document.getElementById('latitude2').value;
 215:         lng[1] = document.getElementById('longitude2').value;
 216:         lat[2] = {$latitude};
 217:         lng[2] = {$longitude};
 218: 
 219:         // 南西端(lat0, lng0), 北東端(lat1, lng1)を求める
 220:         if (lat[0] <= lat[1]) {
 221:             lat0 = lat[0];
 222:             lat1 = lat[1];
 223:         } else {
 224:             lat0 = lat[1];
 225:             lat1 = lat[0];
 226:         }
 227:         if (lat[2] >= 0) {
 228:             if (lat[0] <= lat[2]) {
 229:                 lng0 = lng[0];
 230:                 lng1 = lng[1];
 231:             } else {
 232:                 lng0 = lng[1];
 233:                 lng1 = lng[0];
 234:             }
 235:         } else {
 236:             if (lat[0] <= lat[2]) {
 237:                 lng0 = lng[1];
 238:                 lng1 = lng[0];
 239:             } else {
 240:                 lng0 = lng[0];
 241:                 lng1 = lng[1];
 242:             }
 243:         }
 244: 
 245:         var bounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat0, lng0), new google.maps.LatLng(lat1, lng1));
 246:         map.fitBounds(bounds);                      // 地図表示の最適化
 247:     }
 248: 
 249: EOT;
 250:     return $js;
 251: }

ユーザー関数 jsDragMarker_Gmap は、Googleマップ上のマーカーをドラッグし、別の地点に移動するJavaScriptを生成するものだ。

クラス pahooGeoCode では、さまざまな情報を付加しやすいように、GoogleMaps JavaScript API の AdvancedMarker オブジェクトを利用している。しかし、2025年(令和7年)7月現在、AdvancedMarker はドラッグすることができない。そこで、ドラッグ可能で旧来用いていた Marker オブジェクトを利用することにした。
メソッド drawJSmap もしくは drawGMap の引数 $markerLevelAdvancedMarkerMarker かを選ぶことができるようにした。省略時は AdvancedMarker になるようにしてある。

解説:Leafletマップ上でマーカーをドラッグする

greatCircleSailing.php

 287: /**
 288:  * マーカー設定、フィット機能スクリプト:Leafletマップ用
 289:  * @param   float $latitude, $longitude 経路の中央点座標(緯度・経度)
 290:  * @param   bool  $drag TRUE:ドラッグで再描画する/FALSE:しない(デフォルト)
 291:  * @return  string JavaScript
 292: */
 293: function jsDragMarker_Leaflet($latitude, $longitude, $drag=FALSE) {
 294:     // Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
 295:     if ($longitude < 0)      $longitude = 360 + $longitude;
 296:     $submit = $drag ? 'document.myform.submit();' : '';
 297: 
 298:     $js =<<< EOT
 299:     // イベント設定
 300:     document.getElementById('fit').addEventListener('click', fitting, false);
 301: 
 302:     // マーカー設定
 303:     marker_001.dragging.enable();
 304:     marker_001.on('dragend', function(event) {
 305:         var marker = event.target;
 306:         var position = marker_001.getLatLng();
 307:         marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:true});
 308:         map.panTo(new L.LatLng(position.lat, position.lng))
 309:         document.getElementById('longitude1').value = position.lng;
 310:         document.getElementById('latitude1').value  = position.lat;
 311:         document.getElementById('from').value = '';
 312:         {$submit}
 313:     });
 314: 
 315:     marker_002.dragging.enable();
 316:     marker_002.on('dragend', function(event) {
 317:         var marker = event.target;
 318:         var position = marker.getLatLng();
 319:         marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:true});
 320:         map.panTo(new L.LatLng(position.lat, position.lng))
 321:         document.getElementById('longitude2').value = position.lng;
 322:         document.getElementById('latitude2').value  = position.lat;
 323:         document.getElementById('to').value = '';
 324:         {$submit}
 325:     });
 326: 
 327:     /**
 328:      * 地図を最適サイズにフィットする
 329:      * @param   なし
 330:      * @return  なし
 331:     */
 332:     function fitting() {
 333:         var lat0, lng0, lat1, lng1;
 334:         var lat = Array();
 335:         var lng = Array();
 336:         lat[0] = document.getElementById('latitude1').value;
 337:         lng[0] = document.getElementById('longitude1').value;
 338:         lat[1] = document.getElementById('latitude2').value;
 339:         lng[1] = document.getElementById('longitude2').value;
 340:         lat[2] = {$latitude};
 341:         lng[2] = {$longitude};
 342: 
 343:         // 南西端(lat0, lng0), 北東端(lat1, lng1)を求める
 344:         if (lat[0] <= lat[1]) {
 345:             lat0 = lat[0];
 346:             lat1 = lat[1];
 347:         } else {
 348:             lat0 = lat[1];
 349:             lat1 = lat[0];
 350:         }
 351:         if (lat[2] >= 0) {
 352:             if (lat[0] <= lat[2]) {
 353:                 lng0 = lng[0];
 354:                 lng1 = lng[1];
 355:             } else {
 356:                 lng0 = lng[1];
 357:                 lng1 = lng[0];
 358:             }
 359:         } else {
 360:             if (lat[0] <= lat[2]) {
 361:                 lng0 = lng[1];
 362:                 lng1 = lng[0];
 363:             } else {
 364:                 lng0 = lng[0];
 365:                 lng1 = lng[1];
 366:             }
 367:         }
 368: 
 369:         // Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
 370:         // bug:Leafletマップではfitが効かない
 371:         if (lng0 < 0)   lng0 = 360 + lng0;
 372:         if (lng1 < 0)   lng1 = 360 + lng1;
 373:         var bounds = L.latLngBounds([lat0, lng0], [lat1, lng1])
 374:         map.fitBounds(bounds);      // 地図表示の最適化
 375:     }
 376: EOT;
 377:     return $js;
 378: }

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

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

greatCircleSailing.php

 253: /**
 254:  * 航路描画スクリプト:Googleマップ用スクリプト
 255:  * @param   int    $id     識別ID
 256:  * @param   array  $items  航路の座標
 257:  * @param   string $color  描画色(RGB)
 258:  * @param   int    $weight 線の太さ(省略時=1)
 259:  * @return  string JavaScript
 260: */
 261: function jsDrawLine_Gmap($id, $items, $color, $weight=1) {
 262:     $js =<<< EOT
 263:     var pline{$id} = new google.maps.Polyline({
 264:         map: map,
 265:         path:[
 266: 
 267: EOT;
 268:     foreach ($items as $item) {
 269:         $js .=<<< EOT
 270:         new google.maps.LatLng({$item['latitude']}, {$item['longitude']}),
 271: 
 272: EOT;
 273:     }
 274:     $js .=<<< EOT
 275:     ],
 276:     strokeColor: "{$color}",
 277:     strokeOpacity: 1,
 278:     strokeWeight: {$weight},
 279:     zIndex: 1
 280:     });
 281: 
 282: EOT;
 283:     return $js;
 284: }

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

greatCircleSailing.php

 380: /**
 381:  * 航路描画スクリプト:Leafletマップ用
 382:  * @param   int    $id     識別ID
 383:  * @param   array  $items  航路の座標
 384:  * @param   string $color  描画色(RGB)
 385:  * @param   int    $weight 線の太さ(省略時=1)
 386:  * @return  string JavaScript
 387: */
 388: function jsDrawLine_Leaflet($id, $items, $color, $weight=1) {
 389:     $cnt = 0;
 390:     $js =<<< EOT
 391:     var pline{$id}_{$cnt} = L.polyline([
 392: 
 393: EOT;
 394:     foreach ($items as $key=>$item) {
 395:         if ($key > 0) {
 396:             // Leafletマップ:東経・西経の境目で線分が折り返さないよう調整
 397:             if ((($items[$key - 1]['longitude'> 0&& ($items[$key]['longitude'< 0)) || (($items[$key - 1]['longitude'< 0&& ($items[$key]['longitude'> 0))) {
 398:                 $cnt++;
 399:                 $js .=<<< EOT
 400:     ], {
 401:     "color": "#{$color}",
 402:     "opacity": 1.0,
 403:     "weight": {$weight},
 404: }).addTo(map);
 405:     var pline{$id}_{$cnt} = L.polyline([
 406: 
 407: EOT;
 408:             }
 409:         }
 410:         $long = ($item['longitude'< 0? 360 + $item['longitude': $item['longitude'];
 411:         $js .=<<< EOT
 412:         [{$item['latitude']}, {$long}],
 413: 
 414: EOT;
 415:     }
 416:     $js .=<<< EOT
 417:     ], {
 418:     "color": "#{$color}",
 419:     "opacity": 1.0,
 420:     "weight": {$weight},
 421: }).addTo(map);
 422: 
 423: EOT;
 424:     return $js;
 425: }

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

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

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

解説:距離計算表

greatCircleSailing.php

 428: /**
 429:  * 距離計算表を作成
 430:  * @param   array  $items 地点・距離情報
 431:  * @param   object $pgc   pahooGeoCodeクラス
 432:  * @return  string 距離計算表(HTML)
 433: */
 434: function makeDistanceTable($items, $pgc) {
 435:     $d11 = sprintf('%.1f', round($items['dist1'], 1));
 436:     $d12 = sprintf('%.1f', round($pgc->km2mi($items['dist1']), 1));
 437:     $d13 = sprintf('%.1f', round($pgc->km2nm($items['dist1']), 1));
 438:     $d21 = sprintf('%.1f', round($items['dist2'], 1));
 439:     $d22 = sprintf('%.1f', round($pgc->km2mi($items['dist2']), 1));
 440:     $d23 = sprintf('%.1f', round($pgc->km2nm($items['dist2']), 1));
 441:     $ww  = MAP_WIDTH / 4;
 442: 
 443:     $html =<<< EOT
 444: <table class="plists">
 445: <caption>距離計算</caption>
 446: <tr>
 447: <th style="width:{$ww}px;">&nbsp;</th>
 448: <th style="width:{$ww}px;">キロメートル</th>
 449: <th style="width:{$ww}px;">マイル</th>
 450: <th style="width:{$ww}px;">海里</th>
 451: </tr>
 452: <tr>
 453: <th>大圏航路</th>
 454: <td>{$d11}</td>
 455: <td>{$d12}</td>
 456: <td>{$d13}</td>
 457: </tr>
 458: <tr>
 459: <th>等角航路</th>
 460: <td>{$d21}</td>
 461: <td>{$d22}</td>
 462: <td>{$d23}</td>
 463: </tr>
 464: </table>
 465: 
 466: EOT;
 467:     return $html;
 468: }

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

活用例

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

参考サイト

(この項おわり)
header