簡體   English   中英

在地圖投影后不連續時修復形狀多邊形對象

[英]Fix up shapely polygon object when discontinuous after map projection

這個演示程序(打算在IPython的筆記本電腦上運行,你需要matplotlibmpl_toolkits.basemappyprojshapely )應該繪制地球表面上越來越大的圓圈。 只要圓圈沒有越過其中一個極點,它就能正常工作。 如果發生這種情況,在地圖上繪制時結果完全是胡說八道(參見下面的單元格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()

單元格2的輸出

## 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()

單元格3的輸出

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM