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



Googleマップでは「経路」ボタンをクリックすると経路を表示し、「距離」ボタンをクリックすると再び距離計測表示に戻る。

YOLPコンテンツジオコーダAPI(Yahoo!JAPAN)は、世界のキーワードから緯度・経度を求める機能が弱い。たとえば「サンティアゴ」でキーワード検索すると、Google Geocoding API チリの首都を第一候補として返すが、YOLPコンテンツジオコーダAPIはドミニカ共和国の県都しか返さない。
そこで、地図上のマーカーをドラッグすることで、自動的に航路を再描画し、距離を再計算する機能を採り入れた。
サンプル・プログラムのダウンロード
getDistance.php | サンプル・プログラム本体 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
2.3.0 | 2023/08/12 | 検索キーの最小・最大長の指定 |
2.2.0 | 2023/08/12 | 国土地理院ジオコーディングAPIを追加 |
2.1 | 2021/12/04 | Leaflet対応 |
2.0 | 2021/11/06 | PHP8対応,リファラ・チェック改良,Yahoo!マップ終 了 |
1.21 | 2019/04/25 | IE11動作不具合改善,その他bug-fix |
バージョン | 更新日 | 内容 |
---|---|---|
6.3.3 | 2024/09/14 | $this->NOMINATIM_EMAIL 追加 |
6.3.2 | 2024/02/14 | getStaticMap() -- bug-fix |
6.3.1 | 2023/07/09 | bug-fix |
6.3.0 | 2023/07/02 | getPointsGSI()追加 |
6.2.0 | 2023/07/02 | ip2address()追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.5.0 | 2024/01/28 | exitIfExceedVersion() 追加 |
1.4.2 | 2024/01/28 | exitIfLessVersion() メッセージ修正 |
1.4.1 | 2023/09/30 | コメントの訂正 |
1.4.0 | 2023/09/09 | $_GET, $_POST参照をfilter_input()関数に置換 |
1.3.0 | 2023/07/11 | roundFloat() 追加 |
準備: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 = '*****************************';
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図や住所検索として Google を利用するのであれば、Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば、Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、それぞれ参照されたい。
準備:地図サービスの選択
63: //地図描画サービスの選択
64: // 0:Google
65: // 2:地理院地図・OSM
66: define('MAPSERVICE', 2);
67:
68: //住所検索サービスの選択
69: // 0:Google
70: // 1:Yahoo!JAPAN
71: // 11:HeartRails Geo API
72: // 12:OSM Nominatim Search API
73: // 13:国土地理院ジオコーディングAPI
74: define('GEOSERVICE', 1);
75:
76: //逆ジオコーディングサービスの選択
77: // 0:Google
78: // 1:Yahoo!JAPAN
79: // 11:HeartRails Geo API
80: // 21:簡易ジオコーディングサービス
81: 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を初期化する。 |
解説:初期値
62: //マップの表示サイズ(単位:ピクセル)
63: define('MAP_WIDTH', 600);
64: define('MAP_HEIGHT', 400);
65: //マップID
66: define('MAPID', 'map_id');
67: //初期値
68: define('FLAG_DISTANCE', TRUE); //TRUE:距離明細を表示,FALSE:非表示
69: define('MAX_MARKERS', 20); //マーカーの最大数
70: define('DEF_LONGITUDE0', 139.766667); //地図中心(経度)
71: define('DEF_LATITUDE0', 35.681111); // (緯度)
72: define('DEF_QUERY', '東京駅'); //検索クエリ
73: define('DEF_CAT', 'landmark'); //カテゴリ
74: define('DEF_TYPE', 'roadmap'); //マップタイプ
75: define('DEF_ZOOM', 10); //ズーム
76: define('DEF_POINTS', '[]'); //マーカー位置JSON
77: define('LINE_COLOR', 'FF0000'); //軌跡の色(RGB)
78: define('LINE_WEIGHT', 3); //軌跡の太さ
79:
80: //住所検索時センタリング
81: define('CENTERING', TRUE); //TRUE:センタリングする/FALSE:しない
82:
83: //経路探索
84: if (MAPSERVICE == 1) {
85: define('SEARCH_ROUTE', TRUE); //TRUE:経路探索する/FALSE:しない
86: } else {
87: define('SEARCH_ROUTE', FALSE);
88: }
89: define('ROUTE_COLOR', '0000FF'); //経路の色(RGB)
90: define('ROUTE_WEIGHT', 3); //経路の太さ
定数 FLAG_DISTANCE が TRUE の時は、経由地点間の距離明細を一覧表に表示する。一覧表の行数が不定になるためページのレイアウトが崩れるような、FALSE にすることで、距離の合計値のみを表示するようになる。
定数 MAX_MARKERS はマーカーの最大数を定義している。プログラム上、マーカーの数に上限はないのいだが、処理系に負荷をかけない程度の数に抑えておいた方が無難だろう。

定数 CENTERING を TRUE にすると、住所・ランドマーク検索後に、ヒットした位置に地図がセンタリングする。
定数 SEARCH_ROUTE を TRUE にすると、地図サービスの経路探索機能を利用できるようにする。前述のように、Googleマップでは経路探索機能は課金率が高いことから、FALSE にすることで、経路探索は利用せず、関連スクリプトも出力しなくなる。
解説:大圏航路の軌跡
373: /**
374: * 2地点間の大圏航路軌跡を求める
375: * @param float long_a, lati_a A地点の経度,緯度(世界測地系)
376: * @param float long_b, lati_b B地点の経度,緯度(世界測地系)
377: * @return array points 軌跡の座標を格納
378: * [n]['longitude'] 軌跡の経度(世界測地系)
379: * [n]['latitude'] 軌跡の緯度(世界測地系)
380: */
381: function greatCircleSailing(long_a, lati_a, long_b, lati_b) {
382: let points = Array();
383: lati_a = deg2rad(lati_a);
384: long_a = deg2rad(long_a);
385: lati_b = deg2rad(lati_b);
386: long_b = deg2rad(long_b);
387:
388: let l1 = (long_a >= 0) ? long_a : 2 * Math.PI + long_a;
389: let l2 = (long_b >= 0) ? long_b : 2 * Math.PI + long_b;
390: let dd = l2 - l1;
391: let tt = 0.01; //経度方向の増分
392: if (dd < 0) {
393: dd = Math.abs(dd);
394: tt = -tt;
395: } else if (dd > Math.PI) {
396: [lati_a, lati_b] = [lati_b, lati_a];
397: [long_a, long_b] = [long_b, long_a];
398: dd = 2 * Math.PI - dd;
399: }
400: let st = 0.0;
401: let cnt = 0;
402:
403: //軌跡の計算
404: let latitude = lati_a;
405: let longitude = long_a;
406: while (st < dd) {
407: if (latitude >= 0.5 * Math.PI) latitude -= Math.PI;
408: if (longitude >= 1.0 * Math.PI) longitude = longitude - Math.PI;
409: points[cnt] = {
410: "latitude" : rad2deg(latitude),
411: "longitude" : rad2deg(longitude)
412: };
413: longitude += tt;
414: if (longitude >= Math.PI) longitude = longitude - 2 * Math.PI;
415: 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));
416: latitude = Math.atan(latitude);
417: if (Math.sin(lati_a) / Math.sin(lati_b) < 0) latitude += Math.PI;
418:
419: st += Math.abs(tt);
420: cnt++;
421: }
422: points[cnt] = {
423: "latitude" : rad2deg(lati_b),
424: "longitude" : rad2deg(long_b)
425: };
426: cnt++;
427:
428: return points;
429: }
座標数ではなく、座標そのものの配列を戻す形に変更している。
解説:大圏航路の距離
354: /**
355: * 2地点間の大圏航路距離を求める
356: * @param float long_a, lati_a A地点の経度,緯度(世界測地系)
357: * @param float long_b, lati_b B地点の経度,緯度(世界測地系)
358: * @return float 大圏航路距離(km)
359: */
360: function greatCircleDistance(long_a, lati_a, long_b, lati_b) {
361: lati_a = deg2rad(lati_a);
362: long_a = deg2rad(long_a);
363: lati_b = deg2rad(lati_b);
364: long_b = deg2rad(long_b);
365:
366: //距離の計算
367: let ll = Math.abs(long_b - long_a);
368: let distance = 6371.0 * Math.acos(Math.sin(lati_a) * Math.sin(lati_b) + Math.cos(lati_a) * Math.cos(lati_b) * Math.cos(ll));
369:
370: return distance;
371: }
解説:距離TABLE、経路TABLE
252: /**
253: * 距離TABLEを表示
254: * @param Array arr 距離配列
255: * @return なし
256: */
257: function writeDtable(arr) {
258: let flag={$flagDistance}; //TRUE:距離明細を表示/FALSE:非表示
259: let d0, d1, d2, d3;
260: let html = '';
261:
262: //距離明細(地点間)
263: if (flag) {
264: let pcount = Number(document.getElementById('pcount').value);
265: for (let i = 1; i < pcount; i++) {
266: d0 = arr[i];
267: d1 = roundf(d0, 1);
268: d2 = roundf(km2mi(d0), 1);
269: d3 = roundf(km2nm(d0), 1);
270: html += '<tr>';
271: html += '<th>' + i + '</th>';
272: html += '<td>' + d1 + '</td>';
273: html += '<td>' + d2 + '</td>';
274: html += '<td>' + d3 + '</td>';
275: html += '</tr>';
276: }
277: }
278: //距離合計
279: d0 = sum(arr);
280: d1 = roundf(d0, 1);
281: d2 = roundf(km2mi(d0), 1);
282: d3 = roundf(km2nm(d0), 1);
283: html += '<tr>';
284: html += '<th>合計</th>';
285: html += '<td>' + d1 + '</td>';
286: html += '<td>' + d2 + '</td>';
287: html += '<td>' + d3 + '</td>';
288: html += '</tr>';
289:
290: //TABLEへ表示
291: let dtable = document.getElementById('dtable');
292: dtable.innerHTML = html;
293: }
解説:Google JavaScriptマップ用スクリプト(マーカー・軌跡)
453: /**
454: * Google JavaScriptマップ用スクリプト(マーカー・軌跡)を生成する.
455: * @param string $points マーカーの初期座標(JSONテキスト)
456: * @return string JavaScript
457: */
458: function jsGmap($points) {
459: $maxMarkers = MAX_MARKERS; //マーカーの最大数
460: $color = '#' . LINE_COLOR;
461: $weight = LINE_WEIGHT;
462:
463: $js =<<< EOT
464: let Dmarkers = Array(); //マーカー格納用
465: let Dlines = Array(); //軌跡格納用
466: let Distances = Array(); //距離格納用
467: let EventListner; //イベントハンドラ格納用
468:
469: //マーカーの初期状態
470: let points = JSON.parse('{$points}');
471: document.getElementById('pcount').value = 0;
472: for (let i = 0; i < points.length; i++) {
473: _addMarker(points[i].latitude, points[i].longitude);
474: }
475:
476: //イベント設定
477: EventListner = google.maps.event.addListener(map, 'click', addMarker);
478: document.getElementById('delmarker').addEventListener('click', delMarker, false);
479: document.getElementById('fit').addEventListener('click', fitting, false);
480:
481: /**
482: * マーカーのドラッグエンド処理
483: * @param なし
484: * @return なし
485: */
486: function dragendMarker() {
487: let pcount = Number(document.getElementById('pcount').value);
488:
489: //大圏航法軌跡の再描画と距離の再計算
490: let m0, m1;
491: for (let i = 1; i < pcount; i++) {
492: m0 = Dmarkers[i - 1].getPosition();
493: m1 = Dmarkers[i].getPosition();
494: Distances[i] = greatCircleDistance(m0.lng(), m0.lat(), m1.lng(), m1.lat());
495: let points = greatCircleSailing(m0.lng(), m0.lat(), m1.lng(), m1.lat());
496: let line = [];
497: for (let j = 0; j < points.length; j++) {
498: line[j] = new google.maps.LatLng(points[j].latitude, points[j].longitude);
499: }
500: //古い軌跡を削除
501: Dlines[i].setMap(null);
502: //新しい軌跡を描く
503: Dlines[i] = new google.maps.Polyline({
504: map: map,
505: path: line,
506: strokeColor: '{$color}',
507: strokeOpacity: 1,
508: strokeWeight: {$weight}
509: });
510: stringifyMarkers();
511: }
512: writeDtable(Distances); //距離TABLE更新
513: }
514:
515: /**
516: * 軌跡の最後にマーカーを1つ追加する
517: * @param LatLng point 追加する座標
518: * @return なし
519: */
520: function addMarker(e) {
521: let lat = e.latLng.lat();
522: let lng = e.latLng.lng();
523: _addMarker(lat, lng);
524: }
525:
526: /**
527: * 軌跡の最後にマーカーを1つ追加する(下請け)
528: * @param float lat 緯度
529: * @param float lng 経度
530: * @return なし
531: */
532: function _addMarker(lat, lng) {
533: let dd;
534: let pcount = Number(document.getElementById('pcount').value);
535: if (pcount >= {$maxMarkers}) return; //マーカー数の上限チェック
536: let m0, m1;
537:
538: //マーカー追加
539: Dmarkers[pcount] = new google.maps.Marker({
540: map: map,
541: position: new google.maps.LatLng(lat, lng),
542: icon: {
543: fillColor: '#FF4500',
544: fillOpacity: 0.8,
545: path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
546: scale: 4,
547: strokeColor: "#FF4500",
548: strokeWeight: 1.0
549: }
550: });
551:
552: //ドラッグエンド・イベント処理
553: Dmarkers[pcount].setDraggable(true);
554: Dmarkers[pcount].addListener('dragend', dragendMarker);
555:
556: //大圏航法軌跡の描画と距離の再計算
557: if (pcount >= 1) {
558: m0 = Dmarkers[pcount - 1].getPosition();
559: m1 = Dmarkers[pcount - 0].getPosition();
560: let points = greatCircleSailing(m0.lng(), m0.lat(), m1.lng(), m1.lat());
561: let line = [];
562: for (let j = 0; j < points.length; j++) {
563: line[j] = new google.maps.LatLng(points[j].latitude, points[j].longitude);
564: }
565: Dlines[pcount] = new google.maps.Polyline({
566: map: map,
567: path: line,
568: strokeColor: '{$color}',
569: strokeOpacity: 1,
570: strokeWeight: {$weight}
571: });
572: Distances[pcount] = greatCircleDistance(m0.lng(), m0.lat(), m1.lng(), m1.lat());
573: }
574: document.getElementById('pcount').value = pcount + 1;
575: writeDtable(Distances); //距離TABLE更新
576: stringifyMarkers(); //マーカー座標JSON作成
577: }
578:
579: /**
580: * 軌跡の最後のマーカーを1つ削除する
581: * @param なし
582: * @return なし
583: */
584: function delMarker() {
585: let pcount = Number(document.getElementById('pcount').value);
586: if (pcount > 0) pcount--;
587:
588: //最後の地点間距離を削除
589: Distances.pop();
590:
591: //マーカーと軌跡の削除
592: Dmarkers[pcount].setMap(null);
593: Dlines[pcount].setMap(null);
594: stringifyMarkers(); //マーカー座標JSON作成
595: document.getElementById('pcount').value = pcount;
596: writeDtable(Distances); //距離TABLE更新
597: }
598:
599: /**
600: * マーカーの位置情報をJSON文字列に変換して代入
601: * @param なし
602: * @return なし
603: */
604: function stringifyMarkers() {
605: let points = [];
606: let pcount = Number(document.getElementById('pcount').value);
607: for (let i = 0; i < pcount; i++) {
608: points[i] = {
609: 'latitude' : Dmarkers[i].getPosition().lat(),
610: 'longitude' : Dmarkers[i].getPosition().lng()
611: };
612: }
613: document.getElementById('points').value = JSON.stringify(points);
614: }
615:
616: /**
617: * 地図を最適サイズにフィットする
618: * @param なし
619: * @return なし
620: */
621: function fitting() {
622: let lat, lng, lat0, lng0, lat1, lng1;
623: let pcount = Number(document.getElementById('pcount').value);
624: //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
625: for (let i = 0; i < pcount; i++) {
626: lat = Dmarkers[i].getPosition().lat();
627: lng = Dmarkers[i].getPosition().lng();
628: if (i == 0) {
629: lat0 = lat1 = lat;
630: lng0 = lng1 = lng;
631: }
632: if (lat0 > lat) lat0 = lat;
633: if (lng0 > lng) lng0 = lng;
634: if (lat1 < lat) lat1 = lat;
635: if (lng1 < lng) lng1 = lng;
636: }
637: let bounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat0, lng0), new google.maps.LatLng(lat1, lng1));
638: map.fitBounds(bounds); //地図表示の最適化
639: }
640:
641: EOT;
642: return $js;
643: }

冒頭では、複数のマーカー情報を格納する配列変数 Dmarkers、マーカー間の軌跡を格納する配列変数 Dlines、マーカー間の距離を格納する配列変数 Distances、マップ・クリック時のイベントハンドラを格納する版数 EventListner を、それぞれグローバル変数として用意する。
サーバサイドに処理を渡す際、マーカーの位置情報はJSONテキストに書き出して保存している。このJSONを読み込み、個々のマーカー座標に分離し、後述するマーカー追加関数 _addMarker に渡すことで、初期状態のマーカーと軌跡を描く。

マップをクリックした時のイベントは、後述するマーカー追加関数 addMarker に紐付ける。
「1つ戻る」ボタンをクリックした時のイベントは、後述するマーカー削除関数 delMarker に紐付ける。
「フィット」ボタンをクリックした時のイベントは、後述する地図の最適表示関数 fittng に紐付ける。
515: /**
516: * 軌跡の最後にマーカーを1つ追加する
517: * @param LatLng point 追加する座標
518: * @return なし
519: */
520: function addMarker(e) {
521: let lat = e.latLng.lat();
522: let lng = e.latLng.lng();
523: _addMarker(lat, lng);
524: }

現在のマーカー数は、HTMLのFORMテキスト pcount に格納してある。
まず、この値を参照し、最大数を超えていないかどうかをチェックする。

続いてマーカーを追加する。ここでは icon オブジェクトに矢印形のアイコンを定義しているが、形状は自由に変更して構わない。
マーカーはドラッグできるようにして、ドラッグエンドの時の処理を紐付けておく。

次に、直前のマーカーとの間に大圏航路軌跡を描き、その距離を計算する。
最後にマーカー数をアップカウントし、距離TABLEの更新とマーカー座標JSONの再作成を行っておく。
481: /**
482: * マーカーのドラッグエンド処理
483: * @param なし
484: * @return なし
485: */
486: function dragendMarker() {
487: let pcount = Number(document.getElementById('pcount').value);
488:
489: //大圏航法軌跡の再描画と距離の再計算
490: let m0, m1;
491: for (let i = 1; i < pcount; i++) {
492: m0 = Dmarkers[i - 1].getPosition();
493: m1 = Dmarkers[i].getPosition();
494: Distances[i] = greatCircleDistance(m0.lng(), m0.lat(), m1.lng(), m1.lat());
495: let points = greatCircleSailing(m0.lng(), m0.lat(), m1.lng(), m1.lat());
496: let line = [];
497: for (let j = 0; j < points.length; j++) {
498: line[j] = new google.maps.LatLng(points[j].latitude, points[j].longitude);
499: }
500: //古い軌跡を削除
501: Dlines[i].setMap(null);
502: //新しい軌跡を描く
503: Dlines[i] = new google.maps.Polyline({
504: map: map,
505: path: line,
506: strokeColor: '{$color}',
507: strokeOpacity: 1,
508: strokeWeight: {$weight}
509: });
510: stringifyMarkers();
511: }
512: writeDtable(Distances); //距離TABLE更新
513: }
579: /**
580: * 軌跡の最後のマーカーを1つ削除する
581: * @param なし
582: * @return なし
583: */
584: function delMarker() {
585: let pcount = Number(document.getElementById('pcount').value);
586: if (pcount > 0) pcount--;
587:
588: //最後の地点間距離を削除
589: Distances.pop();
590:
591: //マーカーと軌跡の削除
592: Dmarkers[pcount].setMap(null);
593: Dlines[pcount].setMap(null);
594: stringifyMarkers(); //マーカー座標JSON作成
595: document.getElementById('pcount').value = pcount;
596: writeDtable(Distances); //距離TABLE更新
597: }
あわせて、関連する配列変数の最後の要素を削除し、マーカー数をダウンカウントと、距離TABLEの更新とマーカー座標JSONの再作成を行う。
599: /**
600: * マーカーの位置情報をJSON文字列に変換して代入
601: * @param なし
602: * @return なし
603: */
604: function stringifyMarkers() {
605: let points = [];
606: let pcount = Number(document.getElementById('pcount').value);
607: for (let i = 0; i < pcount; i++) {
608: points[i] = {
609: 'latitude' : Dmarkers[i].getPosition().lat(),
610: 'longitude' : Dmarkers[i].getPosition().lng()
611: };
612: }
613: document.getElementById('points').value = JSON.stringify(points);
614: }
処理がサーバサイドに移った時、この情報をPOST渡しすることでマーカー座標が失われないようにする。
616: /**
617: * 地図を最適サイズにフィットする
618: * @param なし
619: * @return なし
620: */
621: function fitting() {
622: let lat, lng, lat0, lng0, lat1, lng1;
623: let pcount = Number(document.getElementById('pcount').value);
624: //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
625: for (let i = 0; i < pcount; i++) {
626: lat = Dmarkers[i].getPosition().lat();
627: lng = Dmarkers[i].getPosition().lng();
628: if (i == 0) {
629: lat0 = lat1 = lat;
630: lng0 = lng1 = lng;
631: }
632: if (lat0 > lat) lat0 = lat;
633: if (lng0 > lng) lng0 = lng;
634: if (lat1 < lat) lat1 = lat;
635: if (lng1 < lng) lng1 = lng;
636: }
637: let bounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat0, lng0), new google.maps.LatLng(lat1, lng1));
638: map.fitBounds(bounds); //地図表示の最適化
639: }
変数 Dmarkers をスキャンして、マーカー群の南西端と北東端を求め、これをGooleマップAPIの fitBounds メソッドに渡すことで実現している。
後述する経路探索では、経路の南西端と北東端が求めにくいため、経路探索中にフィット機能は使用できないようにしている。
解説:Google JavaScriptマップ用スクリプト(経路探索)
645: /**
646: * Google JavaScriptマップ用スクリプトを(経路探索)を生成する.
647: * @param なし
648: * @return string JavaScript
649: */
650: function jsGroute() {
651: $route_color = '#' . ROUTE_COLOR;
652: $route_weight = ROUTE_WEIGHT;
653:
654: $js =<<< EOT
655: let LineRoute; //経路ライン
656:
657: //イベント設定
658: document.getElementById('route').addEventListener('click', drawRoute, false);
659: document.getElementById('redraw').addEventListener('click', delRoute, false);
660: setButtons(['redraw'], 'disable');
661:
662: /**
663: * 経路探索し,軌跡を消去
664: * @param なし
665: * @return なし
666: * 参考 https://qiita.com/nissuk/items/7e0225ae3764d9f1bef7
667: */
668: function drawRoute() {
669: //クリック・イベント削除
670: google.maps.event.removeListener(EventListner);
671: setButtons(['exec', 'delmarker', 'fit', 'route'], 'disable');
672: setButtons(['redraw'], 'enable');
673:
674: let pcount = Number(document.getElementById('pcount').value);
675: if (pcount < 2) return;
676:
677: latlngs = [];
678: for (let i = 0; i < pcount; i++) {
679: latlngs[i] = Dmarkers[i].getPosition();
680: if (i > 0) Dlines[i].setMap(null); //軌跡を消去
681: }
682: //経由地点8カ所以上は分割して経路探索
683: let d = new google.maps.DirectionsService(); //経路探索オブジェクト
684: let origin = null; //出発地
685: let waypoints = []; //経由地
686: let dest = null; //目的地
687: let resultMap = {}; //分割経路探索の結果
688: let requestIndex = 0; //分割検索番号
689: let done = 0; //検索完了数
690: for (let i = 0, len = latlngs.length; i < len; i++) {
691: //検索の最初
692: if (origin == null) {
693: origin = latlngs[i];
694: //経由地が8カ所になった or 目的地→経路探索
695: } else if (waypoints.length == 8 || i == len - 1) {
696: dest = latlngs[i];
697: (function(index) {
698: //検索条件
699: let request = {
700: origin: origin, //出発地
701: waypoints: waypoints, //経由地
702: destination: dest, //目的地
703: travelMode: google.maps.DirectionsTravelMode.DRIVING
704: //移動手段:自動車
705: };
706: //経路探索実行
707: d.route(request, function(result, status) {
708: //経路データ保持
709: if (status == google.maps.DirectionsStatus.OK) {
710: resultMap[index] = result;
711: done++;
712: } else {
713: console.log(status); //デバッグ用
714: }
715: });
716: })(requestIndex);
717:
718: requestIndex++;
719: origin = latlngs[i]; //目的地を次の出発地へ
720: waypoints = [];
721: //それ以外→waypointsに地点を追加
722: } else {
723: waypoints.push({ location: latlngs[i], stopover: true });
724: }
725: }
726: //経路を配列へ
727: let sid = setInterval(function() {
728: //分割したすべての検索が完了するまで待つ
729: if (requestIndex > done) return;
730: clearInterval(sid);
731:
732: //すべての経由座標を順番に取得して平坦な配列に置換
733: let path = [];
734: let result;
735: let o = { 'TotalDistance' : 0, 'TotalTime' : 0 };
736: for (let i = 0, len = requestIndex; i < len; i++) {
737: result = resultMap[i]; //検索結果
738: let legs = result.routes[0].legs; //Array<DirectionsLeg>
739: for (let li = 0, llen = legs.length; li < llen; li++) {
740: let leg = legs[li]; //DirectionLeg
741: o.TotalDistance += leg.distance.value; //経由距離
742: o.TotalTime += (leg.duration.value / 60); //経由時間
743: let steps = leg.steps; //Array<DirectionsStep>
744: //DirectionsStepが持っているpathを取得して平坦(2次元配列→1次元配列)に
745: let _path = steps.map(function(step){ return step.path })
746: .reduce(function(all, paths){ return all.concat(paths) });
747: path = path.concat(_path);
748: }
749: }
750: //経路描画
751: LineRoute = new google.maps.Polyline({
752: map: map,
753: strokeColor: '{$route_color}',
754: strokeOpacity: 0.8,
755: strokeWeight: {$route_weight},
756: path: path
757: });
758: //小数点以下を丸める
759: o.TotalDistance = Math.round(o.TotalDistance);
760: o.TotalTime = Math.round(o.TotalTime);
761: writeRtable(o);
762: }, 1000);
763: }
764:
765: /**
766: * 経路を消去し,軌跡を再描画
767: * @param なし
768: * @return なし
769: */
770: function delRoute() {
771: //経路消去
772: LineRoute.setMap(null); //経路消去
773: //軌跡の再描画
774: let pcount = Number(document.getElementById('pcount').value);
775: for (let i = 1; i < pcount; i++) {
776: Dlines[i].setMap(map);
777: };
778: //経路TABLE初期化
779: initRtable();
780: //クリック・イベント再開
781: EventListner = google.maps.event.addListener(map, 'click', addMarker);
782: setButtons(['exec', 'delmarker', 'fit', 'route'], 'enable');
783: setButtons(['redraw'], 'disable');
784: }
785:
786: EOT;
787: return $js;
788: }
662: /**
663: * 経路探索し,軌跡を消去
664: * @param なし
665: * @return なし
666: * 参考 https://qiita.com/nissuk/items/7e0225ae3764d9f1bef7
667: */
668: function drawRoute() {
669: //クリック・イベント削除
670: google.maps.event.removeListener(EventListner);
671: setButtons(['exec', 'delmarker', 'fit', 'route'], 'disable');
672: setButtons(['redraw'], 'enable');
673:
674: let pcount = Number(document.getElementById('pcount').value);
675: if (pcount < 2) return;
676:
677: latlngs = [];
678: for (let i = 0; i < pcount; i++) {
679: latlngs[i] = Dmarkers[i].getPosition();
680: if (i > 0) Dlines[i].setMap(null); //軌跡を消去
681: }
682: //経由地点8カ所以上は分割して経路探索
683: let d = new google.maps.DirectionsService(); //経路探索オブジェクト
684: let origin = null; //出発地
685: let waypoints = []; //経由地
686: let dest = null; //目的地
687: let resultMap = {}; //分割経路探索の結果
688: let requestIndex = 0; //分割検索番号
689: let done = 0; //検索完了数
690: for (let i = 0, len = latlngs.length; i < len; i++) {
691: //検索の最初
692: if (origin == null) {
693: origin = latlngs[i];
694: //経由地が8カ所になった or 目的地→経路探索
695: } else if (waypoints.length == 8 || i == len - 1) {
696: dest = latlngs[i];
697: (function(index) {
698: //検索条件
699: let request = {
700: origin: origin, //出発地
701: waypoints: waypoints, //経由地
702: destination: dest, //目的地
703: travelMode: google.maps.DirectionsTravelMode.DRIVING
704: //移動手段:自動車
705: };
706: //経路探索実行
707: d.route(request, function(result, status) {
708: //経路データ保持
709: if (status == google.maps.DirectionsStatus.OK) {
710: resultMap[index] = result;
711: done++;
712: } else {
713: console.log(status); //デバッグ用
714: }
715: });
716: })(requestIndex);
717:
718: requestIndex++;
719: origin = latlngs[i]; //目的地を次の出発地へ
720: waypoints = [];
721: //それ以外→waypointsに地点を追加
722: } else {
723: waypoints.push({ location: latlngs[i], stopover: true });
724: }
725: }
726: //経路を配列へ
727: let sid = setInterval(function() {
728: //分割したすべての検索が完了するまで待つ
729: if (requestIndex > done) return;
730: clearInterval(sid);
731:
732: //すべての経由座標を順番に取得して平坦な配列に置換
733: let path = [];
734: let result;
735: let o = { 'TotalDistance' : 0, 'TotalTime' : 0 };
736: for (let i = 0, len = requestIndex; i < len; i++) {
737: result = resultMap[i]; //検索結果
738: let legs = result.routes[0].legs; //Array<DirectionsLeg>
739: for (let li = 0, llen = legs.length; li < llen; li++) {
740: let leg = legs[li]; //DirectionLeg
741: o.TotalDistance += leg.distance.value; //経由距離
742: o.TotalTime += (leg.duration.value / 60); //経由時間
743: let steps = leg.steps; //Array<DirectionsStep>
744: //DirectionsStepが持っているpathを取得して平坦(2次元配列→1次元配列)に
745: let _path = steps.map(function(step){ return step.path })
746: .reduce(function(all, paths){ return all.concat(paths) });
747: path = path.concat(_path);
748: }
749: }
750: //経路描画
751: LineRoute = new google.maps.Polyline({
752: map: map,
753: strokeColor: '{$route_color}',
754: strokeOpacity: 0.8,
755: strokeWeight: {$route_weight},
756: path: path
757: });
758: //小数点以下を丸める
759: o.TotalDistance = Math.round(o.TotalDistance);
760: o.TotalTime = Math.round(o.TotalTime);
761: writeRtable(o);
762: }, 1000);
763: }
経路は変数legsに入ってくるので、これをマップ上に描画するとともに距離積算、経由時間積算を行う。
765: /**
766: * 経路を消去し,軌跡を再描画
767: * @param なし
768: * @return なし
769: */
770: function delRoute() {
771: //経路消去
772: LineRoute.setMap(null); //経路消去
773: //軌跡の再描画
774: let pcount = Number(document.getElementById('pcount').value);
775: for (let i = 1; i < pcount; i++) {
776: Dlines[i].setMap(map);
777: };
778: //経路TABLE初期化
779: initRtable();
780: //クリック・イベント再開
781: EventListner = google.maps.event.addListener(map, 'click', addMarker);
782: setButtons(['exec', 'delmarker', 'fit', 'route'], 'enable');
783: setButtons(['redraw'], 'disable');
784: }
解説:Leaflet用スクリプト(マーカー・軌跡)
1037: /**
1038: * Leafletマップ用スクリプト(マーカー・軌跡)を生成する.
1039: * @param string $points マーカーの初期座標(JSONテキスト)
1040: * @return string JavaScript
1041: */
1042: function jsLeaflet($points) {
1043: $maxMarkers = MAX_MARKERS; //マーカーの最大数
1044: $color = '#' . LINE_COLOR;
1045: $weight = LINE_WEIGHT;
1046:
1047: $js =<<< EOT
1048: let Dmarkers = Array(); //マーカー格納用
1049: let Dlines = Array(); //軌跡格納用
1050: let Distances = Array(); //距離格納用
1051:
1052: //マーカーの初期状態
1053: let points = JSON.parse('{$points}');
1054: document.getElementById('pcount').value = 0;
1055: for (let i = 0; i < points.length; i++) {
1056: _addMarker(Array(points[i].latitude, points[i].longitude));
1057: }
1058:
1059: //イベント設定
1060: map.on('click', addMarker);
1061: document.getElementById('delmarker').addEventListener('click', delMarker, false);
1062: document.getElementById('fit').addEventListener('click', fitting, false);
1063:
1064: /**
1065: * マーカーのドラッグエンド処理
1066: * @param なし
1067: * @return なし
1068: */
1069: function dragendMarker() {
1070: let pcount = Number(document.getElementById('pcount').value);
1071:
1072: //大圏航法軌跡の再描画と距離の再計算
1073: let m0, m1;
1074: for (let i = 1; i < pcount; i++) {
1075: m0 = Dmarkers[i - 1].getLatLng();
1076: m1 = Dmarkers[i].getLatLng();
1077: Distances[i] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1078: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1079: let line = [];
1080: for (let j = 0; j < points.length; j++) {
1081: line[j] = Array(points[j].latitude, points[j].longitude);
1082: }
1083: //古い軌跡を削除
1084: map.removeLayer(Dlines[i]);
1085: Dlines[i] = null;
1086: //新しい軌跡を描く
1087: Dlines[i] = L.polyline(
1088: line, {
1089: 'color': '{$color}',
1090: 'opacity': 1,
1091: 'weight': {$weight}
1092: }).addTo(map);
1093: stringifyMarkers();
1094: }
1095: writeDtable(Distances); //距離TABLE更新
1096: }
1097:
1098: /**
1099: * 軌跡の最後にマーカーを1つ追加する
1100: * @param LatLng point 追加する座標
1101: * @return なし
1102: */
1103: function addMarker(e) {
1104: _addMarker(e.latlng);
1105: }
1106:
1107: /**
1108: * 軌跡の最後にマーカーを1つ追加する(下請け)
1109: * @param float lat 緯度
1110: * @param float lng 経度
1111: * @return なし
1112: */
1113: function _addMarker(latlng) {
1114: let pcount = Number(document.getElementById('pcount').value);
1115: if (pcount >= {$maxMarkers}) return; //マーカー数の上限チェック
1116:
1117: //マーカー追加
1118: Dmarkers[pcount] = new L.marker(
1119: latlng, {
1120: draggable: true,
1121: }).addTo(map).on('dragend', dragendMarker);
1122:
1123: //大圏航法軌跡の描画と距離の再計算
1124: if (pcount >= 1) {
1125: let m0 = Dmarkers[pcount - 1].getLatLng();
1126: let m1 = Dmarkers[pcount - 0].getLatLng();
1127: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1128: let line = [];
1129: for (let j = 0; j < points.length; j++) {
1130: line[j] = Array(points[j].latitude, points[j].longitude);
1131: }
1132: Dlines[pcount] = L.polyline(
1133: line, {
1134: 'color': '{$color}',
1135: 'opacity': 1,
1136: 'weight': {$weight}
1137: }).addTo(map);
1138: Distances[pcount] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1139: }
1140: document.getElementById('pcount').value = pcount + 1;
1141: writeDtable(Distances); //距離TABLE更新
1142: stringifyMarkers(); //マーカー座標JSON作成
1143: }
1144:
1145: /**
1146: * 軌跡の最後のマーカーを1つ削除する
1147: * @param なし
1148: * @return なし
1149: */
1150: function delMarker() {
1151: let pcount = Number(document.getElementById('pcount').value);
1152: if (pcount > 0) pcount--;
1153: Dmarkers[pcount].dragging.disable(); //ドラッグ禁止
1154:
1155: //最後の地点距離を削除
1156: Distances.pop();
1157:
1158: //マーカーと軌跡の削除
1159: map.removeLayer(Dmarkers[pcount]);
1160: map.removeLayer(Dlines[pcount]);
1161: stringifyMarkers();
1162: document.getElementById('pcount').value = pcount;
1163: writeDtable(Distances); //距離TABLE更新
1164: }
1165:
1166: /**
1167: * マーカーの位置情報をJSON文字列に変換して代入
1168: * @param なし
1169: * @return なし
1170: */
1171: function stringifyMarkers() {
1172: let points = [];
1173: let pcount = Number(document.getElementById('pcount').value);
1174: for (let i = 0; i < pcount; i++) {
1175: points[i] = {
1176: 'latitude' : Dmarkers[i].getLatLng().lat,
1177: 'longitude' : Dmarkers[i].getLatLng().lng
1178: };
1179: }
1180: document.getElementById('points').value = JSON.stringify(points);
1181: }
1182:
1183: /**
1184: * 地図を最適サイズにフィットする
1185: * @param なし
1186: * @return なし
1187: */
1188: function fitting() {
1189: let lat0, lng0, lat1, lng1;
1190: let pcount = Number(document.getElementById('pcount').value);
1191: //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
1192: for (let i = 0; i < pcount; i++) {
1193: points[i] = {
1194: 'latitude' : Dmarkers[i].getLatLng().lat,
1195: 'longitude' : Dmarkers[i].getLatLng().lng
1196: };
1197: if (i == 0) {
1198: lat0 = lat1 = points[i].latitude;
1199: lng0 = lng1 = points[i].longitude;
1200: }
1201: if (lat0 > points[i].latitude) lat0 = points[i].latitude;
1202: if (lng0 > points[i].longitude) lng0 = points[i].longitude;
1203: if (lat1 < points[i].latitude) lat1 = points[i].latitude;
1204: if (lng1 < points[i].longitude) lng1 = points[i].longitude;
1205: }
1206: map.fitBounds([[lat0, lng0], [lat1, lng1]]);
1207: }
1208:
1209: EOT;
1210: return $js;
1211: }
なお、Leafletでは経路探索はできない。オープンストリートマップ側でフリーの経路探索エンジンが用意されているという情報もあったが、複数のサービスをあたってみたが、動作していなかったり、日本国内ではうまく動作しないようなので実装を見送った。
1098: /**
1099: * 軌跡の最後にマーカーを1つ追加する
1100: * @param LatLng point 追加する座標
1101: * @return なし
1102: */
1103: function addMarker(e) {
1104: _addMarker(e.latlng);
1105: }
1106:
1107: /**
1108: * 軌跡の最後にマーカーを1つ追加する(下請け)
1109: * @param float lat 緯度
1110: * @param float lng 経度
1111: * @return なし
1112: */
1113: function _addMarker(latlng) {
1114: let pcount = Number(document.getElementById('pcount').value);
1115: if (pcount >= {$maxMarkers}) return; //マーカー数の上限チェック
1116:
1117: //マーカー追加
1118: Dmarkers[pcount] = new L.marker(
1119: latlng, {
1120: draggable: true,
1121: }).addTo(map).on('dragend', dragendMarker);
1122:
1123: //大圏航法軌跡の描画と距離の再計算
1124: if (pcount >= 1) {
1125: let m0 = Dmarkers[pcount - 1].getLatLng();
1126: let m1 = Dmarkers[pcount - 0].getLatLng();
1127: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1128: let line = [];
1129: for (let j = 0; j < points.length; j++) {
1130: line[j] = Array(points[j].latitude, points[j].longitude);
1131: }
1132: Dlines[pcount] = L.polyline(
1133: line, {
1134: 'color': '{$color}',
1135: 'opacity': 1,
1136: 'weight': {$weight}
1137: }).addTo(map);
1138: Distances[pcount] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1139: }
1140: document.getElementById('pcount').value = pcount + 1;
1141: writeDtable(Distances); //距離TABLE更新
1142: stringifyMarkers(); //マーカー座標JSON作成
1143: }
1064: /**
1065: * マーカーのドラッグエンド処理
1066: * @param なし
1067: * @return なし
1068: */
1069: function dragendMarker() {
1070: let pcount = Number(document.getElementById('pcount').value);
1071:
1072: //大圏航法軌跡の再描画と距離の再計算
1073: let m0, m1;
1074: for (let i = 1; i < pcount; i++) {
1075: m0 = Dmarkers[i - 1].getLatLng();
1076: m1 = Dmarkers[i].getLatLng();
1077: Distances[i] = greatCircleDistance(m0.lng, m0.lat, m1.lng, m1.lat);
1078: let points = greatCircleSailing(m0.lng, m0.lat, m1.lng, m1.lat);
1079: let line = [];
1080: for (let j = 0; j < points.length; j++) {
1081: line[j] = Array(points[j].latitude, points[j].longitude);
1082: }
1083: //古い軌跡を削除
1084: map.removeLayer(Dlines[i]);
1085: Dlines[i] = null;
1086: //新しい軌跡を描く
1087: Dlines[i] = L.polyline(
1088: line, {
1089: 'color': '{$color}',
1090: 'opacity': 1,
1091: 'weight': {$weight}
1092: }).addTo(map);
1093: stringifyMarkers();
1094: }
1095: writeDtable(Distances); //距離TABLE更新
1096: }
1145: /**
1146: * 軌跡の最後のマーカーを1つ削除する
1147: * @param なし
1148: * @return なし
1149: */
1150: function delMarker() {
1151: let pcount = Number(document.getElementById('pcount').value);
1152: if (pcount > 0) pcount--;
1153: Dmarkers[pcount].dragging.disable(); //ドラッグ禁止
1154:
1155: //最後の地点距離を削除
1156: Distances.pop();
1157:
1158: //マーカーと軌跡の削除
1159: map.removeLayer(Dmarkers[pcount]);
1160: map.removeLayer(Dlines[pcount]);
1161: stringifyMarkers();
1162: document.getElementById('pcount').value = pcount;
1163: writeDtable(Distances); //距離TABLE更新
1164: }
あわせて、関連する配列変数の最後の要素を削除し、マーカー数をダウンカウントと、距離TABLEの更新とマーカー座標JSONの再作成を行う。
1167: * マーカーの位置情報をJSON文字列に変換して代入
1168: * @param なし
1169: * @return なし
1170: */
1171: function stringifyMarkers() {
1172: let points = [];
1173: let pcount = Number(document.getElementById('pcount').value);
1174: for (let i = 0; i < pcount; i++) {
1175: points[i] = {
1176: 'latitude' : Dmarkers[i].getLatLng().lat,
1177: 'longitude' : Dmarkers[i].getLatLng().lng
1178: };
1179: }
1180: document.getElementById('points').value = JSON.stringify(points);
1181: }
処理がサーバサイドに移った時、この情報をPOST渡しすることでマーカー座標が失われないようにする。
1183: /**
1184: * 地図を最適サイズにフィットする
1185: * @param なし
1186: * @return なし
1187: */
1188: function fitting() {
1189: let lat0, lng0, lat1, lng1;
1190: let pcount = Number(document.getElementById('pcount').value);
1191: //南西端(lat0, lng0), 北東端(lat1, lng1)を求める
1192: for (let i = 0; i < pcount; i++) {
1193: points[i] = {
1194: 'latitude' : Dmarkers[i].getLatLng().lat,
1195: 'longitude' : Dmarkers[i].getLatLng().lng
1196: };
1197: if (i == 0) {
1198: lat0 = lat1 = points[i].latitude;
1199: lng0 = lng1 = points[i].longitude;
1200: }
1201: if (lat0 > points[i].latitude) lat0 = points[i].latitude;
1202: if (lng0 > points[i].longitude) lng0 = points[i].longitude;
1203: if (lat1 < points[i].latitude) lat1 = points[i].latitude;
1204: if (lng1 < points[i].longitude) lng1 = points[i].longitude;
1205: }
1206: map.fitBounds([[lat0, lng0], [lat1, lng1]]);
1207: }
変数 Dmarkers をスキャンして、マーカー群の南西端と北東端を求め、これをGooleマップAPIの fitBounds メソッドに渡すことで実現している。
解説:住所検索でマーカー追加
1416: //緯度・経度を取得する.
1417: } else if (($errmsg == '') && isButton('exec')) {
1418: if ($query != '') {
1419: list($n, $url) = $pgc->searchPoint3($query, GEOSERVICE, $category);
1420: if ($pgc->iserror()) {
1421: $errmsg = $pgc->geterror();
1422: } else {
1423: //終点マーカーを追加
1424: list($latitude1, $longitude1, $address1) = $pgc->getPoint(1);
1425: $arr = json_decode($points);
1426: $n = ($arr == NULL) ? 0 : count($arr);
1427: $arr[$n]['latitude'] = $latitude1;
1428: $arr[$n]['longitude'] = $longitude1;
1429: $points = json_encode($arr);
1430: //地図のセンタリング
1431: if (CENTERING) {
1432: $latitude0 = $latitude1;
1433: $longitude0 = $longitude1;
1434: }
1435: }
1436: }
1437: }
活用例
参考サイト
- 各種WebAPIの登録方法:ぱふぅ家のホームページ
- PHPで住所・ランドマークから緯度・経度を求める:ぱふぅ家のホームページ
- PHPで大圏航路を描く:ぱふぅ家のホームページ
- 地図上で目的地までの距離を測る:みんなの知識 ちょっと便利帳
複数地点に配置するマーカーをドラッグで移動するインタラクティブな処理はJavaScriptに任せ、PHP側は住所・ランドマーク検索のみとする。地図サービスは、Googleマップと地理院地図・OSM(オープンストリートマップ)を切り替えて使えるようにする。
「Mapion キョリ測」より機能が多い分、プログラムもかなり長くなった。
(2023年8月12日)国土地理院ジオコーディングAPIを利用できるようにした.検索キーの最小・最大長が指定できるようにした.