我有一个200x200的网格/数组(即40,000个值),每个"像素"的大小为10m x 10m,其值表示该点的平均海拔高度。
在整个网格中,有广阔的连通区域,其高度值为0m,因为它们代表实际上的海洋。
问题:是否存在一种快速算法来获取陆地的近似面积?我知道可以将200^2 x 10^2相乘以获得大致的面积近似值,但其中一些值变化很大。
我认为我知道一种相当昂贵的方法,即求出所有顶点高程相同的三角形的总面积。但是否有更快/更简单的方法?
我有一个200x200的网格/数组(即40,000个值),每个"像素"的大小为10m x 10m,其值表示该点的平均海拔高度。
在整个网格中,有广阔的连通区域,其高度值为0m,因为它们代表实际上的海洋。
问题:是否存在一种快速算法来获取陆地的近似面积?我知道可以将200^2 x 10^2相乘以获得大致的面积近似值,但其中一些值变化很大。
我认为我知道一种相当昂贵的方法,即求出所有顶点高程相同的三角形的总面积。但是否有更快/更简单的方法?
NumPy和SciPy是解决这种问题的工具。这里是一个合成的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)
>>> 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
>>> 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
首先是一些有用的测试附加内容:
# 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²
len([tile for tile in row if tile>min_level])
这种方式,它会创建一个列表然后立即丢弃。可以改为使用sum(tile > min_level for tile in row)
的方式。 - Gareth Rees