[英]Fix up shapely polygon object when discontinuous after map projection
這個演示程序(打算在IPython的筆記本電腦上運行,你需要matplotlib
, mpl_toolkits.basemap
, pyproj
和shapely
)應該繪制地球表面上越來越大的圓圈。 只要圓圈沒有越過其中一個極點,它就能正常工作。 如果發生這種情況,在地圖上繪制時結果完全是胡說八道(參見下面的單元格2)
如果我在“虛空”中而不是在地圖上繪制它們(參見下面的單元格3),結果是正確的,如果你移除了從+180到-180經度的水平線,那么曲線的其余部分將是確實划定了所需圓的內部和外部之間的邊界。 但是,它們的錯誤在於多邊形無效( .is_valid
為False),更重要的是,多邊形的非零數字內部不包含地圖的正確區域。
我相信這種情況正在發生,因為shapely.ops.transform
對+180 == - 180經度的坐標奇點是盲目的。 問題是,如何檢測問題並修復多邊形,以便它包含地圖的正確區域? 在這種情況下,一個適當的修正方法是將(X,+ 180) - (X,-180)的水平線段替換為三條線,(X,+ 180) - (+ 90,+ 180) - (+ 90,-180) - (X,-180); 但要注意,如果圈走了在南極的修正線需要,而不是去南方。 而如果圈走了過兩極 ,我們會再有一個有效的多邊形,但其內部將是它應該是什么樣的補充。 我需要檢測所有這些情況並正確處理它們。 另外,我不知道如何“編輯”一個勻稱的幾何對象。
可下載的筆記本: https : //gist.github.com/zackw/e48cb1580ff37acfee4d0a7b1d43a037
## cell 1
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pyproj
from shapely.geometry import Point, Polygon, MultiPolygon
from shapely.ops import transform as sh_transform
from functools import partial
wgs84_globe = pyproj.Proj(proj='latlong', ellps='WGS84')
def disk_on_globe(lat, lon, radius):
aeqd = pyproj.Proj(proj='aeqd', ellps='WGS84', datum='WGS84',
lat_0=lat, lon_0=lon)
return sh_transform(
partial(pyproj.transform, aeqd, wgs84_globe),
Point(0, 0).buffer(radius)
)
## cell 2
def plot_poly_on_map(map_, pol):
if isinstance(pol, Polygon):
map_.plot(*(pol.exterior.xy), '-', latlon=True)
else:
assert isinstance(pol, MultiPolygon)
for p in pol:
map_.plot(*(p.exterior.xy), '-', latlon=True)
plt.figure(figsize=(14, 12))
map_ = Basemap(projection='cyl', resolution='c')
map_.drawcoastlines(linewidth=0.25)
for rad in range(1,10):
plot_poly_on_map(
map_,
disk_on_globe(40.439, -79.976, rad * 1000 * 1000)
)
plt.show()
## cell 3
def plot_poly_in_void(pol):
if isinstance(pol, Polygon):
plt.plot(*(pol.exterior.xy), '-')
else:
assert isinstance(pol, MultiPolygon)
for p in pol:
plt.plot(*(p.exterior.xy), '-', latlon=True)
plt.figure()
for rad in range(1,10):
plot_poly_in_void(
disk_on_globe(40.439, -79.976, rad * 1000 * 1000)
)
plt.show()
( http://www.die.net/earth/rectangular.html上顯示的陽光照射區域是一個例子,當投射到一個equirectangular地圖上時,穿過一個圓柱的圓圈應該是什么樣子,只要它今天不是晝夜平分點。)
手動修復投影多邊形結果並不是那么糟糕。 有兩個步驟:首先,找到在經度±180處穿過坐標奇點的多邊形的所有部分,並用偏移代替北極或南極,取最近的一個; 第二,如果生成的多邊形不包含原點,則將其反轉。 注意,必須執行這兩個步驟, 無論是否認為投影多邊形是“無效”; 取決於起點的位置,它可以穿過一個或兩個極而不是無效的。
這可能不是最有效的方法,但它確實有效。
import pyproj
from shapely.geometry import Point, Polygon, box as Box
from shapely.ops import transform as sh_transform
from functools import partial
wgs84_globe = pyproj.Proj(proj='latlong', ellps='WGS84')
def disk_on_globe(lat, lon, radius):
"""Generate a shapely.Polygon object representing a disk on the
surface of the Earth, containing all points within RADIUS meters
of latitude/longitude LAT/LON."""
aeqd = pyproj.Proj(proj='aeqd', ellps='WGS84', datum='WGS84',
lat_0=lat, lon_0=lon)
disk = sh_transform(
partial(pyproj.transform, aeqd, wgs84_globe),
Point(0, 0).buffer(radius)
)
# Fix up segments that cross the coordinate singularity at longitude ±180.
# We do this unconditionally because it may or may not create a non-simple
# polygon, depending on where the initial point was.
boundary = np.array(disk.boundary)
i = 0
while i < boundary.shape[0] - 1:
if abs(boundary[i+1,0] - boundary[i,0]) > 180:
assert (boundary[i,1] > 0) == (boundary[i,1] > 0)
vsign = -1 if boundary[i,1] < 0 else 1
hsign = -1 if boundary[i,0] < 0 else 1
boundary = np.insert(boundary, i+1, [
[hsign*179, boundary[i,1]],
[hsign*179, vsign*89],
[-hsign*179, vsign*89],
[-hsign*179, boundary[i+1,1]]
], axis=0)
i += 5
else:
i += 1
disk = Polygon(boundary)
# If the fixed-up polygon doesn't contain the origin point, invert it.
if not disk.contains(Point(lon, lat)):
disk = Box(-180, -90, 180, 90).difference(disk)
assert disk.is_valid
assert disk.boundary.is_simple
assert disk.contains(Point(lon, lat))
return disk
另一個問題 - mpl_toolkits.basemap.Basemap.plot
生成垃圾 - 不能通過修復上面的多邊形來糾正。 但是,如果您手動將多邊形投影到地圖坐標中,然后使用descartes.PolygonPatch
繪制它,只要投影具有矩形邊界,這對我來說就足夠了。 (如果在地圖邊界的所有直線上添加了很多額外的點,我認為它適用於任何投影。)
%matplotlib inline
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
from descartes import PolygonPatch
plt.figure(figsize=(14, 12))
map_ = Basemap(projection='cea', resolution='c')
map_.drawcoastlines(linewidth=0.25)
for rad in range(3,19,2):
plt.gca().add_patch(PolygonPatch(
sh_transform(map_,
disk_on_globe(40.439, -79.976, rad * 1000 * 1000)),
alpha=0.1))
plt.show()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.