znlgis 博客

GIS开发与技术分享

第8章:空间度量与计算

空间度量是 GIS 分析的基础。Shapely 提供了丰富的度量函数,涵盖面积、长度、距离、 形状相似度等多种计算。这些函数基于平面几何(欧几里得空间),能够高效地计算几何对象的 各种度量属性。本章将全面介绍 Shapely 中的所有度量函数,包括数学含义、应用场景和使用方法。


area:面积计算

基本用法

area(geometry) 返回几何对象的面积。只有面状几何(Polygon / MultiPolygon)才有非零面积。

from shapely import area
from shapely.geometry import Polygon, Point, LineString

# 矩形面积
rect = Polygon([(0, 0), (4, 0), (4, 3), (0, 3)])
print(f"矩形面积: {area(rect)}")  # 12.0

# 三角形面积
tri = Polygon([(0, 0), (6, 0), (3, 4)])
print(f"三角形面积: {area(tri)}")  # 12.0

# 线和点的面积为 0
line = LineString([(0, 0), (1, 1)])
point = Point(0, 0)
print(f"线面积: {area(line)}")    # 0.0
print(f"点面积: {area(point)}")   # 0.0

带洞多边形的面积

from shapely import area
from shapely.geometry import Polygon

# 外环面积 100,内环面积 4
poly_with_hole = Polygon(
    [(0, 0), (10, 0), (10, 10), (0, 10)],
    [[(3, 3), (5, 3), (5, 5), (3, 5)]]
)
print(f"带洞多边形面积: {area(poly_with_hole)}")  # 96.0

MultiPolygon 面积

from shapely import area
from shapely.geometry import MultiPolygon, Polygon

mp = MultiPolygon([
    Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
    Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
])
print(f"MultiPolygon 面积: {area(mp)}")  # 1 + 4 = 5.0

批量面积计算

from shapely import area, box
import numpy as np

# 利用向量化进行批量计算
boxes = np.array([box(0, 0, i, i) for i in range(1, 6)])
areas = area(boxes)
print(f"批量面积: {areas}")  # [1. 4. 9. 16. 25.]

length:长度 / 周长

基本用法

length(geometry) 返回几何对象的长度。对于线状几何返回线长度,对于面状几何返回周长。

from shapely import length
from shapely.geometry import LineString, Polygon, Point

# 线长度
line = LineString([(0, 0), (3, 4)])
print(f"线长度: {length(line)}")  # 5.0

# 折线长度
polyline = LineString([(0, 0), (3, 0), (3, 4)])
print(f"折线长度: {length(polyline)}")  # 7.0

# 多边形周长
poly = Polygon([(0, 0), (4, 0), (4, 3), (0, 3)])
print(f"矩形周长: {length(poly)}")  # 14.0

# 点没有长度
point = Point(0, 0)
print(f"点长度: {length(point)}")  # 0.0

圆的周长

from shapely import length
from shapely.geometry import Point
import math

circle = Point(0, 0).buffer(5)
print(f"近似圆周长: {length(circle):.4f}")
print(f"理论圆周长: {2 * math.pi * 5:.4f}")

bounds:边界框

基本用法

bounds(geometry) 返回几何对象的边界框,格式为 (minx, miny, maxx, maxy)

from shapely import bounds
from shapely.geometry import Polygon, LineString, Point

poly = Polygon([(1, 2), (5, 1), (4, 6), (2, 5)])
print(f"多边形边界框: {bounds(poly)}")  # (1.0, 1.0, 5.0, 6.0)

line = LineString([(0, 0), (3, 4)])
print(f"线边界框: {bounds(line)}")  # (0.0, 0.0, 3.0, 4.0)

point = Point(3, 7)
print(f"点边界框: {bounds(point)}")  # (3.0, 7.0, 3.0, 7.0)

批量边界框

from shapely import bounds, box
import numpy as np

geoms = np.array([
    box(0, 0, 1, 1),
    box(2, 3, 5, 6),
    box(-1, -1, 4, 4),
])

bbox = bounds(geoms)
print(f"批量边界框:\n{bbox}")

total_bounds:批量总边界框

total_bounds(geometries) 返回一组几何对象的总边界框。

from shapely import total_bounds, box
import numpy as np

geoms = np.array([
    box(0, 0, 2, 2),
    box(3, 1, 5, 4),
    box(1, 3, 4, 6),
])

tb = total_bounds(geoms)
print(f"总边界框: {tb}")  # (0.0, 0.0, 5.0, 6.0)

distance:欧几里得距离

基本用法

distance(a, b) 返回两个几何对象之间的最短欧几里得距离。

from shapely import distance
from shapely.geometry import Point, Polygon, LineString

# 两点之间的距离
p1 = Point(0, 0)
p2 = Point(3, 4)
print(f"两点距离: {distance(p1, p2)}")  # 5.0

# 点到线的距离
line = LineString([(0, 1), (4, 1)])
print(f"点到线距离: {distance(Point(2, 3), line)}")  # 2.0

# 点到多边形的距离
poly = Polygon([(2, 2), (6, 2), (6, 6), (2, 6)])
print(f"内部点距离: {distance(Point(3, 3), poly)}")  # 0.0(在内部)
print(f"外部点距离: {distance(Point(0, 0), poly)}")

相交几何体的距离

from shapely import distance
from shapely.geometry import Polygon

a = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
b = Polygon([(1, 1), (3, 1), (3, 3), (1, 3)])

# 相交时距离为 0
print(f"相交几何体距离: {distance(a, b)}")  # 0.0

批量距离计算

from shapely import distance
from shapely.geometry import Point
import numpy as np

origin = Point(0, 0)
points = np.array([Point(i, i) for i in range(1, 6)])

# 向量化计算
distances = distance(origin, points)
print(f"批量距离: {distances}")

hausdorff_distance:Hausdorff 距离

概述

Hausdorff 距离是衡量两个几何对象形状相似度的指标。它定义为:对于 A 上的每个点, 找到距离 B 最近的点的距离,取所有这些距离中的最大值;对 B 做同样的操作;取两者中的较大值。

直觉上,如果 Hausdorff 距离为 d,那么任何一个几何体上的每个点,到另一个几何体的距离都不超过 d。

基本用法

from shapely import hausdorff_distance
from shapely.geometry import LineString

line1 = LineString([(0, 0), (1, 0), (2, 0)])
line2 = LineString([(0, 1), (1, 1), (2, 1)])

hd = hausdorff_distance(line1, line2)
print(f"Hausdorff 距离: {hd}")  # 1.0

形状相似度比较

from shapely import hausdorff_distance
from shapely.geometry import LineString

# 原始曲线
original = LineString([(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)])

# 近似曲线
approx1 = LineString([(0, 0), (2, 0), (4, 0)])
approx2 = LineString([(0, 0.1), (1, 1.1), (2, 0.1), (3, 1.1), (4, 0.1)])

hd1 = hausdorff_distance(original, approx1)
hd2 = hausdorff_distance(original, approx2)

print(f"粗略近似 Hausdorff: {hd1:.3f}")
print(f"精细近似 Hausdorff: {hd2:.3f}")

densify 参数

densify 参数用于在计算前对几何体进行加密,以获得更精确的结果:

from shapely import hausdorff_distance
from shapely.geometry import LineString

line1 = LineString([(0, 0), (10, 0)])
line2 = LineString([(0, 1), (5, 1), (10, 0)])

# 不加密
hd_raw = hausdorff_distance(line1, line2)
print(f"不加密: {hd_raw:.3f}")

# 加密(densify 表示每段长度与总长度的比例)
hd_dense = hausdorff_distance(line1, line2, densify=0.1)
print(f"加密后: {hd_dense:.3f}")

frechet_distance:Fréchet 距离

概述

Fréchet 距离(弗雷歇距离)是衡量两条曲线相似度的指标,直觉上类似于”遛狗距离”: 一个人沿着一条曲线走,他的狗沿着另一条曲线走,两者都只能前进不能后退, Fréchet 距离就是在所有可能的行走方案中,最短狗绳的最小长度。

基本用法

from shapely import frechet_distance
from shapely.geometry import LineString

line1 = LineString([(0, 0), (1, 0), (2, 0), (3, 0)])
line2 = LineString([(0, 1), (1, 1), (2, 1), (3, 1)])

fd = frechet_distance(line1, line2)
print(f"Fréchet 距离: {fd}")  # 1.0

Fréchet vs Hausdorff

from shapely import frechet_distance, hausdorff_distance
from shapely.geometry import LineString

# 两条方向相反的线
line1 = LineString([(0, 0), (1, 0), (2, 0)])
line2 = LineString([(2, 1), (1, 1), (0, 1)])

hd = hausdorff_distance(line1, line2)
fd = frechet_distance(line1, line2)

print(f"Hausdorff 距离: {hd:.3f}")  # 只看最近距离的最大值
print(f"Fréchet 距离: {fd:.3f}")    # 考虑行进方向,通常更大

densify 参数

from shapely import frechet_distance
from shapely.geometry import LineString

line1 = LineString([(0, 0), (10, 0)])
line2 = LineString([(0, 0), (5, 5), (10, 0)])

fd = frechet_distance(line1, line2)
fd_dense = frechet_distance(line1, line2, densify=0.1)
print(f"Fréchet 距离: {fd:.3f}")
print(f"加密后 Fréchet: {fd_dense:.3f}")

minimum_bounding_radius:最小外接圆半径

from shapely import minimum_bounding_radius
from shapely.geometry import Polygon, MultiPoint

poly = Polygon([(0, 0), (4, 0), (4, 3), (0, 3)])
radius = minimum_bounding_radius(poly)
print(f"最小外接圆半径: {radius:.3f}")  # 对角线一半

points = MultiPoint([(0, 0), (3, 0), (0, 4)])
radius_pts = minimum_bounding_radius(points)
print(f"三角形外接圆半径: {radius_pts:.3f}")

minimum_clearance:最小间隙

概述

minimum_clearance(geometry) 返回几何对象中,移动任何顶点使几何体变为无效(如自相交) 所需的最小距离。这是衡量几何体”稳定性”的指标。

基本用法

from shapely import minimum_clearance
from shapely.geometry import Polygon

# 接近退化的矩形
poly = Polygon([(0, 0), (10, 0), (10, 0.001), (0, 0.001)])
mc = minimum_clearance(poly)
print(f"最小间隙: {mc}")  # 接近 0.001

# 正方形
square = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
mc_sq = minimum_clearance(square)
print(f"正方形最小间隙: {mc_sq}")  # 1.0

minimum_clearance_line:最小间隙线

返回表示最小间隙的线段。

from shapely import minimum_clearance, minimum_clearance_line
from shapely.geometry import Polygon

poly = Polygon([(0, 0), (10, 0), (10, 5), (5, 5), (5, 4.9), (0, 5)])

mc = minimum_clearance(poly)
mc_line = minimum_clearance_line(poly)

print(f"最小间隙: {mc:.3f}")
print(f"最小间隙线: {mc_line}")
print(f"线长度: {mc_line.length:.3f}")

minimum_width:最小宽度

返回几何对象的最小宽度。

from shapely import minimum_width
from shapely.geometry import Polygon

# 窄矩形
narrow = Polygon([(0, 0), (10, 0), (10, 1), (0, 1)])
mw = minimum_width(narrow)
print(f"最小宽度: {mw.length}")  # 1.0

# 三角形
tri = Polygon([(0, 0), (6, 0), (3, 2)])
mw_tri = minimum_width(tri)
print(f"三角形最小宽度: {mw_tri.length:.3f}")

maximum_inscribed_circle:最大内切圆

返回几何对象内部的最大内切圆。

from shapely import maximum_inscribed_circle
from shapely.geometry import Polygon

# 正方形
square = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
mic = maximum_inscribed_circle(square, tolerance=0.01)
print(f"最大内切圆类型: {mic.geom_type}")  # LineString(圆心到边的线)
print(f"内切圆半径: {mic.length:.2f}")  # 5.0

# L形
L_shape = Polygon([
    (0, 0), (6, 0), (6, 2), (2, 2), (2, 6), (0, 6)
])
mic_L = maximum_inscribed_circle(L_shape, tolerance=0.01)
print(f"L形最大内切圆半径: {mic_L.length:.2f}")

shortest_line:最短连线

返回连接两个几何对象的最短线段。

from shapely import shortest_line
from shapely.geometry import Point, Polygon, LineString

p = Point(0, 0)
poly = Polygon([(3, 3), (6, 3), (6, 6), (3, 6)])

sl = shortest_line(p, poly)
print(f"最短连线: {sl}")
print(f"长度: {sl.length:.3f}")
from shapely import shortest_line
from shapely.geometry import LineString

line1 = LineString([(0, 0), (4, 0)])
line2 = LineString([(1, 3), (3, 3)])

sl = shortest_line(line1, line2)
print(f"最短连线: {sl}")
print(f"起点: ({sl.coords[0][0]:.1f}, {sl.coords[0][1]:.1f})")
print(f"终点: ({sl.coords[1][0]:.1f}, {sl.coords[1][1]:.1f})")

批量最短连线

from shapely import shortest_line
from shapely.geometry import Point, LineString
import numpy as np

road = LineString([(0, 0), (10, 0)])
houses = np.array([Point(i, j) for i, j in [(2, 3), (5, 1), (8, 4)]])

lines = shortest_line(houses, road)
for i, sl in enumerate(lines):
    print(f"房屋{i} 到道路最短连线长度: {sl.length:.2f}")

批量计算与向量化操作

NumPy 数组输入

Shapely 2.x 的度量函数支持 NumPy 数组输入,实现高效的批量计算:

from shapely import area, length, distance, bounds
from shapely.geometry import Point
import numpy as np

# 创建大量随机多边形
np.random.seed(42)
n = 1000
points = np.array([Point(x, y).buffer(r)
                    for x, y, r in zip(
                        np.random.uniform(0, 100, n),
                        np.random.uniform(0, 100, n),
                        np.random.uniform(0.5, 3, n),
                    )])

# 批量计算
areas = area(points)
perimeters = length(points)

print(f"面积统计: min={areas.min():.2f}, max={areas.max():.2f}, mean={areas.mean():.2f}")
print(f"周长统计: min={perimeters.min():.2f}, max={perimeters.max():.2f}")

距离矩阵

from shapely import distance
from shapely.geometry import Point
import numpy as np

# 计算点之间的距离矩阵
pts = np.array([Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)])
n = len(pts)

dist_matrix = np.zeros((n, n))
for i in range(n):
    # 向量化计算一行
    dist_matrix[i] = distance(pts[i], pts)

print("距离矩阵:")
print(np.round(dist_matrix, 3))

度量函数应用场景

场景 1:最近设施分析

from shapely import distance
from shapely.geometry import Point
import numpy as np

# 设施位置
facilities = np.array([
    Point(10, 20), Point(30, 10), Point(50, 40),
    Point(20, 50), Point(40, 25),
])

# 查询点
query = Point(25, 30)

# 找到最近的设施
distances = distance(query, facilities)
nearest_idx = np.argmin(distances)
print(f"最近的设施索引: {nearest_idx}")
print(f"最近距离: {distances[nearest_idx]:.2f}")

场景 2:形状紧凑度分析

from shapely import area, length
from shapely.geometry import Polygon, Point
import math

shapes = {
    "正方形": Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
    "长矩形": Polygon([(0, 0), (10, 0), (10, 1), (0, 1)]),
    "圆形": Point(0, 0).buffer(1),
    "L形": Polygon([(0, 0), (2, 0), (2, 1), (1, 1), (1, 2), (0, 2)]),
}

print("形状紧凑度分析(4π × 面积 / 周长²):")
for name, shape in shapes.items():
    a = area(shape)
    p = length(shape)
    compactness = 4 * math.pi * a / (p * p)
    print(f"  {name}: 面积={a:.2f}, 周长={p:.2f}, 紧凑度={compactness:.4f}")

场景 3:多边形面积与周长统计

from shapely import area, length
from shapely.geometry import Polygon
import numpy as np

# 随机多边形
np.random.seed(0)
polygons = []
for _ in range(20):
    cx, cy = np.random.uniform(0, 100, 2)
    r = np.random.uniform(1, 10)
    angles = np.sort(np.random.uniform(0, 2 * np.pi, 6))
    coords = [(cx + r * np.cos(a), cy + r * np.sin(a)) for a in angles]
    coords.append(coords[0])
    try:
        p = Polygon(coords)
        if p.is_valid and not p.is_empty:
            polygons.append(p)
    except Exception:
        pass

geoms = np.array(polygons)
areas = area(geoms)
perimeters = length(geoms)

print(f"有效多边形数: {len(geoms)}")
print(f"平均面积: {areas.mean():.2f}")
print(f"平均周长: {perimeters.mean():.2f}")
print(f"面积标准差: {areas.std():.2f}")

注意事项

平面计算 vs 球面计算

Shapely 只进行平面(笛卡尔坐标系)计算,不支持球面(地理坐标系)计算。

from shapely import distance
from shapely.geometry import Point

# 这是坐标差值,不是真实的地球表面距离!
beijing = Point(116.4, 39.9)
shanghai = Point(121.5, 31.2)

# 这个结果的单位是"度",不是"米"或"千米"
d = distance(beijing, shanghai)
print(f"坐标距离: {d:.2f}(度,不是实际距离)")

# 如需计算真实地球距离,应该:
# 1. 将坐标投影到合适的平面坐标系(如 UTM)
# 2. 使用 pyproj 或 geopy 进行球面距离计算

浮点精度

from shapely import area
from shapely.geometry import Polygon

# 极小多边形可能有精度问题
tiny = Polygon([
    (0, 0),
    (1e-15, 0),
    (1e-15, 1e-15),
    (0, 1e-15),
])
print(f"极小面积: {area(tiny)}")

# 极大坐标也可能有精度问题
huge = Polygon([
    (1e15, 1e15),
    (1e15 + 1, 1e15),
    (1e15 + 1, 1e15 + 1),
    (1e15, 1e15 + 1),
])
print(f"极大坐标面积: {area(huge)}")

空几何体的度量

from shapely import area, length, distance, bounds
from shapely import wkt

empty = wkt.loads("GEOMETRYCOLLECTION EMPTY")

print(f"空几何面积: {area(empty)}")     # 0.0
print(f"空几何长度: {length(empty)}")   # 0.0
print(f"空几何边界框: {bounds(empty)}")

度量函数速查表

函数 说明 适用类型
area(geom) 面积 Polygon, MultiPolygon
length(geom) 长度/周长 Line*, Polygon
bounds(geom) 边界框 所有类型
total_bounds(geoms) 总边界框 几何数组
distance(a, b) 最短距离 所有类型
hausdorff_distance(a, b) Hausdorff 距离 所有类型
frechet_distance(a, b) Fréchet 距离 LineString
minimum_bounding_radius(geom) 最小外接圆半径 所有类型
minimum_clearance(geom) 最小间隙 所有类型
minimum_clearance_line(geom) 最小间隙线 所有类型
minimum_width(geom) 最小宽度 Polygon
maximum_inscribed_circle(geom) 最大内切圆 Polygon
shortest_line(a, b) 最短连线 所有类型

本章小结

本章全面介绍了 Shapely 的空间度量函数:

  • 基础度量arealengthbounds
  • 距离度量distancehausdorff_distancefrechet_distance
  • 形状度量minimum_clearanceminimum_widthmaximum_inscribed_circle
  • 连接度量shortest_line

关键要点:

  1. Shapely 只做平面计算,地理坐标需先投影
  2. 度量函数支持 NumPy 数组输入,可高效批量计算
  3. 注意浮点精度问题和空几何体的特殊行为

下一章将介绍仿射变换,学习如何对几何对象进行平移、旋转、缩放等操作。