繁体   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