前回の記事で、"指定した2点間を地球が球体なことを考慮し最短経路で結ぶ”地図(フライトマップ風の地図)を作成するスクリプトを掲載したが、この地図をマップアニメーション化することを考える。
1. マップアニメーションの作成
マップアニメーションは、LeafletとJavaScriptのsetIntervalやanimate関数を併用し作成する。(HTML上にJavaScript(Leafletとアニメーションの関数)を記述。)
Leaflet自体にはアニメーション機能がないため、Foliumでの操作はできない。
HTMLとJavaScriptの関係は上記の通り(「HTMLとJavaScriptってなんだ...?」という思いからまとめたもの)。
今回の地図作成では、Google Colaboratoryでコード作成/実行/結果確認をしたいので、マジックコマンドやHTMLテンプレートの利用により、python上で全ての作業が完結するようにした。
2-a. フライトマップ風の地図(マップアニメーション)
スクリプトは以下の通り。
# 羽田からDallasまでの飛行ルートマップ風のアニメーションを描くスクリプト
# マジックコマンドを利用してPython上で生成から出力まで実行
%%html
<!DOCTYPE html>
<html>
<head>
<title>Great Circle Animation</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
</head>
<body>
<div id="map" style="height: 500px;"></div>
<script>
var map = L.map('map').setView([35.5533, 135], 3); // 中心を東経135度に変更
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
}).addTo(map);
var startPoint = L.latLng(35.5533, 139.7811); // Haneda Airport
var endPoint = L.latLng(32.7767, -96.7970); // Dallas
var polyline = L.polyline([], { color: 'blue' }).addTo(map);
var points = [];
var numPoints = 100;
var index = 0;
function animateLine() {
var lat1 = startPoint.lat * Math.PI / 180; // 緯度1をラジアンに変換
var lon1 = startPoint.lng * Math.PI / 180; // 緯度2をラジアンに変換
var lat2 = endPoint.lat * Math.PI / 180; // 経度1をラジアンに変換
var lon2 = endPoint.lng * Math.PI / 180; // 経度2をラジアンに変換
var d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin((lon1 - lon2) / 2), 2))); //球面三角法のハーフバーシンの公式を使用して、2点間の角度を求め、その角度から大円距離を計算
for (var i = 0; i <= numPoints; i++) {
var f = i / numPoints;
var A = Math.sin((1 - f) * d) / Math.sin(d);
var B = Math.sin(f * d) / Math.sin(d);
var x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2) * Math.cos(lon2);
var y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2) * Math.sin(lon2);
var z = A * Math.sin(lat1) + B * Math.sin(lat2);
var lat = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))) * 180 / Math.PI;
var lon = Math.atan2(y, x) * 180 / Math.PI;
// 経路を-45度から315度の範囲に修正
if (lon < -45) {
lon += 360;
} else if (lon > 315) {
lon -= 360;
}
points.push(L.latLng(lat, lon));
}
animate();
}
function animate() {
if (index < points.length) {
polyline.addLatLng(points[index]);
index++;
setTimeout(animate, 100); // アニメーション速度を調整するための遅延
}
}
animateLine(); // アニメーションを開始
</script>
</body>
</html>
gistはこちら → gist:MapAnime_flight.py
このスクリプトを掻い摘んで説明する。
① マジックコマンドの利用を宣言
%%html
生成から実行、出力結果確認までPython上で実行するため、「%%html」でHTMLの記述を囲む。
② 羽田空港とDallasをそれぞれ出発点と到着点に設定
var startPoint = L.latLng(35.5533, 139.7811); // Haneda Airport
var endPoint = L.latLng(32.7767, -96.7970); // Dallas
③ スクリプト内で2点間の経路を求め、経路上の各点の座標を配列に格納
function animateLine() {
var lat1 = startPoint.lat * Math.PI / 180; // 緯度1をラジアンに変換
var lon1 = startPoint.lng * Math.PI / 180; // 緯度2をラジアンに変換
var lat2 = endPoint.lat * Math.PI / 180; // 経度1をラジアンに変換
var lon2 = endPoint.lng * Math.PI / 180; // 経度2をラジアンに変換
var d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin((lon1 - lon2) / 2), 2))); //球面三角法のハーフバーシンの公式を使用して、2点間の角度を求め、その角度から大円距離を計算
for (var i = 0; i <= numPoints; i++) {
var f = i / numPoints;
var A = Math.sin((1 - f) * d) / Math.sin(d);
var B = Math.sin(f * d) / Math.sin(d);
var x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2) * Math.cos(lon2);
var y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2) * Math.sin(lon2);
var z = A * Math.sin(lat1) + B * Math.sin(lat2);
var lat = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))) * 180 / Math.PI;
var lon = Math.atan2(y, x) * 180 / Math.PI;
2点間の距離(弧の長さ)計算は今回のスクリプトでは’ハバーサインの公式’が使われている。
(参考→)Calculate distance, bearing and more between Latitude/Longitude points
④ animate 関数でアニメーションを描写
function animate() {
if (index < points.length) {
polyline.addLatLng(points[index]);
index++;
setTimeout(animate, 100); // アニメーション速度を調整するための遅延
animate 関数は、points 配列に格納された座標を順番に取り出し、それを polyline に追加する。
以上で、フライトマップ風の地図(マップアニメーション)を作成することができた。
3. 参考文献
・「GISファイルフォーマットの簡単なまとめ/地図のワークブック」
・Leaflet
・Leafletの使い方(埼玉大学教育学部人文地理学 谷謙二研究室)
・OpenStreetMap Japan
・TRAINOCAMP「HTMLとJavaScriptを使いこなす!」
・BrainGate「マークアップ(Markup)とは?」(マークアップ言語とプログラミング言語の違い)
・Jupyter Notebook マジックコマンド自分的まとめ
・Jupyter Notebookでマジックコマンド一覧を確認する%lsmagic
・Calculate distance, bearing and more between Latitude/Longitude points
・Jetlovers
・chatGPT 3.5