Files
geo_tools/app/analysis/spatial_ops.py
missum db51d41aef refactor: 重构项目结构,将geo_tools重命名为app并更新相关引用
- 将主包名从geo_tools改为app
- 更新所有模块中的引用路径
- 迁移并更新测试用例
- 添加项目规则文档
- 保持原有功能不变,仅进行结构调整
2026-04-12 19:49:56 +08:00

150 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
geo_tools.analysis.spatial_ops
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
空间叠加与邻域分析操作。
"""
from __future__ import annotations
from typing import Any
import geopandas as gpd
import pandas as pd
from app.utils.logger import get_logger
logger = get_logger(__name__)
def buffer_and_overlay(
source: gpd.GeoDataFrame,
distance: float,
target: gpd.GeoDataFrame,
how: str = "intersection",
projected_crs: str | None = None,
) -> gpd.GeoDataFrame:
"""对 source 执行缓冲区后与 target 执行叠置分析。
Parameters
----------
source:
源图层(生成缓冲区)。
distance:
缓冲距离(与 ``projected_crs`` 单位一致)。
target:
叠置目标图层。
how:
叠置类型:``"intersection"``、``"union"``、``"difference"``、``"symmetric_difference"``、``"identity"``。
projected_crs:
执行缓冲区前先投影到此 CRS建议使用平面坐标系以保证距离精度
``None`` 则使用 source 的当前 CRS地理 CRS 下 distance 单位为度)。
Returns
-------
gpd.GeoDataFrame
"""
original_crs = source.crs
if projected_crs:
source = source.to_crs(projected_crs)
target = target.to_crs(projected_crs)
buffered = source.copy()
buffered["geometry"] = buffered.geometry.buffer(distance)
logger.debug("缓冲区完成distance=%.2f执行叠置分析how=%s", distance, how)
result = gpd.overlay(buffered, target, how=how, keep_geom_type=False)
if projected_crs:
result = result.to_crs(original_crs) # type: ignore
logger.info("叠置分析完成:%d 条结果", len(result))
return result
def overlay(
df1: gpd.GeoDataFrame,
df2: gpd.GeoDataFrame,
how: str = "intersection",
keep_geom_type: bool = True,
) -> gpd.GeoDataFrame:
"""封装 geopandas overlay自动对齐 CRS。
Parameters
----------
how:
叠置类型:``"intersection"``、``"union"``、``"difference"``、
``"symmetric_difference"``、``"identity"``。
"""
if df1.crs != df2.crs:
df2 = df2.to_crs(df1.crs) # type: ignore
result = gpd.overlay(df1, df2, how=how, keep_geom_type=keep_geom_type)
logger.debug("overlay(%s)%d 条结果", how, len(result))
return result
def nearest_features(
source: gpd.GeoDataFrame,
target: gpd.GeoDataFrame,
k: int = 1,
max_distance: float | None = None,
) -> gpd.GeoDataFrame:
"""为 source 中每条要素找到 target 中最近的 k 个要素。
Parameters
----------
source:
查询图层。
target:
被查询图层。
k:
最近邻数量。
max_distance:
最大搜索距离(与 CRS 单位一致),``None`` 表示无限制。
Returns
-------
gpd.GeoDataFrame
连接了最近 target 属性的 source GDF可能包含重复行每行对应一个近邻
"""
if source.crs != target.crs:
target = target.to_crs(source.crs) # type: ignore
result = gpd.sjoin_nearest(
source,
target,
how="left",
max_distance=max_distance,
distance_col="nearest_distance",
lsuffix="left",
rsuffix="right",
)
logger.debug("最近邻分析完成k=%d%d 条结果", k, len(result))
return result
def select_by_location(
source: gpd.GeoDataFrame,
selector: gpd.GeoDataFrame,
predicate: str = "intersects",
) -> gpd.GeoDataFrame:
"""按位置关系从 source 中选取要素(等同于 ArcGIS「按位置选择」
Parameters
----------
predicate:
空间谓词:``"intersects"``、``"within"``、``"contains"``、``"touches"``。
Returns
-------
gpd.GeoDataFrame
满足条件的 source 子集。
"""
if source.crs != selector.crs:
selector = selector.to_crs(source.crs) # type: ignore
joined = gpd.sjoin(source, selector, how="inner", predicate=predicate)
result = source.loc[source.index.isin(joined.index)].copy()
logger.debug("按位置选择(%s%d / %d", predicate, len(result), len(source))
return result