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


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 = '*****************************';
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

Yahoo! JAPAN Webサービスを利用するには Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を参照されたい。
また、地図としてGoogleマップを利用するのであれば、Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を参照されたい。
準備:地図サービスの選択
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo APIから選択可能で、定数 GEOSERVICE に値を設定すること。




この問題は、Googleマップでは発生しない。
サンプル・プログラムの流れ

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

なお、地球は完全な球体ではないので、厳密には大円と大圏航路には微妙なずれがある。今回は計算を簡便にする目的で、大円が大圏航路であるとしている。


これに対し、極点に対して常に一定の角度で進むコースを等角航路(rhumb line)と呼ぶ。天体観測によって緯度は観測できても、正確な時刻を知るためのクロノメーターが無かった時代は、大圏航路より時間がかかるが、等角航路を使っていた。



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


によって計算できる。
解説:大圏航路の軌跡
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: }

なお、「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: }
解説:等角航路の軌跡
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: }

なお、等角航路は複雑な曲線を描くため、距離を求める適当な方程式がない。そこで、隣り合う座標間の大圏距離を積分し、等角航路の距離を同時に算出する。
解説:初期値
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: }
まず、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: }
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: }
解説:航路描画スクリプト
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: }
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: }
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: }
なお、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;"> </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: }
活用例
参考サイト
- 各種WebAPIの登録方法:ぱふぅ家のホームページ
- PHPでGoogleを利用して住所から緯度・経度を求める
- PHPで2地点間の直線距離を求める
- 地図上で2点間の直線距離を測:みんなの知識 ちょっと便利帳
(2021年2月20日)PHP8対応,Yahoo! JavaScriptマップ廃止
(2020年3月22日)OSM Nominatim Search APIを利用できるようにした。
(2020年3月14日)地理院地図・OSMに対応した。
(2019年4月25日)フィット機能を追加。IE11動作不具合改善、その他細かい修正を行った。