我处理组织在不规则二维网格上的卫星数据,其尺寸为扫描线(沿轨道尺寸)和地面像素(跨轨道尺寸)。每个地面像素的纬度和经度信息存储在辅助坐标变量中。
给定(lat,lon)点,我想确定我的数据集上最接近的地面像素。
让我们构建一个10x10玩具数据集:
import numpy as np
import xarray as xr
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
%matplotlib inline
lon, lat = np.meshgrid(np.linspace(-20, 20, 10),
np.linspace(30, 60, 10))
lon += lat/10
lat += lon/10
da = xr.DataArray(data = np.random.normal(0,1,100).reshape(10,10),
dims=['scanline', 'ground_pixel'],
coords = {'lat': (('scanline', 'ground_pixel'), lat),
'lon': (('scanline', 'ground_pixel'), lon)})
ax = plt.subplot(projection=ccrs.PlateCarree())
da.plot.pcolormesh('lon', 'lat', ax=ax, cmap=plt.cm.get_cmap('Blues'),
infer_intervals=True);
ax.scatter(lon, lat, transform=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines(draw_labels=True)
plt.tight_layout()
请注意,lat / lon坐标标识中心像素,xarray会自动推断像素边界。
现在,我想要确定离罗马最近的地面像素。
到目前为止,我提出的最佳方法是在堆叠扁平的lat / lon阵列上使用scipy的kdtree:
from scipy import spatial
pixel_center_points = np.stack((da.lat.values.flatten(), da.lon.values.flatten()), axis=-1)
tree = spatial.KDTree(pixel_center_points)
rome = (41.9028, 12.4964)
distance, index = tree.query(rome)
print(index)
# 36
然后我应用unravel_index
来获取我的scanline / ground_pixel索引:
pixel_coords = np.unravel_index(index, da.shape)
print(pixel_coords)
# (3, 6)
这给了我(罗马)最接近地面像素的scanline / ground_pixel坐标:
ax = plt.subplot(projection=ccrs.PlateCarree())
da.plot.pcolormesh('lon', 'lat', ax=ax, cmap=plt.cm.get_cmap('Blues'),
infer_intervals=True);
ax.scatter(da.lon[pixel_coords], da.lat[pixel_coords],
marker='x', color='r', transform=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines(draw_labels=True)
plt.tight_layout()
我确信必须有一种更优雅的方法来解决这个问题。特别是,我想摆脱展平/解开的步骤(我在二维数组上构建kdtree的尝试都失败了),并尽可能地利用我的xarray数据集变量(例如,添加一个新的centre_pixel维度,并将其用作KDTree
}的输入。
答案 0 :(得分:2)
我将回答我自己的问题,因为我相信我提出了一个不错的解决方案,我在blog post就这个主题进行了更详细的讨论。
首先,将地球表面上两点之间的距离定义为两个纬度/经度对之间的欧氏距离可能会导致两个点之间的距离不准确。因此,最好先将坐标转换为ECEF坐标,并在变换后的坐标上构建KD树。假设行星表面上的点(h = 0),坐标变换就这样完成:
def transform_coordinates(coords):
""" Transform coordinates from geodetic to cartesian
Keyword arguments:
coords - a set of lan/lon coordinates (e.g. a tuple or
an array of tuples)
"""
# WGS 84 reference coordinate system parameters
A = 6378.137 # major axis [km]
E2 = 6.69437999014e-3 # eccentricity squared
coords = np.asarray(coords).astype(np.float)
# is coords a tuple? Convert it to an one-element array of tuples
if coords.ndim == 1:
coords = np.array([coords])
# convert to radiants
lat_rad = np.radians(coords[:,0])
lon_rad = np.radians(coords[:,1])
# convert to cartesian coordinates
r_n = A / (np.sqrt(1 - E2 * (np.sin(lat_rad) ** 2)))
x = r_n * np.cos(lat_rad) * np.cos(lon_rad)
y = r_n * np.cos(lat_rad) * np.sin(lon_rad)
z = r_n * (1 - E2) * np.sin(lat_rad)
return np.column_stack((x, y, z))
然后,我们可以通过转换数据集坐标来构建KD树,负责将2D网格展平为lat / lon元组的一维序列。这是因为KD树输入数据需要是(N,K),其中N是点数,K是维数(在我们的例子中K = 2,因为我们假设没有高度成分)。
# reshape and stack coordinates
coords = np.column_stack((da.lat.values.ravel(),
da.lon.values.ravel()))
# construct KD-tree
ground_pixel_tree = spatial.cKDTree(transform_coordinates(coords))
查询树现在就像将点的纬度/经度坐标转换为ECEF并将它们传递给树的query
方法一样简单:
rome = (41.9028, 12.4964)
index = ground_pixel_tree.query(transform_coordinates(rome))
在这样做时,我们需要解析原始数据集形状的索引,以获得scanline / ground_pixel索引:
index = np.unravel_index(index, self.shape)
我们现在可以使用这两个组件来索引我们的原始xarray数据集,但我们也可以构建两个索引器来与xarray pointwise indexing功能一起使用:
index = xr.DataArray(index[0], dims='pixel'), \
xr.DataArray(index[1], dims='pixel')
同时获得最近的像素既简单又优雅:
da[index]
请注意,我们也可以同时查询多个点,通过构建上面的索引器,我们仍然可以通过一次调用来索引数据集:
da[index]
然后将包含最接近的地面像素的数据集的子集返回到我们的查询点。