在正投影中绘制圆圈与cartopy

Bia*_*ble 3 python orthographic cartopy

我试图使用cartopy在给定的半径范围内绘制圆圈.我想使用正交投影绘制,该投影以圆心为中心.

我使用以下python代码进行测试:

import numpy as np
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

# example: draw circle with 45 degree radius around the North pole
lon = 0
lat = 90
r = 45

# find map ranges (with 5 degree margin)
minLon = lon - r - 5
maxLon = lon + r + 5
minLat = lat - r - 5
maxLat = lat + r + 5

# define image properties
width = 800
height = 800
dpi = 96
resolution = '50m'

# create figure
fig = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi)
ax = fig.add_subplot(1, 1, 1, projection=ccrs.Orthographic(central_longitude=lon, central_latitude=lat))
ax.set_extent([minLon, maxLon, minLat, maxLat])
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'lakes', resolution, edgecolor='none', facecolor=cfeature.COLORS['water']), alpha=0.5)
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'rivers_lake_centerlines', resolution, edgecolor=cfeature.COLORS['water'], facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_1_states_provinces_lines', resolution, edgecolor='gray', facecolor='none'))
ax.add_patch(mpatches.Circle(xy=[lon, lat], radius=r, color='red', alpha=0.3, transform=ccrs.PlateCarree(), zorder=30))
fig.tight_layout()
plt.savefig('CircleTest.png', dpi=dpi)

plt.show()
Run Code Online (Sandbox Code Playgroud)

我在赤道得到了一个正确的结果(lat在上面的例子中设置为0): 赤道上的例子

但是当我走向极点时,形状会扭曲(lat = 45): 45度示例

在极点我只看到圆的四分之一: 极点的例子

如果视图正确居中,我希望在正投影中总能看到完美的圆形.我也尝试在add_patch方法中使用不同的变换,但是圆圈完全消失了!

小智 8

这可能有点晚了,但是 Cartopy 中有一个方便的函数可以实现这一点。

我们可以使用 Cartopy 的 .circle 函数(文档)从测地坐标系中的特定(经度和纬度)生成具有指定半径的点环,然后使用 Shapely 用这些点绘制多边形。

这看起来像下面这样

circle_points = cartopy.geodesic.Geodesic().circle(lon=lon, lat=lat, radius=radius_in_meters, n_samples=n_points, endpoint=False)
geom = shapely.geometry.Polygon(circle_points)
ax.add_geometries((geom,), crs=cartopy.crs.PlateCarree(), facecolor='red', edgecolor='none', linewidth=0)
Run Code Online (Sandbox Code Playgroud)

将 crs 指定为 PlateCarree 并不重要,只是避免了 Shapely 的警告。您将保留您想要的投影。但是,如果您直接使用极点上的圆心进行绘图,您可能仍然会遇到问题,并且可能需要进行一些奇特的转换(最近没有测试过,但回想一下几个月前它有点不稳定) 。

您还可以使用 Cartopy 使用的 pyproj 库(特别是 Geod 类)手动计算这些点。选择一个具有半径的点,然后通过 azmoths 进行循环,无论您希望圆的大小如何,都可以使用 .inv 或 .fwd 函数,类似于/sf/answers/3990194351/中的建议。我不推荐这种方法,但很久以前就用过它来完成同样的事情。


ajd*_*son 6

您在PlateCarree坐标中定义圆的方法不起作用,因为这是一个笛卡尔投影,并且在其上绘制的圆不一定是球形几何中的圆形(除非圆圈在(0,0)处,如您所见).

由于您希望结果在正交投影中为圆形,因此您可以使用原生坐标绘制圆.这需要首先定义以圆的中心为中心的正投影,然后计算圆的半径(以度数指定)将是投影坐标(距投影中心的距离).这样做很方便,因为它还为您提供了一种确定正确的地图范围的简洁方法.下面的示例通过将距离投影中心45度(北部,如果更方便)的点转换为正交坐标的半径来计算,并给出以下内容:

在此输入图像描述

完整代码如下:

import numpy as np
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

# example: draw circle with 45 degree radius around the North pole
lat = 51.4198101
lon = -0.950854653584
r = 45

# Define the projection used to display the circle:
proj = ccrs.Orthographic(central_longitude=lon, central_latitude=lat)


def compute_radius(ortho, radius_degrees):
    phi1 = lat + radius_degrees if lat <= 0 else lat - radius_degrees
    _, y1 = ortho.transform_point(lon, phi1, ccrs.PlateCarree())
    return abs(y1)

# Compute the required radius in projection native coordinates:
r_ortho = compute_radius(proj, r)

# We can now compute the correct plot extents to have padding in degrees:
pad_radius = compute_radius(proj, r + 5)

# define image properties
width = 800
height = 800
dpi = 96
resolution = '50m'

# create figure
fig = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi)
ax = fig.add_subplot(1, 1, 1, projection=proj)
# Deliberately avoiding set_extent because it has some odd behaviour that causes
# errors for this case. However, since we already know our extents in native
# coordinates we can just use the lower-level set_xlim/set_ylim safely.
ax.set_xlim([-pad_radius, pad_radius])
ax.set_ylim([-pad_radius, pad_radius])
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'lakes', resolution, edgecolor='none', facecolor=cfeature.COLORS['water']), alpha=0.5)
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'rivers_lake_centerlines', resolution, edgecolor=cfeature.COLORS['water'], facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_1_states_provinces_lines', resolution, edgecolor='gray', facecolor='none'))
ax.add_patch(mpatches.Circle(xy=[lon, lat], radius=r_ortho, color='red', alpha=0.3, transform=proj, zorder=30))
fig.tight_layout()
plt.savefig('CircleTest.png', dpi=dpi)
plt.show()
Run Code Online (Sandbox Code Playgroud)

  • 很好的答案@ajdawson +1 (2认同)