フライトマップ風の地図（マップアニメーション）をつくる

　[前回の記事](https://qiita.com/hamanasu/items/ea071823fc732cb5e21f)で、"指定した２点間を地球が球体なことを考慮し最短経路で結ぶ”地図（フライトマップ風の地図）を作成するスクリプトを掲載したが、この地図をマップアニメーション化することを考える。

## 1. マップアニメーションの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3325469/cbfb8ea5-1a9c-3d10-b665-e9fc2084a2b2.png)

　マップアニメーションは、<font color="#9A6229">Leaflet</font>と<font color="#0000ff">JavaScript</font>の<font color="#4db56a">setInterval</font>や<font color="#4db56a">animate</font>関数を併用し作成する。（HTML上にJavaScript(Leafletとアニメーションの関数)を記述。）
　<font color="#9A6229">Leaflet</font>自体にはアニメーション機能がないため、<font color="#9A6229">Folium</font>での操作はできない。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3325469/79328dfc-b0ff-4d08-23ba-9cf3df93bb5a.png)
　 <font color="#0000ff">HTML</font>と<font color="#0000ff">JavaScript</font>の関係は上記の通り（「HTMLとJavaScriptってなんだ...？」という思いからまとめたもの）。
　今回の地図作成では、Google Colaboratoryでコード作成／実行／結果確認をしたいので、<front>**マジックコマンド**</front>や<front>**HTMLテンプレート**</front>の利用により、python上で全ての作業が完結するようにした。

  ## 2-a. フライトマップ風の地図（マップアニメーション）
　スクリプトは以下の通り。
  ```python:MapAnime_flight.py
  # 羽田から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](https://gist.github.com/hamanasu12/406832fb59c302369d1b98aad3f3f6f7)

　このスクリプトを掻い摘んで説明する。

① マジックコマンドの利用を宣言
```python
%%html
```
　生成から実行、出力結果確認までPython上で実行するため、「<font color="#0000ff">%%html</font>」でHTMLの記述を囲む。

 ②　羽田空港とDallasをそれぞれ出発点と到着点に設定
 ```python
  var startPoint = L.latLng(35.5533, 139.7811); // Haneda Airport
  var endPoint = L.latLng(32.7767, -96.7970); // Dallas
```

③　スクリプト内で２点間の経路を求め、経路上の各点の座標を配列に格納
```python
  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;
```

　
 ２点間の距離(弧の長さ)計算は今回のスクリプトでは’ハバーサインの公式’が使われている。
 （参考→）[Calculate distance, bearing and more between Latitude/Longitude points](http://www.davesamuels.com/cams/camspointing/scripts/latlong.html.original)

 ④　<font color="#4db56a">animate</font> 関数でアニメーションを描写
```python
  function animate() {
    if (index < points.length) {
      polyline.addLatLng(points[index]);
      index++;
      setTimeout(animate, 100); // アニメーション速度を調整するための遅延
```
　animate 関数は、points 配列に格納された座標を順番に取り出し、それを polyline に追加する。

　実行結果をgif化したものは以下の通り。
![fly_route2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3325469/cc63c54f-77d9-5d46-03ce-737629a8c4c2.gif)


　以上で、フライトマップ風の地図（マップアニメーション）を作成することができた。

 ## 3. 参考文献
・[「GISファイルフォーマットの簡単なまとめ/地図のワークブック」](https://lonlat.info/gis-file-format/)
・[Leaflet](https://leafletjs.com/)
・[Leafletの使い方（埼玉大学教育学部人文地理学　谷謙二研究室）](https://ktgis.net/service/leafletlearn/index.html)
・[OpenStreetMap Japan](https://openstreetmap.jp)
・[TRAINOCAMP「HTMLとJavaScriptを使いこなす！」](https://camp.trainocate.co.jp/magazine/howto-javascript-html/)
・[BrainGate「マークアップ（Markup）とは？」（マークアップ言語とプログラミング言語の違い）](https://www.brain-gate.net/content/column/system-program-markup/#:~:text=%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E%E3%81%A8%E3%81%AF%E3%80%81%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%BC,html%E3%82%84xml%E3%80%81CSS%E3%81%A7%E3%81%99%E3%80%82)
・[Jupyter Notebook マジックコマンド自分的まとめ](https://qiita.com/mgsk_2/items/437656b8ce42c03e41a6)
・[Jupyter Notebookでマジックコマンド一覧を確認する%lsmagic](https://note.nkmk.me/jupyter-notebook-ipython-lsmagic/)
・[Calculate distance, bearing and more between Latitude/Longitude points](http://www.davesamuels.com/cams/camspointing/scripts/latlong.html.original)
・[Jetlovers](https://www.jetlovers.com/)
・[chatGPT 3.5](https://chat.openai.com/)