2
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

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

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

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

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

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

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

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

Python には、座標変換を行うための pyproj というライブラリがあります。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 の地図上に円が描けます。

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

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

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

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

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

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

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

参考

2
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?