【问题标题】:Algorithm for finding approximate area of the ground via elevation grid?通过高程网格查找地面近似区域的算法?
【发布时间】:2021-12-25 14:02:42
【问题描述】:

我有一个 10mX10m“像素”的 200X200 网格/数组(即 40,000 个值),其值表示该点土地的平均海拔。

整个网格有巨大的连接区域,其中高程值为 0m,因为它们代表实际的海洋。

问题:有没有快速的算法来获取土地的大致面积?我知道我可以乘以 200^2*10^2 来获得该区域的粗略近似值,但其中一些值差异很大。

我想我知道一种相当昂贵的方法,即对所有顶点位于高程的三角形求和。但是有更快/更简单的方法吗?

【问题讨论】:

  • @lmiuelvargasf 事实并非如此,这不是课堂作业。我是数学专业的,不是CS学生。我正在为无人机(无人机俱乐部)编写程序/脚本,并且我已经通过 Google Maps API 和我编写的另一个脚本获得了我提到的数据。这是 (Python) 类的一部分,该类返回有关无人机飞行的地面的信息。
  • 我不知道任务是排除海瓦(因为海!=“陆地”,为什么要另外提到海),还是计算所有瓦片的“3D区域”,或两者。如果您想要 3D 区域,请记住,除非您对地面的陡峭度有限制,否则任何近似都可能是任意错误的。
  • 那么您是否只是在寻找阵列内的土地块(级别 > 0)的数量,并将其乘以 10 平方米?这将在将其投影到地平面后近似该区域(即忽略高度,除非确定它是否是水)。

标签: python arrays algorithm


【解决方案1】:

NumPySciPy 是解决此类问题的工具。这是一个 200×200 的合成景观,点位于 10 米的网格上,高度可达海拔 40 米:

>>> import numpy as np
>>> xaxis = yaxis = np.arange(0, 2000, 10)
>>> x, y = np.meshgrid(xaxis, yaxis)
>>> z = np.maximum(40 * np.sin(np.hypot(x, y) / 350), 0)

我们可以在Matplotlib看看这个:

>>> import matplotlib.pyplot as plt
>>> import mpl_toolkits.mplot3d.axes3d as axes3d
>>> _, axes = plt.subplots(subplot_kw=dict(projection='3d'))
>>> axes.plot_surface(x, y, z, cmap=plt.get_cmap('winter'))
>>> plt.show()

现在,陆地上的点数(即高度大于 0)计算起来很简单,您可以将其乘以方格的大小(在您的问题中为 100 平方米)来估算土地面积:

>>> (z > 0).sum() * 100
1396500

但是从这个问题中,我了解到您想要一个更准确的估计,一个考虑到土地坡度的估计。一种方法是制作一个覆盖土地的三角形网格,然后将三角形的面积相加。

首先,将坐标数组转为点数组(apoint cloud):

>>> points = np.vstack((x, y, z)).reshape(3, -1).T
>>> points
array([[  0.000000e+00,   0.000000e+00,   0.000000e+00],
       [  1.000000e+01,   0.000000e+00,   1.142702e+00],
       [  2.000000e+01,   0.000000e+00,   2.284471e+00],
       ..., 
       [  1.970000e+03,   1.990000e+03,   3.957136e+01],
       [  1.980000e+03,   1.990000e+03,   3.944581e+01],
       [  1.990000e+03,   1.990000e+03,   3.930390e+01]])

其次,使用scipy.spatial.Delaunay进行二维三角剖分,得到表面网格:

>>> from scipy.spatial import Delaunay
>>> tri = Delaunay(points[:,:2])
>>> len(tri.simplices)
79202
>>> tri.simplices
array([[39698, 39899, 39898],
       [39899, 39698, 39699],
       [39899, 39700, 39900],
       ..., 
       [19236, 19235, 19035],
       [19437, 19236, 19237],
       [19436, 19236, 19437]], dtype=int32)

三角剖分中每个三角形的值是三角形中三个点的points数组中的索引。

第三,选择其中有一些土地的三角形:

>>> land = (points[tri.simplices][...,2] > 0).any(axis=1)
>>> triangles = tri.simplices[land]
>>> len(triangles)
27858

第四,计算这些三角形的面积:

>>> v0, v1, v2 = (points[triangles[:,i]] for i in range(3))
>>> areas = np.linalg.norm(np.cross(v1 - v0, v2 - v0), axis=1) / 2
>>> areas
array([ 50.325028,  50.324343,  50.32315 , ...,  50.308673,  50.313157, 50.313649])

最后,把它们加起来:

>>> areas.sum()
1397829.2847141961

这与最初的估计差别不大,这是可以预料的,因为斜坡很浅。

【讨论】:

  • 这太完美了!太感谢了!对于最后的计算(即计算面积),您似乎使用了鞋带公式(也在 Widder 的高级微积分中),这太棒了;我发现了,但我怀疑我会想出一种方法来像你一样高效地计算它。
【解决方案2】:

首先是一些对测试有用的额外内容:

# a function to create a random map as simulated input for testing:
def get_map(x_size, y_size, h_min, h_max):
    import random
    return [[random.randint(h_min, h_max) for x in range(x_size)] for y in range(y_size)]

# a function to nicely print the map for debug and visualization
def print_map(hmap):
    print(*hmap, sep="\n")

然后我们编写实际土地面积计算器:

# calculate approximate land area where the height is greater than zero
# map is a list of lists, tile_size is in m², min_level is the sea level
def calc_land_area(hmap, tile_size=100, min_level=0):
    land_tiles = sum(len([tile for tile in row if tile>min_level]) for row in hmap)
    return tile_size * land_tiles

现在是测试:

hmap = get_map(5, 5, 0, 3)
print_map(hmap)
print("land area:", calc_land_area(hmap), "m²")

这可能会导致例如在这个随机示例输出中:

[2, 0, 3, 0, 2]
[3, 0, 0, 0, 2]
[1, 0, 0, 1, 2]
[3, 3, 3, 2, 1]
[3, 1, 1, 3, 0]
land area: 1700 m²

您在 25 块地图上看到 8 块海块,因此 800 平方米是海洋,1700 平方米是陆地。

See this code running on ideone.com

【讨论】:

  • sum(tile > min_level for tile in row),而不是len([tile for tile in row if tile>min_level]),它会创建一个列表然后将其丢弃。
  • @GarethRees 对,这看起来像是一种改进。谢谢!
猜你喜欢
  • 1970-01-01
  • 2014-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-13
相关资源
最近更新 更多