PostGIS航线分析
postgis-users 邮件列表中的一位社区成员最近提出了一个问题:
我有一张高程点表,我想计算出穿过这些点的航线的高程剖面图。 如何?
这个问题很好地展示了我最喜欢的一些空间工具,包括索引、球体上的点对点距离查询和最近邻查询。 原问题的作者很好地分享了他的高程数据,所以我可以给你看一些图片和一种方法。
1、每2KM高程点
数据集为欧洲1.5M高程点,每2km一个点。 当绘制在地图上时,你可以看到它非常密集。
表结构很简单:
Column | Type | Collation | Nullable | Default
-----------+------------------+-----------+----------+---------
latlng | geography | | |
lat | double precision | | |
lng | double precision | | |
elevation | real | | |
我们所需要的只是 latlng 列上的空间索引,以使我们的最近邻搜索更快。
CREATE INDEX elevation_latlng_x ON elevation USING GIST (latlng)
2、航线
航线不同于陆上航线,因为它们不仅“像乌鸦飞过一样”,而且“像乌鸦飞过一个球体”。 在更长的距离和更高的纬度上,效果更明显,但大圆路线与点对点路线并不完全相同。
例如,伦敦和华沙之间的直线可以这样构造:
SELECT ST_MakeLine(ST_MakePoint(-0.061,51.482), -- London
ST_MakePoint(21.252,52.150)) -- Warsaw
但是直线(平面上的最短路径)不是我们想要的,我们想要一个大圆(球体上的最短路径)。 因此,我们将线转换为地理类型,它使用大圆在顶点之间进行插值,然后对线进行 ST_Segmentize,以在端点之间添加新顶点。
SELECT ST_Segmentize(geography(
ST_MakeLine(
ST_MakePoint(-0.061,51.482),
ST_MakePoint(21.252,52.150))
), 4000)
红线是大圆,蓝线是原来的笛卡尔线。
3、最近点
对于最终查询,我们将构建马德里和瓜达拉哈拉之间的飞行路径,这两个城市距离足够近,我们可以看到实际的高程点。 我们将沿着该路径每 4KM 放置一个顶点,并找到离每个顶点最近的高程点。
要找到最近点,我们使用 <-> “索引有序距离”运算符。 当放在查询的 ORDER BY 子句中时 <-> 不仅计算左右操作数之间的距离,它还返回从可能的最小距离开始的距离。
因此,查询离马德里最近的高程点 (-3.6996,40.4009) 是:
SELECT
e.elevation,
ST_AsText(e.latlng, 3) AS coords,
geography(ST_MakePoint(-3.6996,40.4009)) <-> e.latlng AS dist
FROM elevation e
ORDER BY dist
LIMIT 1
返回结果如下(在 2ms 内,因为有索引辅助):
elevation | coords | dist
-----------+----------------------+--------------------
650.8142 | POINT(-3.707 40.409) | 1045.4185642910293
4、最近的飞行路线
索引辅助最近邻运算符 <->
是一个奇怪的运算符,因为它需要运算符一侧的文字值。 它不能接受两个变量参数,那么我们如何将具有多个点的飞行路径连接到高程网格?
答案是使用 LATERAL JOIN 来控制迭代并将飞行路径点(一次一个)提供给最近的邻居计算。
以块为单位,查询首先构建飞行路径。
WITH path AS (
SELECT ST_Segmentize(geography(
ST_MakeLine(
ST_MakePoint(-3.6996,40.4009), -- Madrid
ST_MakePoint(-3.1675,40.6582)) -- Guadalajara
), 4000) AS geog
)
然后它使用 ST_DumpPoints
将路径分解为每个顶点的一条记录:
points AS (
SELECT (ST_DumpPoints(geog::geometry)).geom FROM path
)
最后,它对高程点进行横向连接,为每个路径点找到最近的高程。
SELECT
d.elevation,
round(d.dist) AS distance,
ST_AsText(d.latlng, 3) AS elevation_pt,
ST_AsText(points.geom, 3) AS path_pt
FROM points
CROSS JOIN LATERAL (
SELECT
e.elevation, e.latlng,
points.geom::geography <-> e.latlng AS dist
FROM elevation e
ORDER BY dist
LIMIT 1
) d
最终结果大约需要 14ms 运行,并找到飞行路线上每个点的高度。
elevation | distance | elevation_pt | path_pt
-----------+----------+----------------------+----------------------
650.8142 | 1045 | POINT(-3.707 40.409) | POINT(-3.7 40.401)
630.8268 | 563 | POINT(-3.66 40.415) | POINT(-3.666 40.417)
701.6845 | 728 | POINT(-3.641 40.436) | POINT(-3.633 40.433)
632.26556 | 882 | POINT(-3.595 40.443) | POINT(-3.6 40.449)
611.845 | 733 | POINT(-3.575 40.464) | POINT(-3.567 40.465)
607.7102 | 701 | POINT(-3.533 40.488) | POINT(-3.534 40.482)
644.5825 | 1057 | POINT(-3.509 40.491) | POINT(-3.501 40.498)
637.69604 | 166 | POINT(-3.467 40.515) | POINT(-3.467 40.514)
666.1685 | 1335 | POINT(-3.447 40.536) | POINT(-3.434 40.53)
673.436 | 373 | POINT(-3.401 40.543) | POINT(-3.401 40.546)
717.8294 | 938 | POINT(-3.358 40.567) | POINT(-3.368 40.562)
696.11383 | 910 | POINT(-3.335 40.57) | POINT(-3.334 40.578)
709.38855 | 723 | POINT(-3.292 40.594) | POINT(-3.301 40.594)
716.1025 | 697 | POINT(-3.273 40.615) | POINT(-3.268 40.61)
655.39514 | 862 | POINT(-3.226 40.621) | POINT(-3.234 40.626)
659.81757 | 501 | POINT(-3.207 40.642) | POINT(-3.201 40.642)
654.524 | 943 | POINT(-3.164 40.666) | POINT(-3.168 40.658)
5、结束语
总结一下——我推荐的计算高程剖面和飞行路线的方法是:
- 创建空间索引以加快最近邻搜索
- 使用地理类型在线之间查询,因此最短路径在球体上而不是直线上
- 使用索引有序距离运算符查找两点之间的最短距离
- 使用最近邻运算符与高程点的横向连接。
原文链接:Elevation Profiles and Flightlines with PostGIS
BimAnt翻译整理,转载请标明出处