Python + Jupyter で地図上に円を描く

# 

Python + Jupyter で地図上に絵を描くには folium というライブラリを使います。例えばこんなふうにすると稚内から 1000 km の地点に円を描く事が出来ます。


```python
import folium

WAKKANAI = [45.4171, 141.6761] # 稚内 (緯度、経度)

m = folium.Map(location=WAKKANAI, zoom_start=4)
folium.Circle(location=WAKKANAI, radius=1_000_000).add_to(m)
m
```

![wakkanai-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/68b43640-e4da-a40c-c299-a786dc41b35b.png)

ただ、これは見て楽しむ以外あまり役に立たないでしょう。folium は円の描画を Python ではなくブラウザ側で行うので、例えばせっかく求めた座標を Python を使って「稚内と大阪の両方から 1000 km 離れた点を求める」ような問題に使えないからです。

Python で位置情報を扱うためには shapely というライブラリを使います。shapely には円を描画する機能が無いので、「稚内の位置からのオフセットを求める」事で代用してみます。

shapely には地図上の距離を指定する機能も無いので、1000 km を角度に換算します。folium での座標の並びが「緯度・経度」なのに対して、shapely では「経度・緯度」なので逆にしないといけないのがちょっと面倒です。


```python
import math
from shapely.geometry import Point, mapping

EQUATORIAL_RADIUS = 6_378_137 # 地球の半径
distance_degree = 1_000_000 / (EQUATORIAL_RADIUS * 2 * math.pi) * 360 # 1000 km を角度に変換。

point_wakkanai = Point(reversed(WAKKANAI))
circle_wakkanai = point_wakkanai.buffer(distance_degree) # 経度,緯度の順にするため reverse

m = folium.Map(location=WAKKANAI, zoom_start=4)
folium.GeoJson(circle_wakkanai).add_to(m)
m
```

![wakkanai-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/06315d66-dabd-e7d4-e091-f2d627d3b96e.png)

なんか変ですね。。。円ではなく多角形なのは仕方ないとして、タマゴ型になってしまいました。

folium は座標を緯度経度で示しますが、shapely は直交座標とみなして図形を操作します。緯度経度というのは球面を表すための物なので、shapely で求めた座標を地図上にそのまま描いてしまうと歪むのです。緯度が高い場所の経度1度は、赤道上の経度1度よりも短くなります。

Python には、座標変換を行うための [pyproj](https://pyproj4.github.io/pyproj/stable/index.html) というライブラリがあります。pyproj で座標変換を行うためには、変換前と変換後の座標系が必要です。pyproj では座標系を "proj-string" https://proj.org/usage/quickstart.html という物で示します。 

変換前の座標系はややこしいですが、中心からの距離と角度が正しくなる正距方位図法を使いましょう。稚内を中心とする正距方位図法を表す "proj-string" は次のようになります。

    aeqd_wakkanai = "+proj=aeqd +lat_0=45.4171 +lon_0=141.6761 +x_0=0 +y_0=0"

変換後の座標系は、電子地図や GPS データ使われる WGS84 を表す proj-stinrg は次のようになります。

    wgs84 = "+proj=longlat +datum=WGS84"

これを使って正距方位図法に描いた円を WGS84 に変換すると、WGS84 の地図上に円が描けます。


```python
import pyproj
from shapely.ops import transform

def circle(point, radius):

    aeqd = f'+proj=aeqd +lat_0={point.y} +lon_0={point.x} +x_0=0 +y_0=0' # point 中心の正距方位図法
    wgs84 = "+proj=longlat +datum=WGS84" # WGS84
    project = pyproj.Transformer.from_crs(aeqd, wgs84).transform # 正距方位図法からWGS84への変換。

    circle_aeqd = point.buffer(radius)
    return transform(project, circle_aeqd)

circle_wakkanai = circle(point_wakkanai, 1_000_000)

m = folium.Map(location=WAKKANAI, zoom_start=4)
folium.GeoJson(circle_wakkanai).add_to(m)
m
```

![wakkanai-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/386c1e88-f32b-8bba-a7a1-f4b87da56434.png)


一応、稚内と大阪の両方から 1000km 離れた場所を探してみます。


```python
OSAKA = [34.6869, 135.5259]

point_osaka = Point(reversed(OSAKA))
circle_osaka = circle(point_osaka, 1_000_000)

intersection = circle_wakkanai.boundary.intersection(circle_osaka.boundary) # 二つの円の交点

m = folium.Map(location=WAKKANAI, zoom_start=4)
folium.GeoJson(circle_wakkanai).add_to(m)
folium.GeoJson(circle_osaka).add_to(m)
folium.GeoJson(intersection).add_to(m)
m
```

![wakkanai-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/b497985d-4942-addb-ece3-1d51fe95892e.png)

検算として距離を測ります。

詳細は私もよく知りませんが、pyproj の機能で距離も測れます。


```python
geod = pyproj.Geod(ellps='WGS84')
print("大阪からの距離: ", 
    geod.inv(point_osaka.x, point_osaka.y, intersection[0].x, intersection[0].y)[2])
print("稚内からの距離: ", 
    geod.inv(point_wakkanai.x, point_wakkanai.y, intersection[0].x, intersection[0].y)[2])
```

    大阪からの距離:  999640.1189235467
    稚内からの距離:  998819.0687679343


ちょっと違いますが、こんなもんでしょうか? 誤差がどこで出るのか分かったらまた書きます。

## 参考

* folium: https://leafletjs.com/
* shapely: https://shapely.readthedocs.io/en/stable/
* pyproj: https://pyproj4.github.io/pyproj/stable/index.html
* proj-string: https://proj.org/usage/quickstart.html
* [Creating buffer circle x kilometers from point using Python?](https://gis.stackexchange.com/questions/289044/creating-buffer-circle-x-kilometers-from-point-using-python)
* 正距方位図法: https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%B7%9D%E6%96%B9%E4%BD%8D%E5%9B%B3%E6%B3%95