目次
サンプル・プログラムの実行例
Googleマップでは「経路」ボタンをクリックすると経路を表示し、「距離」ボタンをクリックすると再び距離計測表示に戻る。
YOLPコンテンツジオコーダAPI(Yahoo!JAPAN)は、世界のキーワードから緯度・経度を求める機能が弱い。たとえば「サンティアゴ」でキーワード検索すると、Google Geocoding API チリの首都を第一候補として返すが、YOLPコンテンツジオコーダAPIはドミニカ共和国の県都しか返さない。
そこで、地図上のマーカーをドラッグすることで、自動的に航路を再描画し、距離を再計算する機能を採り入れた。
サンプル・プログラムのダウンロード
| getDistance.php | サンプル・プログラム本体 |
| pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
| pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 2.5.0 | 2026/01/11 | PHP8.5対応:double→float |
| 2.4.0 | 2025/08/30 | .pahooEnv導入 |
| 2.3.0 | 2023/08/12 | 検索キーの最小・最大長の指定 |
| 2.2.0 | 2023/08/12 | 国土地理院ジオコーディングAPIを追加 |
| 2.1 | 2021/12/04 | Leaflet対応 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 6.9.1 | 2025/11/25 | PHP8.5対応:double→float |
| 6.9.0 | 2025/09/21 | jsPolygon, jsPolygon_Gmap, jsPolygon_Leaflet, loadGeoJSON, getPrefBorderList を追加 |
| 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 追加 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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対応
Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dllLinuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順
これで準備は完了だ。
準備:pahooGeoCode クラス
pahooGeoCode.php
41: class pahooGeoCode {
42: public $items; // 検索結果格納用
43: public $error; // エラー・フラグ
44: public $errmsg; // エラー・メッセージ
45: public $hits; // 検索ヒット件数
46: public $webapi; // 直前に呼び出したWebAPI URL
47:
48: // 都道府県境界線データ
49: // SimpleMaps.com is a product of Pareto Software, LLC. © 2010-2025
50: // https://simplemaps.com/gis/country/jp
51: // ※各自の環境に合わせて設定すること
52: public $GeoJsonJP = __DIR__ . '/jp.json';
53:
54: // -- 以下のデータは .env ファイルに記述可能
55: // Google Cloud Platform APIキー
56: // https://cloud.google.com/maps-platform/
57: // ※Google Maps APIを利用しないのなら登録不要
58: public $GOOGLE_API_KEY_1 = ''; // HTTPリファラ用
59: public $GOOGLE_API_KEY_2 = ''; // IP制限用
60: public $GOOGLE_MAP_ID = ''; // GoogleMaps ID
61:
62: // Yahoo! JAPAN Webサービス アプリケーションID
63: // https://e.developer.yahoo.co.jp/register
64: // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
65: public $YAHOO_APPLICATION_ID = '';
66:
67: // OSM Nominatim Search API利用時に知らせるメールアドレス
68: // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
69: // ※OSM Nominatim Search APIを利用しないのなら登録不要
70: public $NOMINATIM_EMAIL = '';
71:
72: // IP2Location.io APIキー
73: // https://www.ip2location.io/
74: // ※IP2Location.ioを利用しないのなら登録不要
75: public $IP2LOCATION_API_KEY = '';
地図や住所検索として 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 関数群
また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。
準備:地図サービスの選択
weeklyCalendar.php
65: // 地図描画サービスの選択
66: // 0:Google
67: // 2:地理院地図・OSM
68: define('MAPSERVICE', 2);
69:
70: // 住所検索サービスの選択
71: // 0:Google
72: // 1:Yahoo!JAPAN
73: // 11:HeartRails Geo API
74: // 12:OSM Nominatim Search API
75: // 13:国土地理院ジオコーディングAPI
76: define('GEOSERVICE', 1);
77:
78: // 逆ジオコーディングサービスの選択
79: // 0:Google
80: // 1:Yahoo!JAPAN
81: // 11:HeartRails Geo API
82: // 21:簡易ジオコーディングサービス
83: define('REVGEOSERVICE', 1);
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo API、OSM Nominatim Search API、国土地理院ジオコーディングAPI から選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、Google、Yahoo!JAPAN、HeartRails Geo API、簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。
PHPとJavaScriptの役割分担
| No. | 処理系 | 関数名 | 機能 |
|---|---|---|---|
| 1 | PHP | pahooGeoCode::searchPoint3 | 住所・ランドマークから緯度・経度を求める。 |
| 2 | PHP | makeCommonBody | HTML BODYを作成する。 |
| 3 | JavaScript | greatCircleDistance | 2地点間の大圏航路距離を求める。 |
| 4 | JavaScript | greatCircleSailing | 2地点間の大圏航路軌跡を求める。 |
| 5 | JavaScript | dragendMarker | マーカーのドラッグエンド処理。 |
| 6 | JavaScript | addMarker | 軌跡の最後にマーカーを1つ追加する。 |
| 7 | JavaScript | delMarker | 軌跡の最後のマーカーを1つ削除する。 |
| 8 | JavaScript | fitting | 地図を最適サイズにフィットする。 |
| 9 | JavaScript | stringifyMarkers | マーカーの位置情報をJSON文字列に変換して代入する。 |
| 10 | JavaScript | drawRoute | 経路探索し、マーカー・軌跡を消去する。 |
| 11 | JavaScript | delRoute | 経路を消去し、マーカー・軌跡を再描画する。 |
| 12 | JavaScript | writeDtable | 距離TABLEを表示する。 |
| 13 | JavaScript | writeRtable | 経路TABLEを表示する。 |
| 14 | JavaScript | initRtable | 経路TABLEを初期化する。 |
解説:初期値
getDistance.php
67: // マップの表示サイズ(単位:ピクセル)
68: define('MAP_WIDTH', 600);
69: define('MAP_HEIGHT', 400);
70: // マップID
71: define('MAPID', 'map_id');
72: // 初期値
73: define('FLAG_DISTANCE', TRUE); // TRUE:距離明細を表示,FALSE:非表示
74: define('MAX_MARKERS', 20); // マーカーの最大数
75: define('DEF_LONGITUDE0', 139.766667); // 地図中心(経度)
76: define('DEF_LATITUDE0', 35.681111); // (緯度)
77: define('DEF_QUERY', '東京駅'); // 検索クエリ
78: define('DEF_CAT', 'landmark'); // カテゴリ
79: define('DEF_TYPE', 'roadmap'); // マップタイプ
80: define('DEF_ZOOM', 10); // ズーム
81: define('DEF_POINTS', '[]'); // マーカー位置JSON
82: define('LINE_COLOR', 'FF0000'); // 軌跡の色(RGB)
83: define('LINE_WEIGHT', 3); // 軌跡の太さ
84:
85: // 住所検索時センタリング
86: define('CENTERING', TRUE); // TRUE:センタリングする/FALSE:しない
87:
88: // 経路探索
89: if (MAPSERVICE == 1) {
90: define('SEARCH_ROUTE', TRUE); // TRUE:経路探索する/FALSE:しない
91: } else {
92: define('SEARCH_ROUTE', FALSE);
93: }
94: define('ROUTE_COLOR', '0000FF'); // 経路の色(RGB)
95: define('ROUTE_WEIGHT', 3); // 経路の太さ
定数 FLAG_DISTANCE が TRUE の時は、経由地点間の距離明細を一覧表に表示する。一覧表の行数が不定になるためページのレイアウトが崩れるような、FALSE にすることで、距離の合計値のみを表示するようになる。
定数 MAX_MARKERS はマーカーの最大数を定義している。プログラム上、マーカーの数に上限はないのいだが、処理系に負荷をかけない程度の数に抑えておいた方が無難だろう。
定数 CENTERING を TRUE にすると、住所・ランドマーク検索後に、ヒットした位置に地図がセンタリングする。
定数 SEARCH_ROUTE を TRUE にすると、地図サービスの経路探索機能を利用できるようにする。前述のように、Googleマップでは経路探索機能は課金率が高いことから、FALSE にすることで、経路探索は利用せず、関連スクリプトも出力しなくなる。
解説:大圏航路の軌跡
getDistance.php
373:
374: return distance;
375: }
376:
377: /**
378: * 2地点間の大圏航路軌跡を求める
379: * @param float long_a, lati_a A地点の経度,緯度(世界測地系)
380: * @param float long_b, lati_b B地点の経度,緯度(世界測地系)
381: * @return array points 軌跡の座標を格納
382: * [n]['longitude'] 軌跡の経度(世界測地系)
383: * [n]['latitude'] 軌跡の緯度(世界測地系)
384: */
385: function greatCircleSailing(long_a, lati_a, long_b, lati_b) {
386: let points = Array();
387: lati_a = deg2rad(lati_a);
388: long_a = deg2rad(long_a);
389: lati_b = deg2rad(lati_b);
390: long_b = deg2rad(long_b);
391:
392: let l1 = (long_a >= 0) ? long_a : 2 * Math.PI + long_a;
393: let l2 = (long_b >= 0) ? long_b : 2 * Math.PI + long_b;
394: let dd = l2 - l1;
395: let tt = 0.01; // 経度方向の増分
396: if (dd < 0) {
397: dd = Math.abs(dd);
398: tt = -tt;
399: } else if (dd > Math.PI) {
400: [lati_a, lati_b] = [lati_b, lati_a];
401: [long_a, long_b] = [long_b, long_a];
402: dd = 2 * Math.PI - dd;
403: }
404: let st = 0.0;
405: let cnt = 0;
406:
407: // 軌跡の計算
408: let latitude = lati_a;
409: let longitude = long_a;
410: while (st < dd) {
411: if (latitude >= 0.5 * Math.PI) latitude -= Math.PI;
412: if (longitude >= 1.0 * Math.PI) longitude = longitude - Math.PI;
413: points[cnt] = {
414: "latitude" : rad2deg(latitude),
415: "longitude" : rad2deg(longitude)
416: };
417: longitude += tt;
418: if (longitude >= Math.PI) longitude = longitude - 2 * Math.PI;
419: latitude = (Math.sin(lati_a) * Math.sin(long_b - longitude)) / (Math.cos(lati_a) * Math.sin(long_b - long_a)) + (Math.sin(lati_b) * Math.sin(long_a - longitude)) / (Math.cos(lati_b) * Math.sin(long_a - long_b));
420: latitude = Math.atan(latitude);
421: if (Math.sin(lati_a) / Math.sin(lati_b) < 0) latitude += Math.PI;
422:
423: st += Math.abs(tt);
424: cnt++;
425: }
426: points[cnt] = {
427: "latitude" : rad2deg(lati_b),
428: "longitude" : rad2deg(long_b)
429: };
座標数ではなく、座標そのものの配列を戻す形に変更している。
解説:大圏航路の距離
getDistance.php
354: function rad2deg(rad) {
355: return rad / (Math.PI / 180);
356: }
357:
358: /**
359: * 2地点間の大圏航路距離を求める
360: * @param float long_a, lati_a A地点の経度,緯度(世界測地系)
361: * @param float long_b, lati_b B地点の経度,緯度(世界測地系)
362: * @return float 大圏航路距離(km)
363: */
364: function greatCircleDistance(long_a, lati_a, long_b, lati_b) {
365: lati_a = deg2rad(lati_a);
366: long_a = deg2rad(long_a);
367: lati_b = deg2rad(lati_b);
368: long_b = deg2rad(long_b);
369:
370: // 距離の計算
371: let ll = Math.abs(long_b - long_a);
解説:距離TABLE、経路TABLE
getDistance.php
252:
253: return hh + '時間' + mm + '分';
254: }
255:
256: /**
257: * 距離TABLEを表示
258: * @param Array arr 距離配列
259: * @return なし
260: */
261: function writeDtable(arr) {
262: let flag={$flagDistance}; // TRUE:距離明細を表示/FALSE:非表示
263: let d0, d1, d2, d3;
264: let html = '';
265:
266: // 距離明細(地点間)
267: if (flag) {
268: let pcount = Number(document.getElementById('pcount').value);
269: for (let i = 1; i < pcount; i++) {
270: d0 = arr[i];
271: d1 = roundf(d0, 1);
272: d2 = roundf(km2mi(d0), 1);
273: d3 = roundf(km2nm(d0), 1);
274: html += '<tr>';
275: html += '<th>' + i + '</th>';
276: html += '<td>' + d1 + '</td>';
277: html += '<td>' + d2 + '</td>';
278: html += '<td>' + d3 + '</td>';
279: html += '</tr>';
280: }
281: }
282: // 距離合計
283: d0 = sum(arr);
284: d1 = roundf(d0, 1);
285: d2 = roundf(km2mi(d0), 1);
286: d3 = roundf(km2nm(d0), 1);
287: html += '<tr>';
288: html += '<th>合計</th>';
289: html += '<td>' + d1 + '</td>';
290: html += '<td>' + d2 + '</td>';
291: html += '<td>' + d3 + '</td>';
292: html += '</tr>';
293:
解説:Google JavaScriptマップ用スクリプト(マーカー・軌跡)
getDistance.php
457: /**
458: * Google JavaScriptマップ用スクリプト(マーカー・軌跡)を生成する.
459: * @param string $points マーカーの初期座標(JSONテキスト)
460: * @return string JavaScript
461: */
462: function jsGmap($points) {
463: $maxMarkers = MAX_MARKERS; // マーカーの最大数
464: $color = '#' . LINE_COLOR;
465: $weight = LINE_WEIGHT;
466:
467: $js =<<< EOT
468: let Dmarkers = Array(); // マーカー格納用
469: let Dlines = Array(); // 軌跡格納用
470: let Distances = Array(); // 距離格納用
471: let EventListner; // イベントハンドラ格納用
472:
473: // マーカーの初期状態
474: let points = JSON.parse('{$points}');
475: document.getElementById('pcount').value = 0;
476: for (let i = 0; i < points.length; i++) {
477: _addMarker(points[i].latitude, points[i].longitude);
478: }
479:
480: // イベント設定
481: EventListner = google.maps.event.addListener(map, 'click', addMarker);
482: document.getElementById('delmarker').addEventListener('click', delMarker, false);
483: document.getElementById('fit').addEventListener('click', fitting, false);
484:
485: /**
486: * マーカーのドラッグエンド処理
487: * @param なし
488: * @return なし
489: */
490: function dragendMarker() {
491: let pcount = Number(document.getElementById('pcount').value);
492:
493: // 大圏航法軌跡の再描画と距離の再計算
494: let m0, m1;
495: for (let i = 1; i < pcount; i++) {
496: m0 = Dmarkers[i - 1].getPosition();
497: m1 = Dmarkers[i].getPosition();
498: Distances[i] = greatCircleDistance(m0.lng(), m0.lat(), m1.lng(), m1.lat());
499: let points = greatCircleSailing(m0.lng(), m0.lat(), m1.lng(), m1.lat());
500: let line = [];
501: for (let j = 0; j < points.length; j++) {
502: line[j] = new google.maps.LatLng(points[j].latitude, points[j].longitude);
503: }
504: // 古い軌跡を削除
505: Dlines[i].setMap(null);
506: // 新しい軌跡を描く
507: Dlines[i] = new google.maps.Polyline({
508: map: map,
509: path: line,
510: strokeColor: '{$color}',
511: strokeOpacity: 1,
512: strokeWeight: {$weight}
513: });
514: stringifyMarkers();
515: }
516: writeDtable(Distances); // 距離TABLE更新
517: }
518:
519: /**
520: * 軌跡の最後にマーカーを1つ追加する
521: * @param LatLng point 追加する座標
522: * @return なし
523: */
524: function addMarker(e) {
525: let lat = e.latLng.lat();
526: let lng = e.latLng.lng();
527: _addMarker(lat, lng);
528: }
529:
530: /**
531: * 軌跡の最後にマーカーを1つ追加する(下請け)
532: * @param float lat 緯度
533: * @param float lng 経度
534: * @return なし
535: */
536: function _addMarker(lat, lng) {
537: let dd;
538: let pcount = Number(document.getElementById('pcount').value);
539: if (pcount >= {$maxMarkers}) return; // マーカー数の上限チェック
540: let m0, m1;
541:
542: // マーカー追加
543: Dmarkers[pcount] = new google.maps.Marker({
544: map: map,
545: position: new google.maps.LatLng(lat, lng),
546: icon: {
547: fillColor: '#FF4500',
548: fillOpacity: 0.8,
549: path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
550: scale: 4,
551: strokeColor: "#FF4500",
552: strokeWeight: 1.0
553: }
554: });
555:
556: // ドラッグエンド・イベント処理
557: Dmarkers[pcount].setDraggable(true);
558: Dmarkers[pcount].addListener('dragend', dragendMarker);
559:
560: // 大圏航法軌跡の描画と距離の再計算
561: if (pcount >= 1) {
562: m0 = Dmarkers[pcount - 1].getPosition();
563: m1 = Dmarkers[pcount - 0].getPosition();
564: let points = greatCircleSailing(m0.lng(), m0.lat(), m1.lng(), m1.lat());
565: let line = [];
566: for (let j = 0; j < points.length; j++) {
567: line[j] = new google.maps.LatLng(points[j].latitude, points[j].longitude);
568: }
569: Dlines[pcount] = new google.maps.Polyline({
570: map: map,
571: path: line,
572: strokeColor: '{$color}',
573: strokeOpacity: 1,
574: strokeWeight: {$weight}
575: });
576: Distances[pcount] = greatCircleDistance(m0.lng(), m0.lat(), m1.lng(), m1.lat());
577: }
578: document.getElementById('pcount').value = pcount + 1;
579: writeDtable(Distances); // 距離TABLE更新
580: stringifyMarkers(); // マーカー座標JSON作成
581: }
582:
583: /**
584: * 軌跡の最後のマーカーを1つ削除する
585: * @param なし
586: * @return なし
587: */
588: function delMarker() {
589: let pcount = Number(document.getElementById('pcount').value);
590: if (pcount > 0) pcount--;
591:
592: // 最後の地点間距離を削除
593: Distances.pop();
594:
595: // マーカーと軌跡の削除
596: Dmarkers[pcount].setMap(null);
597: Dlines[pcount].setMap(null);
598: stringifyMarkers(); // マーカー座標JSON作成
599: document.getElementById('pcount').value = pcount;
600: writeDtable(Distances); // 距離TABLE更新
601: }
602:
603: /**
604: * マーカーの位置情報をJSON文字列に変換して代入
605: * @param なし
606: * @return なし
607: */
608: function stringifyMarkers() {
609: let points = [];
610: let pcount = Number(document.getElementById('pcount').value);
611: for (let i = 0; i < pcount; i++) {
612: points[i] = {
613: 'latitude' : Dmarkers[i].getPosition().lat(),
614: 'longitude' : Dmarkers[i].getPosition().lng()
615: };
616: }
617: document.getElementById('points').value = JSON.stringify(points);
618: }
619:
620: /**
621: * 地図を最適サイズにフィットする
622: * @param なし
623: * @return なし
624: */
625: function fitting() {
626: let lat, lng, lat0, lng0, lat1, lng1;
627: let pcount = Number(document.getElementById('pcount').value);
628: // 南西端(lat0, lng0), 北東端(lat1, lng1)を求める
629: for (let i = 0; i < pcount; i++) {
630: lat = Dmarkers[i].getPosition().lat();
631: lng = Dmarkers[i].getPosition().lng();
632: if (i == 0) {
633: lat0 = lat1 = lat;
634: lng0 = lng1 = lng;
635: }
636: if (lat0 > lat) lat0 = lat;
637: if (lng0 > lng) lng0 = lng;
638: if (lat1 < lat) lat1 = lat;
639: if (lng1 < lng) lng1 = lng;
640: }
641: let bounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat0, lng0), new google.maps.LatLng(lat1, lng1));
642: map.fitBounds(bounds); // 地図表示の最適化
643: }
644:
645: EOT;
646: return $js;
647: }
冒頭では、複数のマーカー情報を格納する配列変数 Dmarkers、マーカー間の軌跡を格納する配列変数 Dlines、マーカー間の距離を格納する配列変数 Distances、マップ・クリック時のイベントハンドラを格納する版数 EventListner を、それぞれグローバル変数として用意する。
サーバサイドに処理を渡す際、マーカーの位置情報はJSONテキストに書き出して保存している。このJSONを読み込み、個々のマーカー座標に分離し、後述するマーカー追加関数 _addMarker に渡すことで、初期状態のマーカーと軌跡を描く。
マップをクリックした時のイベントは、後述するマーカー追加関数 addMarker に紐付ける。
「1つ戻る」ボタンをクリックした時のイベントは、後述するマーカー削除関数 delMarker に紐付ける。
「フィット」ボタンをクリックした時のイベントは、後述する地図の最適表示関数 fittng に紐付ける。
getDistance.php
515: }
516: writeDtable(Distances); // 距離TABLE更新
517: }
518:
519: /**
520: * 軌跡の最後にマーカーを1つ追加する
521: * @param LatLng point 追加する座標
522: * @return なし
523: */
524: function addMarker(e) {
現在のマーカー数は、HTMLのFORMテキスト pcount に格納してある。
まず、この値を参照し、最大数を超えていないかどうかをチェックする。
続いてマーカーを追加する。ここでは icon オブジェクトに矢印形のアイコンを定義しているが、形状は自由に変更して構わない。
マーカーはドラッグできるようにして、ドラッグエンドの時の処理を紐付けておく。
次に、直前のマーカーとの間に大圏航路軌跡を描き、その距離を計算する。
最後にマーカー数をアップカウントし、距離TABLEの更新とマーカー座標JSONの再作成を行っておく。
getDistance.php
481: EventListner = google.maps.event.addListener(map, 'click', addMarker);
482: document.getElementById('delmarker').addEventListener('click', delMarker, false);
483: document.getElementById('fit').addEventListener('click', fitting, false);
484:
485: /**
486: * マーカーのドラッグエンド処理
487: * @param なし
488: * @return なし
489: */
490: function dragendMarker() {
491: let pcount = Number(document.getElementById('pcount').value);
492:
493: // 大圏航法軌跡の再描画と距離の再計算
494: let m0, m1;
495: for (let i = 1; i < pcount; i++) {
496: m0 = Dmarkers[i - 1].getPosition();
497: m1 = Dmarkers[i].getPosition();
498: Distances[i] = greatCircleDistance(m0.lng(), m0.lat(), m1.lng(), m1.lat());
499: let points = greatCircleSailing(m0.lng(), m0.lat(), m1.lng(), m1.lat());
500: let line = [];
501: for (let j = 0; j < points.length; j++) {
502: line[j] = new google.maps.LatLng(points[j].latitude, points[j].longitude);
503: }
504: // 古い軌跡を削除
505: Dlines[i].setMap(null);
506: // 新しい軌跡を描く
507: Dlines[i] = new google.maps.Polyline({
508: map: map,
509: path: line,
510: strokeColor: '{$color}',
511: strokeOpacity: 1,
512: strokeWeight: {$weight}
513: });
getDistance.php
579: writeDtable(Distances); // 距離TABLE更新
580: stringifyMarkers(); // マーカー座標JSON作成
581: }
582:
583: /**
584: * 軌跡の最後のマーカーを1つ削除する
585: * @param なし
586: * @return なし
587: */
588: function delMarker() {
589: let pcount = Number(document.getElementById('pcount').value);
590: if (pcount > 0) pcount--;
591:
592: // 最後の地点間距離を削除
593: Distances.pop();
594:
595: // マーカーと軌跡の削除
596: Dmarkers[pcount].setMap(null);
597: Dlines[pcount].setMap(null);
あわせて、関連する配列変数の最後の要素を削除し、マーカー数をダウンカウントと、距離TABLEの更新とマーカー座標JSONの再作成を行う。
getDistance.php
599: document.getElementById('pcount').value = pcount;
600: writeDtable(Distances); // 距離TABLE更新
601: }
602:
603: /**
604: * マーカーの位置情報をJSON文字列に変換して代入
605: * @param なし
606: * @return なし
607: */
608: function stringifyMarkers() {
609: let points = [];
610: let pcount = Number(document.getElementById('pcount').value);
611: for (let i = 0; i < pcount; i++) {
612: points[i] = {
613: 'latitude' : Dmarkers[i].getPosition().lat(),
614: 'longitude' : Dmarkers[i].getPosition().lng()
処理がサーバサイドに移った時、この情報をPOST渡しすることでマーカー座標が失われないようにする。
getDistance.php
616: }
617: document.getElementById('points').value = JSON.stringify(points);
618: }
619:
620: /**
621: * 地図を最適サイズにフィットする
622: * @param なし
623: * @return なし
624: */
625: function fitting() {
626: let lat, lng, lat0, lng0, lat1, lng1;
627: let pcount = Number(document.getElementById('pcount').value);
628: // 南西端(lat0, lng0), 北東端(lat1, lng1)を求める
629: for (let i = 0; i < pcount; i++) {
630: lat = Dmarkers[i].getPosition().lat();
631: lng = Dmarkers[i].getPosition().lng();
632: if (i == 0) {
633: lat0 = lat1 = lat;
634: lng0 = lng1 = lng;
635: }
636: if (lat0 > lat) lat0 = lat;
637: if (lng0 > lng) lng0 = lng;
638: if (lat1 < lat) lat1 = lat;
639: if (lng1 < lng) lng1 = lng;
変数 Dmarkers をスキャンして、マーカー群の南西端と北東端を求め、これをGooleマップAPIの fitBounds メソッドに渡すことで実現している。
後述する経路探索では、経路の南西端と北東端が求めにくいため、経路探索中にフィット機能は使用できないようにしている。
解説:Google JavaScriptマップ用スクリプト(経路探索)
getDistance.php
649: /**
650: * Google JavaScriptマップ用スクリプトを(経路探索)を生成する.
651: * @param なし
652: * @return string JavaScript
653: */
654: function jsGroute() {
655: $route_color = '#' . ROUTE_COLOR;
656: $route_weight = ROUTE_WEIGHT;
657:
658: $js =<<< EOT
659: let LineRoute; // 経路ライン
660:
661: // イベント設定
662: document.getElementById('route').addEventListener('click', drawRoute, false);
663: document.getElementById('redraw').addEventListener('click', delRoute, false);
664: setButtons(['redraw'], 'disable');
665:
666: /**
667: * 経路探索し,軌跡を消去
668: * @param なし
669: * @return なし
670: * 参考 https://qiita.com/nissuk/items/7e0225ae3764d9f1bef7
671: */
672: function drawRoute() {
673: // クリック・イベント削除
674: google.maps.event.removeListener(EventListner);
675: setButtons(['exec', 'delmarker', 'fit', 'route'], 'disable');
676: setButtons(['redraw'], 'enable');
677:
678: let pcount = Number(document.getElementById('pcount').value);
679: if (pcount < 2) return;
680:
681: latlngs = [];
682: for (let i = 0; i < pcount; i++) {
683: latlngs[i] = Dmarkers[i].getPosition();
684: if (i > 0) Dlines[i].setMap(null); // 軌跡を消去
685: }
686: // 経由地点8カ所以上は分割して経路探索
687: let d = new google.maps.DirectionsService(); // 経路探索オブジェクト
688: let origin = null; // 出発地
689: let waypoints = []; // 経由地
690: let dest = null; // 目的地
691: let resultMap = {}; // 分割経路探索の結果
692: let requestIndex = 0; // 分割検索番号
693: let done = 0; // 検索完了数
694: for (let i = 0, len = latlngs.length; i < len; i++) {
695: // 検索の最初
696: if (origin == null) {
697: origin = latlngs[i];
698: // 経由地が8カ所になった or 目的地→経路探索
699: } else if (waypoints.length == 8 || i == len - 1) {
700: dest = latlngs[i];
701: (function(index) {
702: // 検索条件
703: let request = {
704: origin: origin, // 出発地
705: waypoints: waypoints, // 経由地
706: destination: dest, // 目的地
707: travelMode: google.maps.DirectionsTravelMode.DRIVING
708: // 移動手段:自動車
709: };
710: // 経路探索実行
711: d.route(request, function(result, status) {
712: // 経路データ保持
713: if (status == google.maps.DirectionsStatus.OK) {
714: resultMap[index] = result;
715: done++;
716: } else {
717: console.log(status); // デバッグ用
718: }
719: });
720: })(requestIndex);
721:
722: requestIndex++;
723: origin = latlngs[i]; // 目的地を次の出発地へ
724: waypoints = [];
725: // それ以外→waypointsに地点を追加
726: } else {
727: waypoints.push({ location: latlngs[i], stopover: true });
728: }
729: }
730: // 経路を配列へ
731: let sid = setInterval(function() {
732: // 分割したすべての検索が完了するまで待つ
733: if (requestIndex > done) return;
734: clearInterval(sid);
735:
736: // すべての経由座標を順番に取得して平坦な配列に置換
737: let path = [];
738: let result;
739: let o = { 'TotalDistance' : 0, 'TotalTime' : 0 };
740: for (let i = 0, len = requestIndex; i < len; i++) {
741: result = resultMap[i]; // 検索結果
742: let legs = result.routes[0].legs; // Array<DirectionsLeg>
743: for (let li = 0, llen = legs.length; li < llen; li++) {
744: let leg = legs[li]; // DirectionLeg
745: o.TotalDistance += leg.distance.value; // 経由距離
746: o.TotalTime += (leg.duration.value / 60); // 経由時間
747: let steps = leg.steps; // Array<DirectionsStep>
748: // DirectionsStepが持っているpathを取得して平坦(2次元配列→1次元配列)に
749: let _path = steps.map(function(step){ return step.path })
750: .reduce(function(all, paths){ return all.concat(paths) });
751: path = path.concat(_path);
752: }
753: }
754: // 経路描画
755: LineRoute = new google.maps.Polyline({
756: map: map,
757: strokeColor: '{$route_color}',
758: strokeOpacity: 0.8,
759: strokeWeight: {$route_weight},
760: path: path
761: });
762: // 小数点以下を丸める
763: o.TotalDistance = Math.round(o.TotalDistance);
764: o.TotalTime = Math.round(o.TotalTime);
765: writeRtable(o);
766: }, 1000);
767: }
768:
769: /**
770: * 経路を消去し,軌跡を再描画
771: * @param なし
772: * @return なし
773: */
774: function delRoute() {
775: // 経路消去
776: LineRoute.setMap(null); // 経路消去
777: // 軌跡の再描画
778: let pcount = Number(document.getElementById('pcount').value);
779: for (let i = 1; i < pcount; i++) {
780: Dlines[i].setMap(map);
781: };
782: // 経路TABLE初期化
783: initRtable();
784: // クリック・イベント再開
785: EventListner = google.maps.event.addListener(map, 'click', addMarker);
786: setButtons(['exec', 'delmarker', 'fit', 'route'], 'enable');
787: setButtons(['redraw'], 'disable');
788: }
789:
790: EOT;
791: return $js;
792: }
getDistance.php
662: document.getElementById('route').addEventListener('click', drawRoute, false);
663: document.getElementById('redraw').addEventListener('click', delRoute, false);
664: setButtons(['redraw'], 'disable');
665:
666: /**
667: * 経路探索し,軌跡を消去
668: * @param なし
669: * @return なし
670: * 参考 https://qiita.com/nissuk/items/7e0225ae3764d9f1bef7
671: */
672: function drawRoute() {
673: // クリック・イベント削除
674: google.maps.event.removeListener(EventListner);
675: setButtons(['exec', 'delmarker', 'fit', 'route'], 'disable');
676: setButtons(['redraw'], 'enable');
677:
678: let pcount = Number(document.getElementById('pcount').value);
679: if (pcount < 2) return;
680:
681: latlngs = [];
682: for (let i = 0; i < pcount; i++) {
683: latlngs[i] = Dmarkers[i].getPosition();
684: if (i > 0) Dlines[i].setMap(null); // 軌跡を消去
685: }
686: // 経由地点8カ所以上は分割して経路探索
687: let d = new google.maps.DirectionsService(); // 経路探索オブジェクト
688: let origin = null; // 出発地
689: let waypoints = []; // 経由地
690: let dest = null; // 目的地
691: let resultMap = {}; // 分割経路探索の結果
692: let requestIndex = 0; // 分割検索番号
693: let done = 0; // 検索完了数
694: for (let i = 0, len = latlngs.length; i < len; i++) {
695: // 検索の最初
696: if (origin == null) {
697: origin = latlngs[i];
698: // 経由地が8カ所になった or 目的地→経路探索
699: } else if (waypoints.length == 8 || i == len - 1) {
700: dest = latlngs[i];
701: (function(index) {
702: // 検索条件
703: let request = {
704: origin: origin, // 出発地
705: waypoints: waypoints, // 経由地
706: destination: dest, // 目的地
707: travelMode: google.maps.DirectionsTravelMode.DRIVING
708: // 移動手段:自動車
709: };
710: // 経路探索実行
711: d.route(request, function(result, status) {
712: // 経路データ保持
713: if (status == google.maps.DirectionsStatus.OK) {
714: resultMap[index] = result;
715: done++;
716: } else {
717: console.log(status); // デバッグ用
718: }
719: });
720: })(requestIndex);
721:
722: requestIndex++;
723: origin = latlngs[i]; // 目的地を次の出発地へ
724: waypoints = [];
725: // それ以外→waypointsに地点を追加
726: } else {
727: waypoints.push({ location: latlngs[i], stopover: true });
728: }
729: }
730: // 経路を配列へ
731: let sid = setInterval(function() {
732: // 分割したすべての検索が完了するまで待つ
733: if (requestIndex > done) return;
734: clearInterval(sid);
735:
736: // すべての経由座標を順番に取得して平坦な配列に置換
737: let path = [];
738: let result;
739: let o = { 'TotalDistance' : 0, 'TotalTime' : 0 };
740: for (let i = 0, len = requestIndex; i < len; i++) {
741: result = resultMap[i]; // 検索結果
742: let legs = result.routes[0].legs; // Array<DirectionsLeg>
743: for (let li = 0, llen = legs.length; li < llen; li++) {
744: let leg = legs[li]; // DirectionLeg
745: o.TotalDistance += leg.distance.value; // 経由距離
746: o.TotalTime += (leg.duration.value / 60); // 経由時間
747: let steps = leg.steps; // Array<DirectionsStep>
748: // DirectionsStepが持っているpathを取得して平坦(2次元配列→1次元配列)に
749: let _path = steps.map(function(step){ return step.path })
750: .reduce(function(all, paths){ return all.concat(paths) });
751: path = path.concat(_path);
752: }
753: }
754: // 経路描画
755: LineRoute = new google.maps.Polyline({
756: map: map,
757: strokeColor: '{$route_color}',
758: strokeOpacity: 0.8,
759: strokeWeight: {$route_weight},
760: path: path
761: });
762: // 小数点以下を丸める
763: o.TotalDistance = Math.round(o.TotalDistance);
経路は変数legsに入ってくるので、これをマップ上に描画するとともに距離積算、経由時間積算を行う。
getDistance.php
765: writeRtable(o);
766: }, 1000);
767: }
768:
769: /**
770: * 経路を消去し,軌跡を再描画
771: * @param なし
772: * @return なし
773: */
774: function delRoute() {
775: // 経路消去
776: LineRoute.setMap(null); // 経路消去
777: // 軌跡の再描画
778: let pcount = Number(document.getElementById('pcount').value);
779: for (let i = 1; i < pcount; i++) {
780: Dlines[i].setMap(map);
781: };
782: // 経路TABLE初期化
783: initRtable();
784: // クリック・イベント再開
解説:Leaflet用スクリプト(マーカー・軌跡)
getDistance.php
1041: /**
1042: * Leafletマップ用スクリプト(マーカー・軌跡)を生成する.
1043: * @param string $points マーカーの初期座標(JSONテキスト)
1044: * @return string JavaScript
1045: */
1046: function jsLeaflet($points) {
1047: $maxMarkers = MAX_MARKERS; // マーカーの最大数
1048: $color = '#' . LINE_COLOR;
1049: $weight = LINE_WEIGHT;
1050:
1051: $js =<<< EOT
1052: let Dmarkers = Array(); // マーカー格納用
1053: let Dlines = Array(); // 軌跡格納用
1054: let Distances = Array(); // 距離格納用
1055:
1056: // マーカーの初期状態
1057: let points = JSON.parse('{$points}');
1058: document.getElementById('pcount').value = 0;
1059: for (let i = 0; i < points.length; i++) {
1060: _addMarker(Array(points[i].latitude, points[i].longitude));
1061: }
1062:
1063: // イベント設定
1064: map.on('click', addMarker);
1065: document.getElementById('delmarker').addEventListener('click', delMarker, false);
1066: document.getElementById('fit').addEventListener('click', fitting, false);
1067:
1068: /**
1069: * マーカーのドラッグエンド処理
1070: * @param なし
1071: * @return なし
1072: */
1073: function dragendMarker() {
1074: let pcount = Number(document.getElementById('pcount').value);
1075:
1076: // 大圏航法軌跡の再描画と距離の再計算
1077: let m0, m1;
1078: for (let i = 1; i < pcount; i++) {
1079: m0 = Dmarkers[i - 1].getLatLng();
1080: m1 = Dmarkers[i].getLatLng();
1081: Distances[i] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1082: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1083: let line = [];
1084: for (let j = 0; j < points.length; j++) {
1085: line[j] = Array(points[j].latitude, points[j].longitude);
1086: }
1087: // 古い軌跡を削除
1088: map.removeLayer(Dlines[i]);
1089: Dlines[i] = null;
1090: // 新しい軌跡を描く
1091: Dlines[i] = L.polyline(
1092: line, {
1093: 'color': '{$color}',
1094: 'opacity': 1,
1095: 'weight': {$weight}
1096: }).addTo(map);
1097: stringifyMarkers();
1098: }
1099: writeDtable(Distances); // 距離TABLE更新
1100: }
1101:
1102: /**
1103: * 軌跡の最後にマーカーを1つ追加する
1104: * @param LatLng point 追加する座標
1105: * @return なし
1106: */
1107: function addMarker(e) {
1108: _addMarker(e.latlng);
1109: }
1110:
1111: /**
1112: * 軌跡の最後にマーカーを1つ追加する(下請け)
1113: * @param float lat 緯度
1114: * @param float lng 経度
1115: * @return なし
1116: */
1117: function _addMarker(latlng) {
1118: let pcount = Number(document.getElementById('pcount').value);
1119: if (pcount >= {$maxMarkers}) return; // マーカー数の上限チェック
1120:
1121: // マーカー追加
1122: Dmarkers[pcount] = new L.marker(
1123: latlng, {
1124: draggable: true,
1125: }).addTo(map).on('dragend', dragendMarker);
1126:
1127: // 大圏航法軌跡の描画と距離の再計算
1128: if (pcount >= 1) {
1129: let m0 = Dmarkers[pcount - 1].getLatLng();
1130: let m1 = Dmarkers[pcount - 0].getLatLng();
1131: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1132: let line = [];
1133: for (let j = 0; j < points.length; j++) {
1134: line[j] = Array(points[j].latitude, points[j].longitude);
1135: }
1136: Dlines[pcount] = L.polyline(
1137: line, {
1138: 'color': '{$color}',
1139: 'opacity': 1,
1140: 'weight': {$weight}
1141: }).addTo(map);
1142: Distances[pcount] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1143: }
1144: document.getElementById('pcount').value = pcount + 1;
1145: writeDtable(Distances); // 距離TABLE更新
1146: stringifyMarkers(); // マーカー座標JSON作成
1147: }
1148:
1149: /**
1150: * 軌跡の最後のマーカーを1つ削除する
1151: * @param なし
1152: * @return なし
1153: */
1154: function delMarker() {
1155: let pcount = Number(document.getElementById('pcount').value);
1156: if (pcount > 0) pcount--;
1157: Dmarkers[pcount].dragging.disable(); // ドラッグ禁止
1158:
1159: // 最後の地点距離を削除
1160: Distances.pop();
1161:
1162: // マーカーと軌跡の削除
1163: map.removeLayer(Dmarkers[pcount]);
1164: map.removeLayer(Dlines[pcount]);
1165: stringifyMarkers();
1166: document.getElementById('pcount').value = pcount;
1167: writeDtable(Distances); // 距離TABLE更新
1168: }
1169:
1170: /**
1171: * マーカーの位置情報をJSON文字列に変換して代入
1172: * @param なし
1173: * @return なし
1174: */
1175: function stringifyMarkers() {
1176: let points = [];
1177: let pcount = Number(document.getElementById('pcount').value);
1178: for (let i = 0; i < pcount; i++) {
1179: points[i] = {
1180: 'latitude' : Dmarkers[i].getLatLng().lat,
1181: 'longitude' : Dmarkers[i].getLatLng().lng
1182: };
1183: }
1184: document.getElementById('points').value = JSON.stringify(points);
1185: }
1186:
1187: /**
1188: * 地図を最適サイズにフィットする
1189: * @param なし
1190: * @return なし
1191: */
1192: function fitting() {
1193: let lat0, lng0, lat1, lng1;
1194: let pcount = Number(document.getElementById('pcount').value);
1195: // 南西端(lat0, lng0), 北東端(lat1, lng1)を求める
1196: for (let i = 0; i < pcount; i++) {
1197: points[i] = {
1198: 'latitude' : Dmarkers[i].getLatLng().lat,
1199: 'longitude' : Dmarkers[i].getLatLng().lng
1200: };
1201: if (i == 0) {
1202: lat0 = lat1 = points[i].latitude;
1203: lng0 = lng1 = points[i].longitude;
1204: }
1205: if (lat0 > points[i].latitude) lat0 = points[i].latitude;
1206: if (lng0 > points[i].longitude) lng0 = points[i].longitude;
1207: if (lat1 < points[i].latitude) lat1 = points[i].latitude;
1208: if (lng1 < points[i].longitude) lng1 = points[i].longitude;
1209: }
1210: map.fitBounds([[lat0, lng0], [lat1, lng1]]);
1211: }
1212:
1213: EOT;
1214: return $js;
1215: }
なお、Leafletでは経路探索はできない。オープンストリートマップ側でフリーの経路探索エンジンが用意されているという情報もあったが、複数のサービスをあたってみたが、動作していなかったり、日本国内ではうまく動作しないようなので実装を見送った。
getDistance.php
1098: }
1099: writeDtable(Distances); // 距離TABLE更新
1100: }
1101:
1102: /**
1103: * 軌跡の最後にマーカーを1つ追加する
1104: * @param LatLng point 追加する座標
1105: * @return なし
1106: */
1107: function addMarker(e) {
1108: _addMarker(e.latlng);
1109: }
1110:
1111: /**
1112: * 軌跡の最後にマーカーを1つ追加する(下請け)
1113: * @param float lat 緯度
1114: * @param float lng 経度
1115: * @return なし
1116: */
1117: function _addMarker(latlng) {
1118: let pcount = Number(document.getElementById('pcount').value);
1119: if (pcount >= {$maxMarkers}) return; // マーカー数の上限チェック
1120:
1121: // マーカー追加
1122: Dmarkers[pcount] = new L.marker(
1123: latlng, {
1124: draggable: true,
1125: }).addTo(map).on('dragend', dragendMarker);
1126:
1127: // 大圏航法軌跡の描画と距離の再計算
1128: if (pcount >= 1) {
1129: let m0 = Dmarkers[pcount - 1].getLatLng();
1130: let m1 = Dmarkers[pcount - 0].getLatLng();
1131: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1132: let line = [];
1133: for (let j = 0; j < points.length; j++) {
1134: line[j] = Array(points[j].latitude, points[j].longitude);
1135: }
1136: Dlines[pcount] = L.polyline(
1137: line, {
1138: 'color': '{$color}',
1139: 'opacity': 1,
1140: 'weight': {$weight}
1141: }).addTo(map);
1142: Distances[pcount] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1143: }
getDistance.php
1064: map.on('click', addMarker);
1065: document.getElementById('delmarker').addEventListener('click', delMarker, false);
1066: document.getElementById('fit').addEventListener('click', fitting, false);
1067:
1068: /**
1069: * マーカーのドラッグエンド処理
1070: * @param なし
1071: * @return なし
1072: */
1073: function dragendMarker() {
1074: let pcount = Number(document.getElementById('pcount').value);
1075:
1076: // 大圏航法軌跡の再描画と距離の再計算
1077: let m0, m1;
1078: for (let i = 1; i < pcount; i++) {
1079: m0 = Dmarkers[i - 1].getLatLng();
1080: m1 = Dmarkers[i].getLatLng();
1081: Distances[i] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1082: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1083: let line = [];
1084: for (let j = 0; j < points.length; j++) {
1085: line[j] = Array(points[j].latitude, points[j].longitude);
1086: }
1087: // 古い軌跡を削除
1088: map.removeLayer(Dlines[i]);
1089: Dlines[i] = null;
1090: // 新しい軌跡を描く
1091: Dlines[i] = L.polyline(
1092: line, {
1093: 'color': '{$color}',
1094: 'opacity': 1,
1095: 'weight': {$weight}
1096: }).addTo(map);
getDistance.php
1145: writeDtable(Distances); // 距離TABLE更新
1146: stringifyMarkers(); // マーカー座標JSON作成
1147: }
1148:
1149: /**
1150: * 軌跡の最後のマーカーを1つ削除する
1151: * @param なし
1152: * @return なし
1153: */
1154: function delMarker() {
1155: let pcount = Number(document.getElementById('pcount').value);
1156: if (pcount > 0) pcount--;
1157: Dmarkers[pcount].dragging.disable(); // ドラッグ禁止
1158:
1159: // 最後の地点距離を削除
1160: Distances.pop();
1161:
1162: // マーカーと軌跡の削除
1163: map.removeLayer(Dmarkers[pcount]);
1164: map.removeLayer(Dlines[pcount]);
あわせて、関連する配列変数の最後の要素を削除し、マーカー数をダウンカウントと、距離TABLEの更新とマーカー座標JSONの再作成を行う。
getDistance.php
1167: writeDtable(Distances); // 距離TABLE更新
1168: }
1169:
1170: /**
1171: * マーカーの位置情報をJSON文字列に変換して代入
1172: * @param なし
1173: * @return なし
1174: */
1175: function stringifyMarkers() {
1176: let points = [];
1177: let pcount = Number(document.getElementById('pcount').value);
1178: for (let i = 0; i < pcount; i++) {
1179: points[i] = {
1180: 'latitude' : Dmarkers[i].getLatLng().lat,
1181: 'longitude' : Dmarkers[i].getLatLng().lng
処理がサーバサイドに移った時、この情報をPOST渡しすることでマーカー座標が失われないようにする。
getDistance.php
1183: }
1184: document.getElementById('points').value = JSON.stringify(points);
1185: }
1186:
1187: /**
1188: * 地図を最適サイズにフィットする
1189: * @param なし
1190: * @return なし
1191: */
1192: function fitting() {
1193: let lat0, lng0, lat1, lng1;
1194: let pcount = Number(document.getElementById('pcount').value);
1195: // 南西端(lat0, lng0), 北東端(lat1, lng1)を求める
1196: for (let i = 0; i < pcount; i++) {
1197: points[i] = {
1198: 'latitude' : Dmarkers[i].getLatLng().lat,
1199: 'longitude' : Dmarkers[i].getLatLng().lng
1200: };
1201: if (i == 0) {
1202: lat0 = lat1 = points[i].latitude;
1203: lng0 = lng1 = points[i].longitude;
1204: }
1205: if (lat0 > points[i].latitude) lat0 = points[i].latitude;
1206: if (lng0 > points[i].longitude) lng0 = points[i].longitude;
1207: if (lat1 < points[i].latitude) lat1 = points[i].latitude;
変数 Dmarkers をスキャンして、マーカー群の南西端と北東端を求め、これをGooleマップAPIの fitBounds メソッドに渡すことで実現している。
解説:住所検索でマーカー追加
getDistance.php
1421: // 緯度・経度を取得する.
1422: } else if (($errmsg == '') && isButton('exec')) {
1423: if ($query != '') {
1424: list($n, $url) = $pgc->searchPoint3($query, GEOSERVICE, $category);
1425: if ($pgc->iserror()) {
1426: $errmsg = $pgc->geterror();
1427: } else {
1428: // 終点マーカーを追加
1429: list($latitude1, $longitude1, $address1) = $pgc->getPoint(1);
1430: $arr = json_decode($points);
1431: $n = ($arr == NULL) ? 0 : count($arr);
1432: $arr[$n]['latitude'] = $latitude1;
1433: $arr[$n]['longitude'] = $longitude1;
1434: $points = json_encode($arr);
1435: // 地図のセンタリング
1436: if (CENTERING) {
1437: $latitude0 = $latitude1;
1438: $longitude0 = $longitude1;
1439: }
1440: }
1441: }
1442: }
活用例
参考サイト
- 各種WebAPIの登録方法:ぱふぅ家のホームページ
- PHPで住所・ランドマークから緯度・経度を求める:ぱふぅ家のホームページ
- PHPで大圏航路を描く:ぱふぅ家のホームページ
- 地図上で目的地までの距離を測る:みんなの知識 ちょっと便利帳

複数地点に配置するマーカーをドラッグで移動するインタラクティブな処理はJavaScriptに任せ、PHP側は住所・ランドマーク検索のみとする。地図サービスは、Googleマップと地理院地図・OSM(オープンストリートマップ)を切り替えて使えるようにする。
「Mapion キョリ測」より機能が多い分、プログラムもかなり長くなった。
(2026年1月11日) PHP8.5対応:double→float
(2025年8月30日).pahooEnv導入
(2025年6月14日)GoogleMaps JavaScript APIの変更に対応した.