GPSwalking

2024.11.12(火)Mapツール

これは友人が「ハイキング」や「ウォーキング」「サイクリング」の際に、スマホのGPSで取得した移動記録のGPSログを自分のパソコン内で表示して楽しむアプリとして作成したものです。

一般的にGPSログをHTML内で表示する場合、ブラウザはネット上にある「****.gpx」と通信してデータを受け取り、HTMLで表示します。しかし「****.gpx」がローカルにある場合は、セキュリティ上の理由からこれができない仕組みになっているようです。

友人は自分のホームページを持っていますが、いちいちネットに上げるのも面倒です。
そこで、GPSデータ(****.gpx)を、表示するHTMLファイルにテキストとして読み込んでおいて、それを表示する方法で作りました。

このページのTopに表示している地図とグラフがそれです。
何日分もGPSデータを読み込んで行くことによって、下図のようにデータの一覧表が出来上がっていきますから、過去のデータも表示して見ることが可能です。

(画像はクリックで拡大します)
一覧表

Toolの画面はこんな風です。起動すると「Tool画面1」が開きます。「新しく作成」または「前回の続き」を選択すると「Tool画面2」が開き、「前回の続き」を選択した場合は、前回までのデータを読み込んだ状態で開きます。

tool画面1
tool画面2

フォルダ構成

  • フォルダ構成は右図の通りです。
  • 「common」フォルダには「css」「images」「js」「leaflet」「lightbox」「tablesorter」があります。
  • 「css」には「style.css」のみ。
  • 「images」には地図内でぐるぐる動き回る赤い cicon.png や、地図内の撮影ポイントを示す青い picon.png があります。
  • 「js」にはネットからダウンロードしたグラフを描くhighcharts.jsの他、カレンダーを描く calen.js と、地図とグラフを描く leaflet_map_240804.js とがあります。カレンダーについては過去ページにあります。
  • 「leaflet」は leaflet地図を表示するJavaScriptライブラリの一式で、ネットからダウンロードします。
  • 「lightbox」は画像を拡大してモーダルダイアログにポップアップ表示させることができるJavaScriptライブラリで、これもネットからダウンロードしました。
  • 「tablesorter」はGPX一覧表示の index.html で昇順・降順に並べ替えるものです。これもネットからダウンロードしました。
  • 「diary」は、このツールが作成したMapを日付毎に格納します。日付の8文字の後に1日に2個以上作成する場合に枝番として 1,2などの番号を付けることにしました。
  • 「test」は作業用フォルダで、toolが自動作成します。
  • 「tool」はこのMapTool(mapTool.hta)が入っているフォルダで、他に「photoshifter」と「template」フォルダが入っています。
  • 「photoshifter」はネットからダウンロードしたもので、コマンドラインから起動できる画像縮小ツールです。
  • 「template」には、作成する「map.html」と一覧表の「index.html」のひな形が入っています。このページに表示している形に拘らず自由な形で作成してください。

leaflet_map_240804.js はネットを参考に自作した、leaflet地図とチャートを表示させるためのもので、下記のとおりです。(数字は自分の覚えのための作成年月日です)


var mk_points = [];
function init() {
	var map = L.map('map_canvas');

	//地理院地図の標準地図タイル
	var gsi_std = new L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {
		minZoom: 5,
		maxZoom: 18,
		attribution: "地理院タイル"
	});
	//地理院地図の淡色地図タイル
	var gsi_pale = new L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', {
		minZoom: 5,
		maxZoom: 18,
		attribution: "国土地理院"
	});
	//地理院地図の航空地図タイル
	var gsi_ort = new L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg', {
		minZoom: 5,
		maxZoom: 18,
		attribution: "国土地理院"
	});
	//オープンストリートマップ標準のタイル
	var osm_std = new L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
		minZoom: 2,
		maxZoom: 18,
		attribution: "© OpenStreetMap contributors"
	});
	//オープンストリートマップ日本のタイル
	var osm_jpn = new L.tileLayer('http://tile.openstreetmap.jp/{z}/{x}/{y}.png', {
		minZoom: 2,
		maxZoom: 18,
        attribution: "OpenStreetMap contributors"
	});
	var googl = new L.tileLayer('https://mt1.google.com/vt/lyrs=r&x={x}&y={y}&z={z}', {
		minZoom: 2,
		maxZoom: 18,
		attribution: "Google Map"
	});
	var shade = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/hillshademap/{z}/{x}/{y}.png', {
		opacity: 0.3,
		minZoom: 5,
		maxZoom: 16,
		attribution: '地理院タイル'
	});

	//baseMapsオブジェクトのプロパティに5つのタイルを設定
	var baseMaps = {
		"地理院地図 標準" : gsi_std,
        "地理院地図 淡色" : gsi_pale,
        "地理院地図 写真" : gsi_ort,
        "OpenStreetMap 標準"  : osm_std,
        "OpenStreetMap 日本"  : osm_jpn,
        "Google Map"  : googl,
	};
	var overlay = {
		"国土地理院 陰影起伏図": shade,
	};

	//layersコントロールにbaseMapsオブジェクトを設定して地図に追加
	//コントロール内にプロパティ名が表示される
	L.control.layers(baseMaps, overlay, null, {
		collapsed: true,
	}).addTo(map);
	gsi_std.addTo(map);

// 1---------------------------------------------------------------------------------------
	var cicon = L.icon({
		iconUrl: '../common/images/cicon.png', 
		iconSize: [20, 20],
		iconAnchor: [10, 10],
	});
	var picon = L.icon({
	    iconUrl: '../common/images/picon.png',
	    iconSize:     [14, 14],		// iconのサイズ
		iconAnchor:   [7, 7],		// iconの表示位置の基点
		popupAnchor:  [0, -7]		// popup表示の基点
	});
	var sicon = L.icon({
	    iconUrl: '../common/images/start.png',
	    iconSize:     [16, 16],		// iconのサイズ
		iconAnchor:   [8, 8],		// iconの表示位置の基点
		popupAnchor:  [0, -8]		// popup表示の基点
	});
	var gicon = L.icon({
	    iconUrl: '../common/images/goal.png',
	    iconSize:     [16, 16],		// iconのサイズ
		iconAnchor:   [8, 8],		// iconの表示位置の基点
		popupAnchor:  [0, -8]		// popup表示の基点
	});

// 2---------------------------------------------------------------------------------------
// ポップアップ
// points[i][0]=lat, [1]=lon, [2]=jpeg, [3]=time, [4]=ele, [5]=title, [6]=coment
// .marker1=マーカー画像あり, .marker2=マーカー時間・高度, .marker3=マーカー画像なし, .marker4=マーカーのコメント
	var i,  html;
	for (i = 0; i < markers.length; i++) {
		var result1 = /\.mp4/g.test(markers[i][2]);
		var result2 = /\.jpg|\.png/g.test(markers[i][2]);

		if (result1 == true){
			html = '<div class="marker1">' + markers[i][5] + '<br><video controls playsinline height="147px"><source src="images/' + markers[i][2] + '"></video><br><span class="marker2">撮影時間=' + markers[i][3] + '</span><span class="marker22">GPS高度=' + markers[i][4] + 'm</span></div>';
		} else if (result2 == true) {
			html = '<div class="marker1">' + markers[i][5] + '<br><a href="img/' + markers[i][2] + '" data-lightbox="g1" data-title="' +  markers[i][5] + '<br>' + markers[i][6] + '"><img src="img/' + markers[i][2] + '" width="300"></a></span><br><span class="marker2">撮影時間=' + markers[i][3] + '</span><span class="marker22">高度=' + markers[i][4] + 'm</span></div>';
		} else {
			html = '<div class="marker3">' + markers[i][5] + '<br><span class="marker2">時刻=' + markers[i][3] + '<br>GPS高度=' + markers[i][4] + 'm</span></div>';
		}

		if (i == 0){
			mk_points[i] = L.marker([markers[i][0], markers[i][1]], {icon: sicon}).addTo(map).bindPopup(html);
		} else if (i == markers.length-1){
			mk_points[i] = L.marker([markers[i][0], markers[i][1]], {icon: gicon}).addTo(map).bindPopup(html);
		} else {
			mk_points[i] = L.marker([markers[i][0], markers[i][1]], {icon: picon}).addTo(map).bindPopup(html);
		}
	}

// 3---------------------------------------------------------------------------------------
// Highcharts

	var data = [];
	var chartEle = [];
	var routeLatLng = [];
	var dist = 0;
	var distkm;
	for (var i=0; i<(points.length); i++) {
		var lat = points[i][0];
		var lon = points[i][1];
		var ele = Math.round([points[i][2]]);
//		var time = new Date(points[i][3]).getTime() + 60*60*9*1000;
		var time = new Date(points[i][3]).getTime();
		var timeStr = new Date(time).toLocaleTimeString();

		if (i == 0) { var stTime = (new Date(points[i][3])).getTime() }
		if (i == points.length-1) { var edTime =  (new Date(points[i][3])).getTime() } 
		if (i > 0) {
			var bf_lat = points[i-1][0];
			var bf_lon = points[i-1][1];
			dist += hubeny(bf_lat, bf_lon, lat, lon );
			distkm = Math.round(dist/1000 * 1000) / 1000;
		}
		routeLatLng[i] = [lat, lon];
		chartEle.push({x:distkm, y:ele, lat:lat, lon:lon, timeStr:timeStr},);
	}

	var diffTime = time2str(edTime - stTime);
	var subtitle = '移動距離:' + distkm + 'km' + ' 所要時間:' + diffTime;

	L.polyline(routeLatLng, {
			color: '#ff0066',
			weight: 4,
			opacity: 0.7,
			lineCap: 'round'
	}).addTo(map);

	Bounds = L.polyline(routeLatLng).getBounds();
	map.fitBounds(Bounds);

	cmarker = new L.marker( [points[0][0], points[0][1]], {icon: cicon}).addTo(map);

//	Now create the chart
	Highcharts.chart('chart', {
    	chart: {
        	type: 'area'
    	},

  		xAxis: {
        	title: {
            	text: '距離 (km)'
        	},
			minPadding: 0
		},

		yAxis: {
			startOnTick: true,
			endOnTick: false,
			minPadding: 0,
			title: {
				text: '',
			},
			labels: {
				formatter: function() {
					return this.value +' m';
				},
			}
		},

		title: {
			text: '標高 グラフ'
		},

		subtitle: {
			text: subtitle,
		},

	    tooltip: {
			headerFormat: '',
			pointFormat: '{point.timeStr}<br/>{point.x} km / {point.y} m',
    	    enabled: true
	    },

		legend: {
			enabled: false
		},

	    plotOptions: {
    	    series: {
        	    point: {
            	    events: {
                	    mouseOver: function () {
							if(this.lat && this.lon){
								if(cmarker){
									map.removeLayer( cmarker );
								}
								cmarker = new L.Marker( [this.lat, this.lon], {icon: cicon}).addTo(map);
							}
	                    }
    	            }
        	    },
            	events: {
                	mouseOut: function () {
						map.removeLayer( cmarker );
						cmarker = null;
    	            }
        	    }
	        }
	    },

	    series: [{
			data: chartEle,
			name: '',
			marker: {
				enabled: false,
				states: {
					hover: {
						enabled: true,
						radius: 5
					}
				}
			},
			fillColor: {
				linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
				stops: [
					[0, 'rgba(65, 116, 158, 0.7)'],
					[1, 'rgba(102, 255, 153, 0)']
				]
			},
			lineWidth: 1,
			threshold: null,
			turboThreshold: 0
	    }]
	});
}

// 4---------------------------------------------------------------------------------------

cicon = L.icon({
	iconUrl: './cicon.png', 
	iconSize: [18, 18],
	popupAnchor: [9, 9],
});

function popupOn(id){				//ポップアップを開く
	mk_points[id].openPopup();
}

function time2str(time) {
	var timeHour = time / (1000 * 60 * 60);
	var timeMinute = (timeHour - Math.floor(timeHour)) * 60;
	var timeSecond = (timeMinute - Math.floor(timeMinute)) * 60;
	return Math.floor(timeHour) + ':' + ('00' + Math.floor(timeMinute)).slice(-2) + ':' + ('00' + Math.round(timeSecond)).slice(-2);
}

// 5---------------------------------------------------------------------------------------
// Hubenyの公式
function hubeny(lat1, lng1, lat2, lng2) {
    function rad(deg) {
        return deg * Math.PI / 180;							//degreeをラジアンに変換
    }

    lat1 = rad(lat1);
    lng1 = rad(lng1);
    lat2 = rad(lat2);
    lng2 = rad(lng2);

    var latDiff = lat1 - lat2;								// 緯度差
    var lngDiff = lng1 - lng2;								// 経度差
    var latAvg = (lat1 + lat2) / 2.0;						// 緯度の平均

    var a = 6378137.0;										// 赤道半径(WGS84系)
    var b = 6356752.314245;									// 極半径(WGS84系)
    var e2 = 0.00669437999019758;							// 第一離心率^2 e^2 = (a^2 - b^2) / a^2
    var a1e2 = 6335439.32729246;							// 赤道上の子午線曲率半径  a(1 - e^2)

    var sinLat = Math.sin(latAvg);
    var W2 = 1.0 - e2 * (sinLat * sinLat);

    var M = a1e2 / (Math.sqrt(W2) * W2);					// 子午線曲率半径M
    var N = a / Math.sqrt(W2);								// 卯酉線(ぼうゆうせん)曲率半径N

    t1 = M * latDiff;
    t2 = N * Math.cos(latAvg) * lngDiff;
    d1 = Math.sqrt((t1 * t1) + (t2 * t2));
	return d1;
}
		
  • 最初から1番の区切り線まではマップタイルの種類とそれを切り替える部分です。
  • 次の2番の区切り線までは、アイコンを登録している部分で基本的には cicon.png と picon.png の2種類あればOKです。
  • 3番の区切り線までは picon.png が張り付けられた撮影ポイントの画像をポップアップ表示させる部分です。
  • 4番の区切り線までは highcharts.js を使ってグラフを描く部分で、highcharts のリファレンスやネットの記事を参考に作成しました。
    グラフ内でマウスを動かすと地図内の「cicon」が連動して動く部分は、たいへん面白いと思いました。他にも地点名や、山小屋泊りの 2日目・3日目のフラグを立てることも可能なようですが、まだ実現には至っていません。
  • 5番目の区切り選までは、アイコンのアンカーを指定したりポップアップさせたり、細かい部分です。
  • 最後の部分はヒューベニの公式でGPXポイント間の距離を計算している部分です。ネットから拝借しました。

MapTool本体は、下記のコードですが、既にサポートが終了しているVBScriptで書いています。
なんせ大学生の孫が産まれた時には既に定年退職していた80代前半の爺さんですから、ご容赦ください。(VBScriptは今でもWindowsに搭載されています)

ファイルはHTMLではなくHTAで書いています。これはHTMLだとオフラインで起動すると正常セキュリティの問題があるので、それを避けるためです。
また、コード内の半角の"<"は、タグだと認識されないよう、半角の"&lt;"に書き換えてあります。

HTAのトップから<head>の部分とデータ入力フォームまでは以下の通りです。


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />

<script type="text/javascript" src="../common/js/jquery.min.js"></script>

<title>GPSマップ作成支援ツール Ver24.09.10</title>

<HTA:APPLICATION BORDER="dialog">
<script language="VBS">
																'ここからVBScript
'変数宣言
Dim fomData()													'fomデータ
Dim jpgData(20,4), mkData(22,9), mkLen							'GPSデータ,画像データ,マーカー画像を読み込む配列
Dim gpsPoints()
Dim rootPath, toolPath, gpxPath, gpxPoint, markers, markerPoint, wayPoint

Dim myDateStr1, dateFolder, NewDateFol
Dim cam1_Offset, offsetT1, resizeFlag
Dim myTitle, wDist, wTime
Dim categoNo, category, mainTitle, subTitle, mtAlt, NewDateStr, numGPS
Dim pageTitle, Copyright, walkTime, walkDist, walkData, mkMax
Dim categoArr : categoArr = Array("", "ハイキング", "サイクリング", "ウォーキング", "キャンプ", "その他")

'配列のメモ
'jpgData(i,j)	j: 0=jpgFullPath  1=ファイル名	2=title		3=coment	4=noResize
'mkData(i,x)	x: 0=lat	  1=lon		2=画像File名	3=撮影Time(utc)	4=高度		5=title		6=coment	7=移動距離(km)	8=撮影Time(m秒)	9=DiffTime(仮の撮影Time)


'Dim objFso
Set objFso  = CreateObject("Scripting.FileSystemObject")	'準備 ファイルシステムオブジェクトのセット
Set objFile = objFso.GetFile("240910.hta")					'C:\Users\Owner\Documents\Map\tool\240910.hta

toolFolder = objFile.ParentFolder							'C:\Users\go\Desktop\Map\tool
rootPath = objFso.getParentFolderName(toolFolder)			'C:\Users\go\Desktop\Map

Set objFolder  = objFso.GetFolder(rootPath)					'C:\Users\go\Desktop\Map
Set colFolders = objFolder.SubFolders						'[Map]フォルダ内フォルダコレクション
Set colFiles   = objFolder.Files							'[Map]フォルダ内ファイルコレクション

toolPath = rootPath & "\tool"								'C:\Users\go\Desktop\Map\tool
tmplPath = toolPath & "\template"							'C:\Users\go\Desktop\Map\tool\template
testPath = rootPath & "\test"								'C:\Users\go\Desktop\Map\tool\test

Sub Window_OnLoad
	Call Window.ResizeTo(1200,800)
End Sub


'日付入力フォーム
fm0 = "<div id=""innerhtml_99"">" & _
"<table width=""100%"" border=""0"">" & _
  "<tbody>" & _
    "<tr>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""2""><span class=""large"">■日付</span></td>" & _
      "<td colspan=""3""><input name=""year1"" id=""year1"" size=""1"">年 <input name=""month1"" id=""month1"" size=""1"">月 <input name=""day1"" id=""day1"" size=""1"">日</td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""2"" height=""120""></td>" & _
      "<td colspan=""6"" align=""center""><input type=""button"" value=""[次へ]"" onclick=""fmChange(10)"" class=""largest""></td>" & _
      "<td colspan=""2""></td>" & _
    "</tr>" & _
  "</tbody>" & _
"</table>"


'データ入力フォーム
fm1 = "<div id=""innerhtml_99"">" & _
"<table width=""100%"" border=""1"">" & _
  "<tbody>" & _
    "<tr>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
      "<td></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""2""><span class=""large"">■日付</span></td>" & _
      "<td colspan=""3""><input name=""year1"" id=""year1"" size=""1"" disabled>年 <input name=""month1"" id=""month1"" size=""1"" disabled>月 <input name=""day1"" id=""day1"" size=""1"" disabled>日</td>" & _
      "<td colspan=""7""><input name=""datefol"" id=""datefol"" size=""7"" disabled><span class=""small""> 日付フォルダ名です。(変更はできません)</span></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""2""><span class=""large"">■タイトル</span></td>" & _
      "<td colspan=""2"">カテゴリ<select name=""category""><option value=""0"">選択する</option><option value=""1"">ハイキング</option><option value=""2"">サイクリング</option><option value=""3"">ウォーキング</option><option value=""4"">キャンプ</option><option value=""5"">その他</option></select></td>" & _
      "<td colspan=""4"">タイトル<input id=""mTitle"" size=""24"" type=""text""></td>" & _
      "<td colspan=""4"">サブタイトル<input id=""sTitle"" size=""24"" type=""text"">(かな)</td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""2""><span class=""large"">■GPXファイル</span></td>" & _
      "<td colspan=""6""><input id=""gpxf"" name=""gpxf"" type=""text""value="""" size=""48""><input type=""file"" onchange=""fnamecopy(108)"" size=""1"" name=""imp_file""></td>" & _
      "<td colspan=""4""><span class=""small"">GPX形式(***.gpx)のファイルを指定します。</span></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""2"" rowspan=""2""><span class=""large"">■カメラ内蔵時計<br>   補正値</span></td>" & _
      "<td align=""center""><input type=""radio"" name=""offset1"" value=""o11"" checked><b>+</b></td>" & _
      "<td rowspan=""2""><input size=""2"" type=""text"" name=""mm1"" value=""00"">分</td>" & _
      "<td rowspan=""2""><input size=""2"" type=""text"" name=""ss1"" value=""00"">秒</td>" & _
      "<td colspan=""7"" rowspan=""2""><span class=""small"">GPSに対してカメラが 進み=(+) 遅れ=(-)</span></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td align=""center""><input type=""radio"" name=""offset1"" value=""o12""><b>-</b></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""2"" rowspan=""2""><span class=""large"">■画像リサイズ</span></td>" & _
      "<td colspan=""1"" align=""center""><input type=""radio"" name=""resize1"" value=""rs1""checked><b>あり</b></td>" & _
      "<td colspan=""3"" rowspan=""2"" align=""center"" height=""120""><input id=""Ok1"" name=""Ok1"" type=""button"" value=""[作成]"" onclick=""onClick(1)"" class=""largest""></td>" & _
      "<td colspan=""3"" rowspan=""2""align=""center""><input id=""Ok2"" name=""Ok2"" type=""button"" value=""[保存]"" onclick=""onClick(2)"" class=""largest""></td>" & _
      "<td colspan=""3"" rowspan=""2""><span class=""small""><ul><li>[作成]ボタンで、作成状況を確認できます。</li><li>[保存]するまではファイルは登録されせん。</li><li><span class=""red"">[保存]の前には必ず[作成]で作成状況を確認してください。</span></span></li></ul></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""1"" align=""center""><input type=""radio"" name=""resize1"" value=""rs2""><b>なし</b></td>" & _
    "</tr>" & _
  "</tbody>" & _
  "<tbody>" & _
    "<tr>" & _
    "<tr><td width=""120""><div id=""thumb0""></div></td><td width=""120""><div id=""thumb1""></div></td><td width=""120""><div id=""thumb2""></div></td><td width=""120""><div id=""thumb3""></div></td><td width=""120""><div id=""thumb4""></div></td><td width=""120""><div id=""thumb5""></div></td><td width=""120""><div id=""thumb6""></div></td><td width=""120""><div id=""thumb7""></div></td><td width=""120""><div id=""thumb8""></div></td><td width=""120""><div id=""thumb9""></div></td></tr>" & _
    "<tr><td><div id=""imgName0""></div></td><td><div id=""imgName1""></div></td><td><div id=""imgName2""></div></td><td><div id=""imgName3""></div></td><td><div id=""imgName4""></div></td><td><div id=""imgName5""></div></td><td><div id=""imgName6""></div></td><td><div id=""imgName7""></div></td><td><div id=""imgName8""></div></td><td><div id=""imgName9""></div></td></tr>" & _
    "<tr><td width=""120""><div id=""thumb10""></div></td><td width=""120""><div id=""thumb11""></div></td><td width=""120""><div id=""thumb12""></div></td> <td width=""120""><div id=""thumb13""></div></td><td width=""120""><div id=""thumb14""></div></td><td width=""120""><div id=""thumb15""></div></td><td width=""120""><div id=""thumb16""></div></td><td width=""120""><div id=""thumb17""></div></td><td width=""120""><div id=""thumb18""></div></td><td width=""120""><div id=""thumb19""></div></td></tr>" & _
    "<tr><td><div id=""imgName10""></div></td><td><div id=""imgName11""></div></td><td><div id=""imgName12""></div></td><td><div id=""imgName13""></div></td><td><div id=""imgName14""></div></td><td><div id=""imgName15""></div></td><td><div id=""imgName16""></div></td><td><div id=""imgName17""></div></td><td><div id=""imgName18""></div></td><td><div id=""imgName19""></div></td></tr>" & _
  "</tbody>" & _
"</table>" & _
"<table width=""100%"" border=""0"">" & _
  "<tbody>" & _
    "<tr>" & _
      "<td width=""3%""> </td>" & _
      "<td width=""60%""> </td>" & _
      "<td width=""35%""> </td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td colspan=""7"" height=""50""><span class=""large"">■画像登録エリア</span></td>" & _
    "</tr>" & _
    "<tr>" & _
      "<td align=""center""><strong>No</strong></td><td colspan=""1""><strong>[ファイル名]</strong></td><td colspan=""1""><strong>[画像タイトル]</strong></td>" & _
    "</tr>" & _
    "<tr>" & _
    "<tr><td align=""center"">1</td><td colspan=""1""><input id=""jpeg0"" name=""jpeg0"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(0)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title0""></td></tr>" & _
    "<tr><td align=""center"">2</td><td colspan=""1""><input id=""jpeg1"" name=""jpeg1"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(1)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title1""></td></tr>" & _
    "<tr><td align=""center"">3</td><td colspan=""1""><input id=""jpeg2"" name=""jpeg2"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(2)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title2""></td></tr>" & _
    "<tr><td align=""center"">4</td><td colspan=""1""><input id=""jpeg3"" name=""jpeg3"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(3)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title3""></td></tr>" & _
    "<tr><td align=""center"">5</td><td colspan=""1""><input id=""jpeg4"" name=""jpeg4"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(4)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title4""></td></tr>" & _
    "<tr><td align=""center"">6</td><td colspan=""1""><input id=""jpeg5"" name=""jpeg5"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(5)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title5""></td></tr>" & _
    "<tr><td align=""center"">7</td><td colspan=""1""><input id=""jpeg6"" name=""jpeg6"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(6)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title6""></td></tr>" & _
    "<tr><td align=""center"">8</td><td colspan=""1""><input id=""jpeg7"" name=""jpeg7"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(7)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title7""></td></tr>" & _
    "<tr><td align=""center"">9</td><td colspan=""1""><input id=""jpeg8"" name=""jpeg8"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(8)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title8""></td></tr>" & _
    "<tr><td align=""center"">10</td><td colspan=""1""><input id=""jpeg9"" name=""jpeg9"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(9)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title9""></td></tr>" & _
    "<tr><td align=""center"">11</td><td colspan=""1""><input id=""jpeg10"" name=""jpeg10"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(10)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title10""></td></tr>" & _
    "<tr><td align=""center"">12</td><td colspan=""1""><input id=""jpeg11"" name=""jpeg11"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(11)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title11""></td></tr>" & _
    "<tr><td align=""center"">13</td><td colspan=""1""><input id=""jpeg12"" name=""jpeg12"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(12)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title12""></td></tr>" & _
    "<tr><td align=""center"">14</td><td colspan=""1""><input id=""jpeg13"" name=""jpeg13"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(13)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title13""></td></tr>" & _
    "<tr><td align=""center"">15</td><td colspan=""1""><input id=""jpeg14"" name=""jpeg14"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(14)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title14""></td></tr>" & _
    "<tr><td align=""center"">16</td><td colspan=""1""><input id=""jpeg15"" name=""jpeg15"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(15)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title15""></td></tr>" & _
    "<tr><td align=""center"">17</td><td colspan=""1""><input id=""jpeg16"" name=""jpeg16"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(16)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title16""></td></tr>" & _
    "<tr><td align=""center"">18</td><td colspan=""1""><input id=""jpeg17"" name=""jpeg17"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(17)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title17""></td></tr>" & _
    "<tr><td align=""center"">19</td><td colspan=""1""><input id=""jpeg18"" name=""jpeg18"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(18)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title18""></td></tr>" & _
    "<tr><td align=""center"">20</td><td colspan=""1""><input id=""jpeg19"" name=""jpeg19"" type=""text"" value="""" size=""60""><input type=""file"" onchange=""fnameCopy(19)"" size=""1""></td><td colspan=""1""><input size=""40"" type=""text"" name=""title19""></td></tr>" & _
  "</tbody>" & _
"</table>"
		

下の方の"<tr> 20行は「Wayポイント」の画像登録行で20行あります。この画像は地図内の撮影ポイントに表示する画像です。

次はフォームチェンジとメインルーチンです。


'-------------------------------------------------
'フォームチェンジ
'-------------------------------------------------
Sub fmChange(no)

	Select Case no
		case 1																'新しく作成
			resizeFlag = True												'新規作成なら画像リサイズあり

			Value = MsgBox ("testPath に前回のデータがあります。" & vbCrLf & vbCrLf & "削除してもいいですか?", 20)
			If Value <> 6 Then
				MsgBox ("削除してはいけないデータがある場合は" & vbCrLf & "[新しく作成] はできません。" & vbCrLf & vbCrLf & "これを終了します。再度実行してください。")
				Window.Close()
			End If

	 		Document.getElementById("innerhtml_99").innerHTML = fm0			'日付入力フォーム表示
			Call subInit()

		case 10																'新しく作成で、日付入力完了
			Call procDate()

			Set objFolder  = objFso.GetFolder(rootPath)						'C:\Users\Owner\Documents\Map\root
			Set colFolders = objFolder.SubFolders

			dateFolder = fncFolderCheck(NewdateFol)							'同一日付フォルダーチェック

			fomlen = Document.forms("fom").Length - 2
			ReDim fomData(fomlen)
			Call dataSave(fomlen)											'"data.txt"に残す

	 		Document.getElementById("innerhtml_99").innerHTML = fm1			'データ入力フォーム表示
			Call dataCopy()
			Document.forms("fom").item(3).Value = dateFolder
			Call testFolCreate(dateFolder)									'[test]フォルダ作成

		case 2																'前回の続き
 			Document.getElementById("innerhtml_99").innerHTML = fm1			'データ入力フォーム表示
			Call dataCopy()
			dateFolder = Document.forms("fom").item(3).Value				'日付フォルダー読込
		case 3																'一覧表示
			Set WshShell = CreateObject("WScript.Shell")
			WshShell.Run "chrome " & rootPath & "\index.html", 1, False
	End Select
End Sub


'-------------------------------------------------
'メインルーチン
'-------------------------------------------------
Sub onClick(NO)

	If resizeFlag = True Then
		document.getElementsByName("resize1")(0).checked = True			'新規作成なら画像リサイズあり
	End If


	If NO = 1 Then														'[作成]
		Set WshShell = CreateObject("WScript.Shell")

		fomlen = Document.forms("fom").Length - 1
		Call dataSave(fomlen)											'全fomDataを [data.txt] に保存
		Call procDate()													'日付成形

		categoNo  = fom.category.value									'カテゴリーNo
		category  = categoArr(categoNo)									'カテゴリー名
		mainTitle = fom.mTitle.value									'メインタイトル
		subTitle  = fom.sTitle.value									'サブタイトル

		Call procTitle()

		offsetmm1 = Document.forms("fom").item(10).Value				'カメラオフセット分
		offsetss1 = Document.forms("fom").item(11).Value				'カメラオフセット秒
		For Each i In document.getElementsByName("offset1")
			If i.checked Then
				cam1_Offset = i.value									'カメラオフセット
				Exit For
			End If
		Next

		offsetT1 = (offsetmm1)*60 + (offsetss1)
		If cam1_Offset = "o11" Then
			offsetT1 = (-1) * offsetT1
		End If

		gpxPath = fom.gpxf.value																	'GPXファイルPath
		gpxFile = objFso.GetFileName(gpxPath)														'GPXファイルネーム
'mkData(i,x)	x: 0=lat 1=lon 2=ファイル名 3=撮影Time(utc) 4=高度 5=title 6=coment 7=移動距離(km) 8=撮影Time(m秒)  9=DiffTime(仮の撮影Time)
		j = 0
		m = 0
		For i=0 To 19
			If Document.forms("fom").item(i*3+17).Value <> "" Then
				jpgData(i,0) = Document.forms("fom").item(i*3+17).Value								'画像フルパス
				jpgData(i,1) = LCase(Replace(objFso.GetFileName(jpgData(i,0)), " ", "-"))			'画像ファイル名
				jpgData(i,2) = Document.forms("fom").item(i*3+19).Value								'画像タイトル
				j = j+1

				mkData(m,2) = jpgData(i,1)															'画像ファイルネーム
				mkData(m,3) = funcExifTimeGet(jpgData(i,0), OffsetT1)								'カメラ撮影時刻
				mkData(m,5) = jpgData(i,2)															'画像タイトル
				mkData(m,9) = 100000000000															'GPS軌跡時刻と撮影時刻との時間差初期値
				m = m+1
			End If
		Next
		mkLen = m-1

		If gpxPath = "" Then
			MsgBox("GPXファイルを登録してください!!")
		Else
			Call createGPX(gpxPath)
			mkMax = createCameraPoint()																'撮影位置計算
			Call quicksort2(mkData, 8, 0, mkMax, "As")												'マーカーポイントソート
			Call objFso.CopyFile (gpxPath, testPath & "\", True)									'GPSファイルをdateFolderにコピー
		End If

		For Each i In document.getElementsByName("resize1")
			If i.checked Then
				resizeCheck = i.value																'リサイズあり/なし
				Exit For
			End If
		Next

		If resizeCheck = "rs1" Then
			Call subJpgResize()																		'画像リサイズ
		End If

		Call subIndexCreate()																		'本文ページ作成
		Call dataSave(fomlen)																		'全fomDataを [data.txt] に保存
		MsgBox("作成完了!" & vbCr & vbCr & "map.html を開きます。" & vbCr & "確認してください!")
		CreateObject("Shell.Application").ShellExecute testPath & "\map.html"


	ElseIf NO = 2 Then																				'[保存]
		objFso.CopyFolder testPath, rootPath & "\" & dateFolder, True								'testフォルダを rootPath に名前を変更してコピー
		objFso.CopyFile rootPath & "\data.txt", rootPath & "\" & dateFolder & "\data.txt", True		'root内のdata.txtファイルを[NewdateFol]にコピー

		Call createMapIndex()																		'index.html作成


		Window.Close()
	End If
End Sub
		

次はメインルーチンの中で使っているサブプロシージャ群です。
画像に埋め込まれたExif情報から撮影時刻を取得する方法が分からなかったので、試行錯誤の結果非常に素人っぽい方法で実現しています。最近ネットにコマンドラインから使える「exiftool」というのがあることを知りました。これを使えば簡単なようです。


'-------------------------------------------------
'初期値設定
'-------------------------------------------------
Sub subInit()

	Document.fom.Year1.Value  = Year(Date)								'年初期値設定
	Document.fom.Month1.Value = Month(Date)								'月初期値設定
	Document.fom.Day1.Value   = Day(Date)								'日初期値設定
End Sub


'-------------------------------------------------
'日付成形
'-------------------------------------------------
Sub procDate()

	y1 = Document.forms("fom").item(0).value
	m1 = Document.forms("fom").item(1).value
	d1 = Document.forms("fom").item(2).value
	d2 = Document.forms("fom").item(3).value

	NewDate1   = DateValue(y1 & "/" & m1 & "/" & d1)					'yyyy/mm/dd
	NewDateStr = Replace(CStr(NewDate1), "/", ".")						'Full年月日(yyyy.mm.dd)
	NewdateFol = Replace(CStr(NewDate1), "/", "")						'4桁年月日(yyyymmdd)
	Week1      = WeekdayName(DatePart("w",NewDate1))					'水曜日
	Yobi1      = Mid(Week1,1,1)											'水(曜日1文字1)
	NewDateStr11 = NewDateStr & "(" & Yobi1 & ")"						'2016.08.25(水)

	myDateStr1 = NewDateStr11											'pageTitle用 2016.08.25(木)
End Sub


'-------------------------------------------------
'同一日付フォルダチェック
'-------------------------------------------------
Function fncFolderCheck(Fol)

	Set objFolder  = objFso.GetFolder (rootPath)						'rootフォルダオブジェクト
	Set colFolders = objFolder.SubFolders								'[root]フォルダ内フォルダコレクション

	i = 2
	For Each objSubFolder in colFolders
		dateFol = objSubFolder.Name
		If dateFol = "common" Then Exit For
		If InStr(dateFol, Fol) > 0 Then									'同じ日付の文字列があったら・・・
			Fol = Left(Fol, 8) & "-" & i								'フォルダ名に「-2」などと枝番号を付ける
			i = i+1
		End If
	Next
	fncFolderCheck = Fol												'チェック結果を返す
End Function


'-------------------------------------------------
'[test]フォルダ 作成
'-------------------------------------------------
Sub testFolCreate(Fol)

	If objFso.FolderExists(testPath) = True Then						'[test]が有ったら
		Call objFso.DeleteFolder (testPath, true)						'[test] 削除
	End If

	Set objFolder  = objFso.GetFolder(rootPath)							'C:\Users\go\Documents\Map
	Set objsubFol  = objFolder.SubFolders
	objsubFol.Add ("test")												'[test]作成

	Set objFolder  = objFso.GetFolder(testPath)							'C:\Users\Owner\Documents\Map\test
	Set objsubFol  = objFolder.SubFolders
	objsubFol.Add ("img")												'C:\Users\go\Documents\Map\test\img
End Sub


'--------------------------------------------
'data.txt 書き出し		data.txt
'--------------------------------------------
Sub dataSave(no)														'再作成時情報を"data.txt"に残す

	Path = rootPath & "\data.txt"
	Set objTxOut = objFso.CreateTextFile(Path)
	txOut = ""

	m=0
	For i=0 To no
		Select Case i
			Case 9,12,13,16
				txOut = txOut & Document.forms("fom").item(i).checked & vbCrLf
			Case Else
				txOut = txOut & Document.forms("fom").item(i).Value & vbCrLf
		End Select
	Next

	objTxOut.Write(txOut)
	objTxOut.Close()
End Sub


'-------------------------------------------------
'data.txt ファイルからデータをコピー
'-------------------------------------------------
Sub dataCopy()

	Path = rootPath & "\data.txt"										'C:\Users\go\Documents\Map\data.txt

	If objFso.FileExists(Path) = True Then
		Set objtxIn = objFso.OpenTextFile(Path)
		i=0
		m=0
		Do Until objtxIn.AtEndOfStream = True
			myDat = objtxIn.ReadLine()
			Select Case i
				Case 9,12,13,16
					Document.forms("fom").item(i).checked = myDat
				Case 17+m
					Document.forms("fom").item(i).Value = myDat
					If myDat <> "" Then 
						Document.getElementById("thumb" & (i-17)/3).innerHTML = "<a href=""" & Document.forms("fom").item(i).Value & """ data-lightbox=""group""><img id=""pic"" src=""" & Document.forms("fom").item(i).Value & """ width=""120""></a>"		'サムネイルにコピー
						Document.getElementById("imgName" & (i-17)/3).innerHTML = (i-17)/3+1 & " = " & objFso.GetFileName(myDat)		'ツールに表示するサムネイル用画像番号
					End If
					m=m+3
				Case Else
					Document.forms("fom").item(i).Value = myDat			'その他の場合
			End Select
			i = i+1
		Loop
		objtxIn.Close()
	End If
End Sub


'-------------------------------------------------
'参照ファイルネームコピー	input type="file" には、前回のデータを読み込むことができないので、苦肉の策で input type="file"のデータを1つ前の inputフォームに書き込む形式にしている
'-------------------------------------------------
Sub fnameCopy(num)
	If num >=0 And num <= 19 Then
		Document.forms("fom").item(num*3+17).Value = Document.forms("fom").item(num*3+18).Value		'参照データを1個前のinputにコピー
		Document.getElementById("thumb" & num).innerHTML = "<a href=""" & Document.forms("fom").item(num*3+17).Value & """ data-lightbox=""group""><img id=""pic"" src=""" & Document.forms("fom").item(num*3+17).Value & """ width=""120""></a>"		'サムネイルにコピー
		Document.getElementById("imgName" & num).innerHTML = num+1 & " = " & objFso.GetFileName(Document.forms("fom").item(num*3+17).Value)							'ツールに表示するサムネイル用画像ファイル名
	ElseIf num >= 100 Then
		Document.forms("fom").item(num-101).Value = Document.forms("fom").item(num-100).Value		'参照データを1個前のinputにコピー(GPSファイル)
	End If
End Sub


'-------------------------------------------------
'タイトル整形
'-------------------------------------------------
Sub procTitle()

	Set reg = New RegExp												'reg に正規表現クラスをセットする
	reg.Global = True													'全行検索On

	reg.Pattern = "\d"
	val = reg.Test(mtAlt)

	If val = True Then
		mtAlt1 = "(" & mtAlt & "m)"
	Else
		mtAlt = ""
	End If

	pageTitle = "<h2 class=""title_date""><span>" & myDateStr1 & category & "</span>" & mainTitle & "<span>" & subtitle & mtAlt1 & "</span></h2>"

End Sub

'----------------------------------------------------------------
'本文ファイル生成			map.html
'----------------------------------------------------------------
Sub subIndexCreate()

	motoFile = tmplPath & "\map.html"									'Templateファイル
	shinFile = testPath & "\map.html"									'作成する新ファイル

	Dim tmpFile : tmpFile = shinFile & ".tmp"							'臨時のテンプファイル

	With CreateObject("ADODB.Stream")									'UTF-8で全文読込
		.Charset = "UTF-8"
		.Open
		.LoadFromFile(motoFile)											'Templateファイルを読み込む
		txOut = .ReadText
		.Close
	End With

	Copyright = "<div class=""copyright""><small>Copyright © 2011 - " & Left(Date(),4) & " GPSwalking All Rights Reserved.</small></div>"

	Call createMarker()
	Call createWayPoint()

	txOut = Replace(txOut, "<!-- タイトル名 -->", mainTitle)			'タイトル(山名)
	txOut = Replace(txOut, "<!-- ページタイトル -->", pageTitle)		'ページタイトル
	txOut = Replace(txOut, "<!-- 著作権表示 -->", Copyright)			'著作権表示

	txOut = Replace(txOut, "<!-- GPSデータエリア -->", gpxPoint)		'GPSデータ
	txOut = Replace(txOut, "<!-- markerポイント -->", markerPoint)		'マーカーポイント
	txOut = Replace(txOut, "<!-- wayポイント -->", wayPoint)			'Wayポイント

	Call WriteUTF8(txOut, shinFile)										'新ファイルをUTF-8で書き込む
End Sub


'-------------------------------------------------
'GPXData 作成															'GPXデータをテキストデータとしてmap.htmlに書き込む
'-------------------------------------------------
Sub createGPX(gpxFile)

	With CreateObject("ADODB.Stream")
		.Charset = "UTF-8"
		.Open
		.LoadFromFile(gpxFile)
		txOut = .ReadText
		.Close
	End With

	Set reg = New RegExp												'reg に正規表現クラスをセットする
	reg.Global = True													'全行検索On

	'--------------------------------------------
	'GPXファイル読込													'GPXファイルによって書式が違うので、統一化するための処理

	reg.Pattern = "\r?\n"												'改行を削除する
	txOut = reg.Replace(txOut, "")

	reg.Pattern = "</trkpt>"											'</trkpt>で改行する
	txOut = reg.Replace(txOut, "</trkpt>" & vbCrLf)

	gpxPoint = "var points = [" & vbCrLf
	reg.Pattern = "\s*<trkpt lat=""(\d{2}\.\d{4,8})"" lon=""(\d{3}\.\d{4,8})"">\s*<ele>(\d{1,4}\.\d{1,13})</ele>\s*<time>(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.*\d{3})?Z</time>.*</trkpt>"

	Set mc = reg.Execute(txOut)											'もの凄く長い1行に変換したデータから lat, lng, ele, date time を取り出す
	For Each m in mc
		mcStr0 = m.SubMatches(0)			'lat
		mcStr1 = m.SubMatches(1)			'lng
		mcStr2 = m.SubMatches(2)			'ele
		mcStr3 = m.SubMatches(3)			'date time
		gpxPoint = gpxPoint & String(3, vbTab) & "[" & mcStr0 & ", " & mcStr1 & ", " & mcStr2 & ", '" & mcStr3 & "Z']," & vbCrLf
	Next
	gpxPoint = gpxPoint & String(2, vbTab) & "];"

End Sub


'-------------------------------------------------
'markerPoint 作成
'-------------------------------------------------
Sub createMarker()

	markerPoint = "var markers = [" & vbCrLf

	For i=0 To mkMax
		myDate = mkData(i,3)
		myTime = DatePart("h", myDate) & ":" & Right("0" & DatePart("n", myDate), 2) & ":" & Right("0" & DatePart("s", myDate), 2)
		myTime = Replace(myTime, "0:00:00", "")
		markerPoint = markerPoint & String(3, vbTab) & "[" & mkData(i, 0) & ", " & mkData(i, 1) & ", """ & mkData(i, 2) & """, """ & myTime & """, " & mkData(i, 4) & ", """ & mkData(i, 5) & """, """ & """]," & vbCrLf
	Next

	markerPoint = markerPoint & String(2, vbTab) & "];"
End Sub


'-------------------------------------------------
'wayPoint 作成
'-------------------------------------------------
Sub createWayPoint()

	wayPoint = "<dl class=""course_time"">" & vbCrLf

'mkData(i,x)  x: 0=lat  1=lon  	2=ファイル名  3=Time(JST)  4=ele  5=title  6=coment  7=移動距離(km)  8=Time(mm秒)  9=DiffTime(仮の撮影Time)

	For i=0 To mkMax
		If mkData(i,5) = "" Then mkData(i,5) = " "
		myTitle = mkData(i,5)

		myDate = mkData(i,3)
		myTime = ""
		If mkData(i,3) <>"" then
			myTime = DatePart("h", myDate) & ":" & Right("0" & DatePart("n", myDate), 2)
		End If
		wayPoint = wayPoint & String(3, vbTab) & "<a href=""#map_canvas"" onclick=""popupOn(" & i & ");""><dt>" & myTime & "</dt><dd>" & myTitle & "</dd></a>" & vbCrLf

		If InStr(mkData(i,5), "goal") > 0 Then
			wayPoint = wayPoint & String(3, vbTab) & "  " & vbCrLf			'区間と区間の間は距離を書かない
		Else
			wayPoint = wayPoint & String(3, vbTab) & "  " & Round((mkData(i+1,7) - mkData(i,7))/1000, 1) & "km" & vbCrLf		'Roundの引数1は小数点以下1桁
		End If
	Next

	wayPoint = wayPoint & String(2, vbTab) & "</dl>"
End Sub


'-------------------------------------------------
'マーカー用画像配列ソート	mkData(i,x)  0=lat  1=lng  2=file  3=utc  4=ele  5=title  6=coment  7=km  8=撮影Time(m秒)  9=DiffTime(仮の撮影Time)
'-------------------------------------------------
Sub mkDataSort()

	Call quicksort2(mkData, 3, 0, mkLen, "As") 									'クイックソート本体(昇順降順)(再帰)

	' quicksort2(sqa, col, first, last, "As")
	' sqa:配列名
	' col:ソート対象0から始まる列番号
	' first:ソート行範囲最初行番号
	' last:ソート行範囲最終行番号
	' sort:昇・降順別 "As"/"Des"と文字列を記述する。

End Sub


'-------------------------------------------------
'walkData 整形
'-------------------------------------------------
Sub procWalkData()

'	wDist = FormatNumber(Round(walkDist/1000, 1), 1) & "km"						'FormatNumberの引数=1は少数点以下1桁,3桁ごとに(,)区切り
	wDist = Round(walkDist/1000, 1) & "km"										'Roundの引数=1は少数点以下1桁
	wTime = Replace(walkTime, ":", "時間") & "分"

	If numGPS=1 Then															'GPSあり
		walkData = String(3, vbTab) & "<p class=""subject"">移動距離</p><p class=""data"">" & wDist & "</p>" & vbCrLf & _
			   String(3, vbTab) & "<p class=""subject"">行動時間</p><p class=""data"">" & wTime & "</p>" & vbCrLf
	End If

End Sub


'--------------------------------------------
'function funcExifTimeGet Exifタイム取得										'画像のExifデータから撮影時刻を取り出す方法が分からないので、
'--------------------------------------------									'素人っぽい方法で取り出している
Function funcExifTimeGet(Filname, Offset)

	Dim jpgFil, i, j, binDate(9), binTime(7), binData(19)
	Dim byt, jpgTimeStr

	set jpgFil = CreateObject("ADODB.Stream")
	jpgFil.Open
	jpgFil.Type = 1
	jpgFil.LoadFromFile(Filname)

	Flag = 0
	i = 0
	Do
		i = i + 1
		byt = jpgFil.Read(1)
		Select Case i
			Case 1,2,3,4,6,7,9,10,12,13,15,16,18,19
				If AscB(midb(byt,1))=>48 And AscB(midb(byt,1))=<57 Then
					binData(i) = Chr(AscB(midb(byt,1)))
				Else
					i = 0
				End If
			Case 5,8,14,17
				If Chr(AscB(midb(byt,1)))=":" Then
					binData(i) = Chr(AscB(midb(byt,1)))
				Else
					i = 0
				End If
			Case 11
				If Chr(AscB(midb(byt,1)))=" " Then
					binData(i) = Chr(AscB(midb(byt,1)))
				Else
					i = 0
				End If
		End Select
		If Flag=0 And i=19 Then
			i = 0
			Flag = 1
		ElseIf Flag=1 And i=19 Then
			Exit Do
		End If
	Loop
	jpgFil.Close

	jpgdateStr = ""
	For i=1 To 19
		If i<10 And binData(i) = ":" Then
			binData(i) = "/"
		End If
		jpgdateStr = jpgdateStr & binData(i)
	Next

	jpgDate = DateAdd("s", Offset, CDate(jpgdateStr))

	funcExifTimeGet = CStr(jpgDate)
End Function


'--------------------------------------------
'JPEG画像リサイズ  			コマンドラインから実行できる「photoshifter」を使ってリサイズ
'--------------------------------------------
Sub subJpgResize()

	Set WshShell = CreateObject("WScript.Shell")
	exeFile  = toolPath & "\photoshifter\photoshifter.exe"
	setFile0 = toolPath & "\photoshifter\mysetting0.xml"							'横1024ピクセル
	outPath0 = testPath & "\img\"

'jpgData(i,j)	j: 0=jpgFullPath 1=ファイル名	2=title		3=coment	4=マーカー	5=撮影time(2018/09/16 9:25:30)	6=noResize

	Set reg = New RegExp															'正規表現を使えるようにする
	reg.Global = True																'全行検索
	For i=0 To 19
		reg.Pattern = "\.JPG|\.jpg|\.png|\.gif"
		If reg.Test(jpgData(i,0)) Then												'jpg|png|gifなら・・・
				copyFileStr = exeFile & " /overwrite /par " & setFile0 & " /format JPEG /file " & """" & jpgData(i,0) & """ " & outPath0 & jpgData(i,1)
				Call WshShell.Run (copyFileStr,,True)
		End If

		reg.Pattern = "\.mp4"
		If reg.Test(jpgData(i,0)) Then												'mp4なら・・・
			Call objFso.CopyFile(jpgData(i,0), outPath0 & jpgData(i,1), True)		'ファイルを[test]にコピー
		End If
	Next

	Set WshShell = Nothing
End Sub


'-------------------------------------------------
'Map一覧作成
'-------------------------------------------------
Sub createMapIndex()

	shinFile = rootPath & "\index.html"							'作成する新ファイル
	hinaFile = tmplPath & "\index.html"							'Templateファイル

	Set objFolder  = objFso.GetFolder(rootPath)					'C:\Users\Owner\Documents\Map
	Set colFolders = objFolder.SubFolders

	Dim lstArr(999,7)											'リストの配列 999個まで登録可
	i = 0
	For Each objSubFolder in colFolders

		dateFol = objSubFolder.Name
		If dateFol = "common" Then Exit For

		dataFile = rootPath & "\" & dateFol & "\data.txt" 
		With CreateObject("ADODB.Stream")
			.Charset = "Shift_JIS"
			.Open
			.LoadFromFile(dataFile)
			txIn = .ReadText
			.Close
		End With

		Arr = Split(txIn, vbCrLf)
		Arr(0) = Right("20" & Arr(0), 4)						'年(4桁)
		Arr(1) = Right("0" & Arr(1), 2)							'月(2桁)
		Arr(2) = Right("0" & Arr(2), 2)							'日(2桁)
		For j = 0 To 6
			lstArr(i, j) = Arr(j)
		Next
		lstArr(i,7) = Arr(0) & Arr(1) & Arr(2)					'年月日(20240910)
		i = i+1
	Next
	lstMax = i-1
'	Call QuickSort2(lstArr, 7, LBound(lstArr), UBound(lstArr), "Des")			'一覧リストソート(降順)'ソートをしないことにした(24.09.18)

	With CreateObject("ADODB.Stream")							'UTF-8で全文読込
		.Charset = "UTF-8"
		.Open
		.LoadFromFile(hinaFile)
		txOut = .ReadText
		.Close
	End With

	lstStr = "<!-- mapList -->" & vbCrLf
	For i=0 To lstMax											'日付フォルダーList読込
		If lstArr(i,7) <> "" Then
			Str4 = categoArr(lstArr(i,4))						'カテゴリー名
			lstStr = lstStr & String(4, vbTab) & "<tr title=""0," & lstArr(i,4) & """ data-href=""" & lstArr(i,3) & "/map.html""><td>" & lstArr(i,0) & "." & lstArr(i,1) & "." & lstArr(i,2) & "</td><td>" & Str4 & "</td><td>" & lstArr(i,5) & "</td><td>" & lstArr(i,6) & "</td><tr>" & vbCrLf
		End If
	Next
	lstStr = lstStr & String(4, vbTab) & "<!-- /mapList -->"
	txOut = Replace(txOut, "<!-- mapList -->", lstStr)			'Map一覧書き換え

	Call WriteUTF8(txOut, shinFile)
End Sub


'--------------------------------------------
'UTF8で書込む
'--------------------------------------------
Sub WriteUTF8(text, fileName)

	tmpFile = fileName & ".tmp"

	' UTF-8で書きこむと自動的にBOM(Byte Order Mark)が、先頭に3バイト付加されてしまう。
	' それを回避するため、一旦一時ファイルにUTF-8形式で書き込む
	With CreateObject("ADODB.Stream")
		.Type = 2
		.charset = "UTF-8"
		.Open
		.WriteText text
		.SaveToFile tmpFile, 2
		.Close
	End With

	' 一時ファイルをバイナリで読み取る
	With CreateObject("ADODB.Stream")
		.Type = 1
		.Open
		.LoadFromFile(tmpFile)									'一時ファイルをバイナリで読み取る
		.Position = 3											'BOMの3バイトを読み飛ばす
					
		'4バイト目から出力ファイルにバイナリで書き込む
		Dim ws : Set ws = CreateObject("ADODB.Stream")
		ws.Type = 1
		ws.Open
		ws.Write(.Read(-1))
		ws.SaveToFile fileName, 2
		ws.Close
		.Close
	End With

	' 一時ファイルの削除
	Call CreateObject("Scripting.FileSystemObject").DeleteFile(tmpFile)
End Sub


'----------------------------------------------------------------------------------
'クイックソート(配列データ, 配列データ最小インデックス, 配列データ最大インデックス), 昇・降順別"As"/"Des"		'使ってないけど使うかもしれないと思って残している
'----------------------------------------------------------------------------------
Sub QuickSort(vntSortData(), lngMin, lngMax, asdes)
	Dim lngIdxL														'左側のインデックス
	Dim lngIdxR														'右側のインデックス
	Dim vntKijunChi													'中央付近の基準値
	Dim vntWk														'スワップの作業領域

	vntKijunChi = vntSortData((lngMin + lngMax) \ 2)				'中央付近のインデックス内容値を基準値とする
	lngIdxL = lngMin												'最小インデックスをセット
	lngIdxR = lngMax												'最大インデックスをセット
	Do
		For lngIdxL = lngIdxL To lngMax Step 1						'配列のインデックスの小さい方から基準値に向かって、インデックス内容値が基準値より大きい値かイコールな値を探す
			If asdes = "As" Then
				If (vntSortData(lngIdxL) >= vntKijunChi) Then		'*1:降順は『>=』を『<=』 にする
					Exit For
				End If
			Else
				If (vntSortData(lngIdxL) <= vntKijunChi) Then		'*1:降順は『>=』を『<=』 にする
					Exit For
				End If
			End If
		Next
		For lngIdxR = lngIdxR To lngMin Step -1						'配列のインデックスの大きい方から基準値に向かって、インデックス内容値が基準値より小さい値かイコールな値を探す
			If asdes = "As" Then
				If (vntSortData(lngIdxR) <= vntKijunChi) Then		'*2:降順は『<=』を『>=』 にする
					Exit For
				End If
			Else
				If (vntSortData(lngIdxR) >= vntKijunChi) Then		'*2:降順は『<=』を『>=』 にする
					Exit For
				End If
			End If
		Next

		If lngIdxL >= lngIdxR Then									'最小インデックスと最大インデックスが同じか大きくになったらブレイクDOループ
			Exit Do													'この段階で、基準値より小さな値が基準値より左に、大きな値が基準値より右にきている(昇順の場合)
		End If

		vntWk = vntSortData(lngIdxL)								'双方の値を交換
		vntSortData(lngIdxL) = vntSortData(lngIdxR)
		vntSortData(lngIdxR) = vntWk

		lngIdxL = lngIdxL + 1										'配列のインデックスの小さい方から基準値に向かってのインデックスを更新する
		lngIdxR = lngIdxR - 1										'配列のインデックスの大きい方から基準値に向かってのインデックスを更新する
	Loop

	If (lngMin < lngIdxL - 1) Then									'左の配列の処理が必要か
		QuickSort vntSortData, lngMin, lngIdxL - 1, asdes			'基準値の左の配列に対してのソート処理を行う(再帰処理)
	End If

	If (lngMax > lngIdxR + 1) Then									'右の配列の処理が必要か
		QuickSort vntSortData, lngIdxR + 1, lngMax, asdes			'基準値の右の配列に対してのソート処理を行う(再帰処理)
	End If
End Sub


'----------------------------------------------------------------------------------
'ニ元配列のクイックソート(配列名, ソート対象0から始まる列番号, ソート行範囲最初行番号, ソート行範囲最終行番号, 昇・降順別"As"/"Des")
'----------------------------------------------------------------------------------
' 【 qsortBinaryArray.vbs 】 - ニ元配列のクイックソート
 '    http://tuka.s12.xrea.com/index.xcg?p=VBS#p16
 '      (一元配列のクイックソート)を変更
 '    ソート行範囲を指定できるので、CSVなど最初行が項目名の
 '    二元配列のソートに便利である。
 '
 ' Dim sB, sE, sF
 ' Dim qA(4,3)
 ' qA(0,0)=" A": qA(0,1)=" B": qA(0,2)=" C": qA(0,3)=" D"
 ' qA(1,0)=22: qA(1,1)=34: qA(1,2)=45: qA(1,3)=56
 ' qA(2,0)=23: qA(2,1)=33: qA(2,2)=43: qA(2,3)=55
 ' qA(3,0)=24: qA(3,1)=32: qA(3,2)=42: qA(3,3)=54
 ' qA(4,0)=25: qA(4,1)=31: qA(4,2)=44: qA(4,3)=53

 'sB =dispArray(qA)
 '行0を除いた範囲で列1で昇順にソート
 'Call quicksort(qA, 1, 1, UBound(qA), "As")
 'sE =dispArray(qA)
 '行0を除いた範囲で列2で降順にソート
 'Call quicksort(qA, 2, 1, UBound(qA), "Des")
 'sF =dispArray(qA)
 'msgbox sB & vbCrlf & sE & vbCrlf & sF


Sub quicksort2(sqa, col, first, last, sort) 						'クイックソート本体(昇順降順)(再帰)

	' sqa:配列名
	' col:ソート対象0から始まる列番号
	' first:ソート行範囲最初行番号
	' last:ソート行範囲最終行番号
	' sort:昇・降順別 "As"/"Des"と文字列を記述する。

	Dim point, f, l, m, wk
	point = sqa(first,col)
	f = first: l = last

	Do
		If sort = "As" then
			While (sqa(f,col) < point)
				f = f + 1
			Wend
         	While (sqa(l,col) > point)
				l = l - 1
			Wend
        Else
			While (sqa(f,col) > point)
				f = f + 1
			Wend
			While (sqa(l,col) < point)
				l = l - 1
			Wend
		End if
		If (f >= l) Then Exit Do

		'tmp = sqa(f): sqa(f) = sqa(l): sqa(l) = tmp
		For m = 0 to UBound(sqa,2)
			wk = sqa(f,m)
			sqa(f, m) = sqa(l, m)
			sqa(l, m) = wk
		Next
  		f = f + 1
		l = l - 1
	Loop
	If first < f - 1 Then quicksort2 sqa, col, first, f - 1, sort
	If l + 1 < last  Then quicksort2 sqa, col, l + 1, last, sort

End Sub


'配列内容を表示する
Function dispArray(qD)

	Dim i, j, sA
	For i = 0 To UBound(qD, 1)
		For j = 0 To UBound(qD, 2)
			sA = sA + CStr(qD(i, j)) & ", "
		Next
		sA = Left(sA, Len(sA)-2)									'末尾の,を削除
		sA = sA & vbCrLf
	Next
	sA = sA & vbCrLf
	dispArray = sA

End Function


</script>
		

ここまでは、VBScript ですが、これ以降は参考例が多い Javascript にしました。
撮影位置計算は、画像内に埋め込まれているExif情報の撮影時刻とGPXの時刻情報とを照合して、最も近い時刻の位置情報を撮影地点とするものです。(GPXは数秒間隔でログを取っていますから、撮影時刻にピッタリ一致するということは、ほぼ期待できませんから最も近い時刻を探し出して、それを撮影ポイントとしています。GPXの記録間隔が数秒ですから位置の誤差は2~3m程度です)

ヒューベニの公式については、過去のページにちょっとだけ詳しく書いていますから、ご覧ください。


<script language="Javascript">

//--------------------------------------------
// 撮影位置計算
//--------------------------------------------

function createCameraPoint(){

	gpxPath2 = "file:///" + gpxPath.replace(/\\/g, "\/");
	var dateStr, timeStr, jst;
	var stime = new Array();
	var gtime = new Array();
	var gdist = new Array();
	var sstr  = new Array();
	var gstr  = new Array();
	var gpsPoints = new Array();
	var camPoints = new Array();
	var seglen;

	//GPXのデータを取得する
	$.get(gpxPath2, function(xml){

		var seg = $("trkseg", xml);

		//切れているログを処理
		var trkpt, lat, lng, i, j, ele, gmt, utc, gpsTime, jpgTime, dt, diffTime, mySt, myGl, Od, Ev
		var lat1 = "";
		var lat2 = "";
		var lng1 = "";
		var lng2 = "";
		var dist1 = 0;
		var dist2 = 0;

		seglen = seg.length;

		for (n = 0; n < seg.length; n++) {

			if (seglen > 1) {
				mySt = "区間" + (n+1) + " start";
				myGl = "区間" + (n+1) + " goal";
			} else {
				mySt = "start";
				myGl = "goal";
			}

			Od = (n+1) * 2;
			Ev = (n+1) * 2 - 1;
			mkMax = mkLen + seglen * 2;

			var gpx = $("trkpt", seg[n]);

//mkData(i,x)	x: 0=lat 1=lon 2=ファイル名 3=撮影Time(utc) 4=高度 5=title 6=coment 7=移動距離(km) 8=撮影Time(m秒)  9=DiffTime(仮の撮影Time)

			//受け取った結果から緯度経度データを取り出し、配列に格納
			for (i = 0; i < gpx.length; i++) {
				if (i==0) {
					lat1 = "";
					lat2 = "";
					lng1 = "";
					lng2 = "";
					dist1 = 0;
					dist2 = 0;
				}
				lat = $(gpx[i]).attr("lat");							//<trkpt lat="yyy" lon="xxx">からyyy,xxxを取り出す
				lng = $(gpx[i]).attr("lon");
				ele = $(gpx[i]).find("ele").text();
				gmt = $(gpx[i]).find("time").text();					//2015-08-01T21:42:44Z

				gpsTime = gpsTime2JSTtimeStr(gmt);						//9時間プラスした日本標準時、2015/8/2 6:42:44 表示
				gpsTmm  = new Date(gpsTime).getTime();

				lat1 = lat;
				lng1 = lng;
				if (lat1 != "" && lat2 !=""){
					dist1 = hubeny(lat1, lng1, lat2, lng2);				//gpsポイント間の距離
				}
				dist2 += dist1;											//ポイント間の距離の累積値
				lat2 = lat1;
				lng2 = lng1;

				for (j=0; j <= mkLen; j++) {
					var myTime = new Date(mkData(j,3)).getTime();
					diffTime = Math.abs(gpsTmm - myTime);
					if (mkData(j,9) > diffTime) {
						mkData(j,9) = diffTime;
						mkData(j,0) = lat;
						mkData(j,1) = lng;
						mkData(j,4) = Math.round(ele);
						mkData(j,7) = dist2;							//ポイントまでの距離
						mkData(j,8) = myTime;							//Time(mm秒)
					}
				}
				if (i == 0) {
					mkData(mkLen+Ev,0) = lat;
					mkData(mkLen+Ev,1) = lng;
					mkData(mkLen+Ev,2) = "";
					mkData(mkLen+Ev,3) = gpsTime2gpsTimeHmmssStr(gpsTime);
					mkData(mkLen+Ev,4) = Math.round(ele);
					mkData(mkLen+Ev,5) = mySt;
					mkData(mkLen+Ev,7) = dist2;								//ポイントまでの距離
					mkData(mkLen+Ev,8) = new Date(gpsTime).getTime();		//utc(mm秒)
				}
				if (i == gpx.length-1) {
					mkData(mkLen+Od,0) = lat;
					mkData(mkLen+Od,1) = lng;
					mkData(mkLen+Od,2) = "";
					mkData(mkLen+Od,3) = gpsTime2gpsTimeHmmssStr(gpsTime);
					mkData(mkLen+Od,4) = Math.round(ele);					//高度
					mkData(mkLen+Od,5) = myGl;
					mkData(mkLen+Od,7) = dist2;								//ポイントまでの距離
					mkData(mkLen+Od,8) = new Date(gpsTime).getTime();		//utc(mm秒)
				}
				gpsPoints.push(lat, lng, ele, gpsTime, gpsTmm, dist2);
			}
		}
	});

	var wtime = 0;
	var wdist = 0;
	for (n=0; n < seglen; n++ ){
		wtime += (mkData(mkLen+(n+1)*2,8) - mkData(mkLen+(n+1)*2-1,8))/1000;		//ウォークタイム(秒)
		wdist += mkData(mkLen+(n+1)*2,7);
	}
	walkDist = wdist;
	walkTime = parseInt(wtime/3600,10) + ":" + ("0" + parseInt((wtime % 3600)/60,10)).slice(-2);			//歩いた時間(セグメント時間の累計)
	return mkMax;
}


// GPX形式TimeをJSTタイムに書き換える (例)2015-08-01T21:42:44Z → [JSTStr] "2015/08/02 6:42:44"
function gpsTime2JSTtimeStr(s){
	var t = s.replace(/-/g,"/");
	t = t.replace(/T/," ");
	t = t.match(/^\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}:\d{2}/);
	var d = new Date(t);
	d = new Date(d.setHours(d.getHours() + 9));							//日本標準にするため9時間プラス
	d = d.toLocaleString();												//2015年8月2日 6:42:44 形式
	d = d.replace(/年|月/g,"/");
	d = d.replace(/日/g,"");											//2015/8/2 6:42:44
	return (d);
}

// JSTタイムから h:mm:ss 形式を得る (例)[JSTStr] "2015/08/02 6:42:44" → 6:42:44
function gpsTime2gpsTimeHmmssStr(s){
	var d = new Date(s);
//	d = new Date(d.setSeconds(d.getSeconds() + 30));					//秒を四捨五入するために30秒加算
	var hmm = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
	return (hmm);
}

// 日付形式を書き換える DDMMYY形式 (例)1994年11月19日 → [NMEA]191194 → [dateStr] "1994/11/19"
function gpsDate2dateStr(s){
    var yy = s.substr(0,4);
    var mm = s.substr(5,2);
    var dd = s.substr(8,2);
    return (yy + '/' + mm + '/' + dd);
}

// 時刻形式を書き換える hhmmss形式 (例)22:54:46 → [NMEA] 225446.000 → [timeStr] "22:54:46"   (例)8:54:46 → [NMEA] 85446.000 → [timeStr] " 8:54:46"
function gpsTime2timeStr(s){
	var pos = s.indexOf(":");
	var hh = s.substr(pos-2, 2);
	var mm = s.substr(pos+1, 2);
	var ss = s.substr(pos+4, 2);
    return (hh + ':' + mm + ':' + ss);
}

function dateTimeStr2dateJST(dateStr,timeStr){
    var d = new Date(dateStr + ' ' + timeStr);
    return d;
}


//--------------------------------------------
// 移動距離計算			ヒュベニの公式(URL=yamadarake.jp/trdi/report000001.html と URL=tech-blog.s-yoshiki.com/2018/05/92/ を参考にしました)
//--------------------------------------------

function hubeny(lat1, lng1, lat2, lng2) {
    function rad(deg) {
        return deg * Math.PI / 180;							//degreeをラジアンに変換
    }

    lat1 = rad(lat1);
    lng1 = rad(lng1);
    lat2 = rad(lat2);
    lng2 = rad(lng2);

    var latDiff = lat1 - lat2;								// 緯度差
    var lngDiff = lng1 - lng2;								// 経度差
    var latAvg = (lat1 + lat2) / 2.0;						// 緯度の平均

    var a = 6378137.0;										// 赤道半径(WGS84系)
    var b = 6356752.314245;									// 極半径(WGS84系)
    var e2 = 0.00669437999019758;							// 第一離心率^2 e^2 = (a^2 - b^2) / a^2
    var a1e2 = 6335439.32729246;							// 赤道上の子午線曲率半径  a(1 - e^2)

    var sinLat = Math.sin(latAvg);
    var W2 = 1.0 - e2 * (sinLat * sinLat);

    var M = a1e2 / (Math.sqrt(W2) * W2);					// 子午線曲率半径M
    var N = a / Math.sqrt(W2);								// 卯酉線(ぼうゆうせん)曲率半径N

    t1 = M * latDiff;
    t2 = N * Math.cos(latAvg) * lngDiff;
    return Math.sqrt((t1 * t1) + (t2 * t2));
}
</script>
		

コードは以上です。古い頭の爺さんの作ったものですから、とても公開するような代物ではありませんが、この世の記念として書き残したつもりです。ご容赦ください。

アーカイブス