第04章-空间关系与谓词操作

第04章:空间关系与谓词操作

4.1 空间关系概述

空间关系是 GIS 中的核心概念,用于描述两个几何对象之间的拓扑关系。NetTopologySuite 实现了完整的 OGC Simple Features Specification 定义的空间关系谓词。

4.1.1 空间关系分类

空间关系可以分为以下几类:

分类 关系类型 说明
相等关系 Equals 两个几何在空间上完全相同
包含关系 Contains, Within, Covers, CoveredBy 一个几何包含另一个几何
相交关系 Intersects, Crosses, Overlaps 两个几何有交集
接触关系 Touches 两个几何仅边界接触
分离关系 Disjoint 两个几何没有任何公共点

4.1.2 准备测试数据

using NetTopologySuite.Geometries;

var factory = new GeometryFactory();

// 创建测试多边形
var polygon1 = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(10, 0),
    new Coordinate(10, 10), new Coordinate(0, 10), new Coordinate(0, 0)
});

var polygon2 = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(5, 5), new Coordinate(15, 5),
    new Coordinate(15, 15), new Coordinate(5, 15), new Coordinate(5, 5)
});

var polygon3 = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(20, 0), new Coordinate(30, 0),
    new Coordinate(30, 10), new Coordinate(20, 10), new Coordinate(20, 0)
});

var polygon4 = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(2, 2), new Coordinate(8, 2),
    new Coordinate(8, 8), new Coordinate(2, 8), new Coordinate(2, 2)
});

// 创建测试点
var pointInside = factory.CreatePoint(new Coordinate(5, 5));
var pointOutside = factory.CreatePoint(new Coordinate(20, 20));
var pointOnBoundary = factory.CreatePoint(new Coordinate(0, 5));

// 创建测试线
var line = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(-5, 5), new Coordinate(15, 5)
});

4.2 基本空间谓词

4.2.1 Equals - 相等

判断两个几何是否在空间上相等(拓扑相等,不要求坐标顺序相同):

var poly1 = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(10, 0),
    new Coordinate(10, 10), new Coordinate(0, 10), new Coordinate(0, 0)
});

// 坐标顺序不同,但拓扑相同
var poly2 = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(10, 0), new Coordinate(10, 10),
    new Coordinate(0, 10), new Coordinate(0, 0), new Coordinate(10, 0)
});

// 完全不同
var poly3 = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(5, 0),
    new Coordinate(5, 5), new Coordinate(0, 5), new Coordinate(0, 0)
});

Console.WriteLine($"poly1.Equals(poly2): {poly1.Equals(poly2)}");  // true(拓扑相等)
Console.WriteLine($"poly1.Equals(poly3): {poly1.Equals(poly3)}");  // false

// 精确相等(坐标完全一致)
Console.WriteLine($"poly1.EqualsExact(poly2): {poly1.EqualsExact(poly2)}");  // false
Console.WriteLine($"poly1.EqualsNormalized(poly2): {poly1.EqualsNormalized(poly2)}");  // true

4.2.2 Disjoint - 分离

判断两个几何是否没有任何公共点:

// polygon1 和 polygon3 不相交
Console.WriteLine($"Disjoint: {polygon1.Disjoint(polygon3)}");  // true

// polygon1 和 polygon2 有重叠
Console.WriteLine($"Disjoint: {polygon1.Disjoint(polygon2)}");  // false

// Disjoint 是 Intersects 的否定
Console.WriteLine($"!Intersects == Disjoint: {!polygon1.Intersects(polygon3) == polygon1.Disjoint(polygon3)}");  // true

4.2.3 Intersects - 相交

判断两个几何是否有至少一个公共点:

// 多边形相交
Console.WriteLine($"polygon1 ∩ polygon2: {polygon1.Intersects(polygon2)}");  // true

// 多边形不相交
Console.WriteLine($"polygon1 ∩ polygon3: {polygon1.Intersects(polygon3)}");  // false

// 点在多边形内
Console.WriteLine($"point in polygon: {polygon1.Intersects(pointInside)}");  // true

// 点在边界上
Console.WriteLine($"point on boundary: {polygon1.Intersects(pointOnBoundary)}");  // true

// 线与多边形相交
Console.WriteLine($"line ∩ polygon: {polygon1.Intersects(line)}");  // true

4.2.4 Touches - 接触

判断两个几何是否仅边界接触(内部不相交):

// 创建仅边界接触的多边形
var touchPoly = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(10, 0), new Coordinate(20, 0),
    new Coordinate(20, 10), new Coordinate(10, 10), new Coordinate(10, 0)
});

Console.WriteLine($"polygon1 touches touchPoly: {polygon1.Touches(touchPoly)}");  // true

// 重叠的多边形不是 Touches
Console.WriteLine($"polygon1 touches polygon2: {polygon1.Touches(polygon2)}");  // false

// 点在边界上
Console.WriteLine($"point on boundary touches: {polygon1.Touches(pointOnBoundary)}");  // true

// 点在内部不是 Touches
Console.WriteLine($"point inside touches: {polygon1.Touches(pointInside)}");  // false

4.2.5 Contains - 包含

判断一个几何是否完全包含另一个几何(边界和内部都在内):

// polygon1 包含 polygon4
Console.WriteLine($"polygon1 contains polygon4: {polygon1.Contains(polygon4)}");  // true

// polygon1 包含内部点
Console.WriteLine($"polygon1 contains pointInside: {polygon1.Contains(pointInside)}");  // true

// polygon1 不包含边界点(Contains 要求在内部)
Console.WriteLine($"polygon1 contains pointOnBoundary: {polygon1.Contains(pointOnBoundary)}");  // false

// polygon1 不包含 polygon2(只是部分重叠)
Console.WriteLine($"polygon1 contains polygon2: {polygon1.Contains(polygon2)}");  // false

4.2.6 Within - 被包含

Within 是 Contains 的逆操作:

// polygon4 在 polygon1 内
Console.WriteLine($"polygon4 within polygon1: {polygon4.Within(polygon1)}");  // true

// pointInside 在 polygon1 内
Console.WriteLine($"pointInside within polygon1: {pointInside.Within(polygon1)}");  // true

// Within 和 Contains 的关系
Console.WriteLine($"A.Contains(B) == B.Within(A): {polygon1.Contains(polygon4) == polygon4.Within(polygon1)}");  // true

4.2.7 Covers - 覆盖

Covers 比 Contains 更宽松,允许被包含的几何在边界上:

// Contains 不包含边界点
Console.WriteLine($"polygon1 contains boundary point: {polygon1.Contains(pointOnBoundary)}");  // false

// Covers 包含边界点
Console.WriteLine($"polygon1 covers boundary point: {polygon1.Covers(pointOnBoundary)}");  // true

// Covers 也包含内部点
Console.WriteLine($"polygon1 covers inside point: {polygon1.Covers(pointInside)}");  // true

4.2.8 CoveredBy - 被覆盖

CoveredBy 是 Covers 的逆操作:

// 边界点被多边形覆盖
Console.WriteLine($"boundary point covered by polygon1: {pointOnBoundary.CoveredBy(polygon1)}");  // true

// CoveredBy 和 Covers 的关系
Console.WriteLine($"A.Covers(B) == B.CoveredBy(A): {polygon1.Covers(pointOnBoundary) == pointOnBoundary.CoveredBy(polygon1)}");  // true

4.2.9 Crosses - 跨越

Crosses 用于不同维度几何之间的相交:

// 线跨越多边形(穿过内部)
var crossingLine = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(-5, 5), new Coordinate(15, 5)
});
Console.WriteLine($"line crosses polygon: {crossingLine.Crosses(polygon1)}");  // true

// 线在边界上(不是 Crosses)
var boundaryLine = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(10, 0)
});
Console.WriteLine($"boundary line crosses polygon: {boundaryLine.Crosses(polygon1)}");  // false

// 两条线相交
var line1 = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(10, 10)
});
var line2 = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(0, 10), new Coordinate(10, 0)
});
Console.WriteLine($"line1 crosses line2: {line1.Crosses(line2)}");  // true

4.2.10 Overlaps - 重叠

Overlaps 用于相同维度几何之间的部分重叠:

// 两个多边形部分重叠
Console.WriteLine($"polygon1 overlaps polygon2: {polygon1.Overlaps(polygon2)}");  // true

// 完全包含不是 Overlaps
Console.WriteLine($"polygon1 overlaps polygon4: {polygon1.Overlaps(polygon4)}");  // false

// 不相交不是 Overlaps
Console.WriteLine($"polygon1 overlaps polygon3: {polygon1.Overlaps(polygon3)}");  // false

// 两条线部分重叠
var lineA = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(10, 0)
});
var lineB = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(5, 0), new Coordinate(15, 0)
});
Console.WriteLine($"lineA overlaps lineB: {lineA.Overlaps(lineB)}");  // true

4.3 DE-9IM 空间关系模型

4.3.1 DE-9IM 介绍

DE-9IM(Dimensionally Extended Nine-Intersection Model)是一种精确描述两个几何对象空间关系的模型。它使用一个 3x3 的矩阵来描述两个几何的内部(I)、边界(B)、外部(E)之间的相交情况。

         几何 B
        I  B  E
几何 A  I [x  x  x]
        B [x  x  x]
        E [x  x  x]

矩阵中的每个值可以是:

  • T - True,有交集
  • F - False,无交集
  • * - 任意值
  • 0 - 交集维度为 0(点)
  • 1 - 交集维度为 1(线)
  • 2 - 交集维度为 2(面)

4.3.2 获取 DE-9IM 矩阵

// 获取两个多边形的 DE-9IM 矩阵
var matrix = polygon1.Relate(polygon2);

Console.WriteLine($"DE-9IM 矩阵: {matrix}");
// 输出类似: 212101212

// 矩阵字符串解释
var matrixStr = matrix.ToString();
Console.WriteLine($"II: {matrixStr[0]}");  // 内部-内部
Console.WriteLine($"IB: {matrixStr[1]}");  // 内部-边界
Console.WriteLine($"IE: {matrixStr[2]}");  // 内部-外部
Console.WriteLine($"BI: {matrixStr[3]}");  // 边界-内部
Console.WriteLine($"BB: {matrixStr[4]}");  // 边界-边界
Console.WriteLine($"BE: {matrixStr[5]}");  // 边界-外部
Console.WriteLine($"EI: {matrixStr[6]}");  // 外部-内部
Console.WriteLine($"EB: {matrixStr[7]}");  // 外部-边界
Console.WriteLine($"EE: {matrixStr[8]}");  // 外部-外部

// 访问矩阵元素
Console.WriteLine($"内部是否相交: {matrix.IsIntersects()}");
Console.WriteLine($"是否分离: {matrix.IsDisjoint()}");
Console.WriteLine($"是否接触: {matrix.IsTouches(Dimension.Surface, Dimension.Surface)}");

4.3.3 常见空间关系的 DE-9IM 模式

空间关系 DE-9IM 模式 说明
Equals TF**FFF 内部和边界相等
Disjoint FF*FF**** 没有任何交集
Touches FT******* 或 F**T***** 等 边界接触
Within T*F**F*** A 在 B 内部
Contains T*****FF* B 在 A 内部
Overlaps (面) T*T***T** 部分重叠
Crosses (线/面) T*T****** 线穿过面

4.3.4 使用模式匹配

// 自定义空间关系判断
bool CustomRelate(Geometry a, Geometry b, string pattern)
{
    return a.Relate(b, pattern);
}

// 判断是否相交(使用模式)
Console.WriteLine($"Intersects: {polygon1.Relate(polygon2, "T********")}");

// 判断是否内部完全相交
Console.WriteLine($"Interior intersects: {polygon1.Relate(polygon2, "T********")}");

// 自定义:判断边界是否接触
Console.WriteLine($"Boundary touches: {polygon1.Relate(polygon2, "****T****")}");

// 自定义:判断是否有二维交集
Console.WriteLine($"2D intersection: {polygon1.Relate(polygon2, "2********")}");

4.3.5 IntersectionMatrix 详解

var matrix = polygon1.Relate(polygon2);

// 矩阵属性
Console.WriteLine($"是否相交: {matrix.IsIntersects()}");
Console.WriteLine($"是否分离: {matrix.IsDisjoint()}");
Console.WriteLine($"是否接触: {matrix.IsTouches(Dimension.Surface, Dimension.Surface)}");
Console.WriteLine($"是否相等: {matrix.IsEquals(Dimension.Surface, Dimension.Surface)}");
Console.WriteLine($"是否包含: {matrix.IsContains()}");
Console.WriteLine($"是否被包含: {matrix.IsWithin()}");
Console.WriteLine($"是否覆盖: {matrix.IsCovers()}");
Console.WriteLine($"是否被覆盖: {matrix.IsCoveredBy()}");

// 获取特定位置的维度
var interiorIntersection = matrix.Get(Location.Interior, Location.Interior);
Console.WriteLine($"内部交集维度: {interiorIntersection}");

// 检查是否匹配模式
Console.WriteLine($"匹配 T*F**FFF*: {matrix.Matches("T*F**FFF*")}");

4.4 距离计算

4.4.1 基本距离计算

var factory = new GeometryFactory();

var point1 = factory.CreatePoint(new Coordinate(0, 0));
var point2 = factory.CreatePoint(new Coordinate(3, 4));

// 点与点之间的距离
var distance = point1.Distance(point2);
Console.WriteLine($"两点距离: {distance}");  // 5

// 点与多边形之间的距离
var polygon = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(10, 10), new Coordinate(20, 10),
    new Coordinate(20, 20), new Coordinate(10, 20), new Coordinate(10, 10)
});
Console.WriteLine($"点到多边形距离: {point1.Distance(polygon)}");

// 点在多边形内部时,距离为0
var insidePoint = factory.CreatePoint(new Coordinate(15, 15));
Console.WriteLine($"内部点到多边形距离: {insidePoint.Distance(polygon)}");  // 0

// 线与线之间的距离
var line1 = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(10, 0)
});
var line2 = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(0, 5), new Coordinate(10, 5)
});
Console.WriteLine($"两线距离: {line1.Distance(line2)}");  // 5

4.4.2 判断是否在指定距离内

// 使用 IsWithinDistance 方法(比 Distance 更高效)
var isWithin5 = point1.IsWithinDistance(polygon, 20);
Console.WriteLine($"是否在20单位内: {isWithin5}");  // true

var isWithin1 = point1.IsWithinDistance(polygon, 5);
Console.WriteLine($"是否在5单位内: {isWithin1}");  // false

// 对于大量几何的距离查询,IsWithinDistance 更高效
// 因为它可以在确定结果后立即返回,不需要计算精确距离

4.4.3 最近点计算

using NetTopologySuite.Operation.Distance;

var point = factory.CreatePoint(new Coordinate(0, 0));
var polygon = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(10, 10), new Coordinate(20, 10),
    new Coordinate(20, 20), new Coordinate(10, 20), new Coordinate(10, 10)
});

// 获取最近点对
var distanceOp = new DistanceOp(point, polygon);
var nearestPoints = distanceOp.NearestPoints();

Console.WriteLine($"点1最近位置: ({nearestPoints[0].X}, {nearestPoints[0].Y})");
Console.WriteLine($"点2最近位置: ({nearestPoints[1].X}, {nearestPoints[1].Y})");
Console.WriteLine($"距离: {distanceOp.Distance()}");

// 获取最近点所在的几何位置
var nearestLocations = distanceOp.NearestLocations();
Console.WriteLine($"位置1几何索引: {nearestLocations[0].GeometryComponentIndex}");
Console.WriteLine($"位置2几何索引: {nearestLocations[1].GeometryComponentIndex}");

4.5 空间关系的实际应用

4.5.1 点在多边形内判断(选址分析)

public class LocationAnalysisService
{
    private readonly GeometryFactory _factory;
    
    public LocationAnalysisService()
    {
        _factory = new GeometryFactory(new PrecisionModel(), 4326);
    }
    
    /// <summary>
    /// 判断商店是否在配送范围内
    /// </summary>
    public bool IsInDeliveryArea(double storeLon, double storeLat, Polygon deliveryArea)
    {
        var storeLocation = _factory.CreatePoint(new Coordinate(storeLon, storeLat));
        return deliveryArea.Contains(storeLocation);
    }
    
    /// <summary>
    /// 筛选在指定区域内的所有位置
    /// </summary>
    public List<Point> FilterLocationsInArea(IEnumerable<Point> locations, Polygon area)
    {
        return locations.Where(p => area.Contains(p)).ToList();
    }
    
    /// <summary>
    /// 判断点是否在任意一个区域内
    /// </summary>
    public bool IsInAnyArea(Point point, IEnumerable<Polygon> areas)
    {
        return areas.Any(area => area.Contains(point));
    }
}

// 使用示例
var service = new LocationAnalysisService();
var factory = new GeometryFactory(new PrecisionModel(), 4326);

// 定义配送范围
var deliveryArea = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(116.3, 39.8), new Coordinate(116.5, 39.8),
    new Coordinate(116.5, 40.0), new Coordinate(116.3, 40.0),
    new Coordinate(116.3, 39.8)
});

// 判断商店是否在配送范围内
var inRange = service.IsInDeliveryArea(116.4, 39.9, deliveryArea);
Console.WriteLine($"商店在配送范围内: {inRange}");

4.5.2 缓冲区分析(服务范围)

public class ServiceAreaAnalysis
{
    private readonly GeometryFactory _factory;
    
    public ServiceAreaAnalysis()
    {
        _factory = new GeometryFactory();
    }
    
    /// <summary>
    /// 计算服务点的覆盖范围
    /// </summary>
    public Geometry CalculateServiceArea(Point servicePoint, double radius)
    {
        return servicePoint.Buffer(radius);
    }
    
    /// <summary>
    /// 计算多个服务点的联合覆盖范围
    /// </summary>
    public Geometry CalculateCombinedServiceArea(IEnumerable<Point> servicePoints, double radius)
    {
        var buffers = servicePoints.Select(p => p.Buffer(radius));
        var union = buffers.First();
        foreach (var buffer in buffers.Skip(1))
        {
            union = union.Union(buffer);
        }
        return union;
    }
    
    /// <summary>
    /// 分析服务覆盖重叠区域
    /// </summary>
    public Geometry FindOverlapArea(Geometry area1, Geometry area2)
    {
        return area1.Intersection(area2);
    }
    
    /// <summary>
    /// 查找未覆盖区域
    /// </summary>
    public Geometry FindUncoveredArea(Geometry totalArea, Geometry coveredArea)
    {
        return totalArea.Difference(coveredArea);
    }
}

// 使用示例
var analysis = new ServiceAreaAnalysis();
var factory = new GeometryFactory();

// 创建服务点
var hospital1 = factory.CreatePoint(new Coordinate(100, 100));
var hospital2 = factory.CreatePoint(new Coordinate(150, 100));

// 计算5km服务半径的覆盖范围
var serviceArea1 = analysis.CalculateServiceArea(hospital1, 50);
var serviceArea2 = analysis.CalculateServiceArea(hospital2, 50);

// 查找重叠服务区域
var overlap = analysis.FindOverlapArea(serviceArea1, serviceArea2);
Console.WriteLine($"重叠区域面积: {overlap.Area}");

// 计算联合服务范围
var combined = analysis.CalculateCombinedServiceArea(
    new[] { hospital1, hospital2 }, 50);
Console.WriteLine($"联合覆盖面积: {combined.Area}");

4.5.3 相交分析(用地分析)

public class LandUseAnalysis
{
    private readonly GeometryFactory _factory;
    
    public LandUseAnalysis()
    {
        _factory = new GeometryFactory();
    }
    
    /// <summary>
    /// 分析道路与地块的交叉情况
    /// </summary>
    public AnalysisResult AnalyzeRoadIntersection(Polygon parcel, LineString road)
    {
        var result = new AnalysisResult();
        
        result.DoesIntersect = parcel.Intersects(road);
        
        if (result.DoesIntersect)
        {
            result.Intersection = parcel.Intersection(road);
            result.IntersectionLength = result.Intersection.Length;
            
            // 判断相交类型
            if (road.Within(parcel))
            {
                result.IntersectionType = "道路完全在地块内";
            }
            else if (road.Crosses(parcel))
            {
                result.IntersectionType = "道路穿过地块";
            }
            else if (road.Touches(parcel))
            {
                result.IntersectionType = "道路与地块边界接触";
            }
        }
        
        return result;
    }
    
    /// <summary>
    /// 计算地块被道路分割后的部分
    /// </summary>
    public Geometry[] SplitParcelByRoad(Polygon parcel, LineString road, double roadWidth)
    {
        // 创建道路缓冲区
        var roadBuffer = road.Buffer(roadWidth / 2);
        
        // 从地块中减去道路
        var remaining = parcel.Difference(roadBuffer);
        
        // 返回分割后的各部分
        if (remaining is GeometryCollection collection)
        {
            return collection.Geometries;
        }
        
        return new[] { remaining };
    }
}

public class AnalysisResult
{
    public bool DoesIntersect { get; set; }
    public Geometry? Intersection { get; set; }
    public double IntersectionLength { get; set; }
    public string IntersectionType { get; set; } = "";
}

// 使用示例
var landAnalysis = new LandUseAnalysis();
var factory = new GeometryFactory();

// 创建地块
var parcel = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(0, 0), new Coordinate(100, 0),
    new Coordinate(100, 100), new Coordinate(0, 100), new Coordinate(0, 0)
});

// 创建道路
var road = factory.CreateLineString(new Coordinate[]
{
    new Coordinate(-20, 50), new Coordinate(120, 50)
});

// 分析交叉情况
var result = landAnalysis.AnalyzeRoadIntersection(parcel, road);
Console.WriteLine($"道路是否穿过地块: {result.DoesIntersect}");
Console.WriteLine($"交叉类型: {result.IntersectionType}");
Console.WriteLine($"交叉长度: {result.IntersectionLength}");

// 分割地块
var splitParts = landAnalysis.SplitParcelByRoad(parcel, road, 10);
Console.WriteLine($"分割后部分数量: {splitParts.Length}");

4.5.4 邻近分析

public class ProximityAnalysis
{
    private readonly GeometryFactory _factory;
    
    public ProximityAnalysis()
    {
        _factory = new GeometryFactory();
    }
    
    /// <summary>
    /// 查找指定距离内的所有要素
    /// </summary>
    public List<T> FindNearbyFeatures<T>(
        Geometry searchGeometry, 
        IEnumerable<T> candidates, 
        Func<T, Geometry> geometrySelector,
        double maxDistance)
    {
        return candidates
            .Where(c => searchGeometry.IsWithinDistance(geometrySelector(c), maxDistance))
            .ToList();
    }
    
    /// <summary>
    /// 查找最近的N个要素
    /// </summary>
    public List<(T Feature, double Distance)> FindNearestN<T>(
        Geometry searchGeometry,
        IEnumerable<T> candidates,
        Func<T, Geometry> geometrySelector,
        int n)
    {
        return candidates
            .Select(c => (Feature: c, Distance: searchGeometry.Distance(geometrySelector(c))))
            .OrderBy(x => x.Distance)
            .Take(n)
            .ToList();
    }
    
    /// <summary>
    /// 按距离分组
    /// </summary>
    public Dictionary<string, List<T>> GroupByDistance<T>(
        Geometry centerGeometry,
        IEnumerable<T> items,
        Func<T, Geometry> geometrySelector,
        double[] distanceBreaks)
    {
        var result = new Dictionary<string, List<T>>();
        
        foreach (var item in items)
        {
            var distance = centerGeometry.Distance(geometrySelector(item));
            var category = GetDistanceCategory(distance, distanceBreaks);
            
            if (!result.ContainsKey(category))
            {
                result[category] = new List<T>();
            }
            result[category].Add(item);
        }
        
        return result;
    }
    
    private string GetDistanceCategory(double distance, double[] breaks)
    {
        for (int i = 0; i < breaks.Length; i++)
        {
            if (distance <= breaks[i])
            {
                return i == 0 
                    ? $"0-{breaks[i]}" 
                    : $"{breaks[i-1]}-{breaks[i]}";
            }
        }
        return $">{breaks[^1]}";
    }
}

// 使用示例
var proximity = new ProximityAnalysis();
var factory = new GeometryFactory();

// 创建中心点
var center = factory.CreatePoint(new Coordinate(0, 0));

// 创建候选点
var candidates = Enumerable.Range(1, 100)
    .Select(i => factory.CreatePoint(new Coordinate(i * 10, i * 5)))
    .ToList();

// 查找100单位距离内的点
var nearby = proximity.FindNearbyFeatures(center, candidates, p => p, 100);
Console.WriteLine($"100单位内的点数量: {nearby.Count}");

// 查找最近的5个点
var nearest = proximity.FindNearestN(center, candidates, p => p, 5);
foreach (var (feature, distance) in nearest)
{
    Console.WriteLine($"点: {feature.AsText()}, 距离: {distance:F2}");
}

// 按距离分组
var groups = proximity.GroupByDistance(
    center, candidates, p => p, 
    new[] { 100.0, 200.0, 300.0, 500.0 });
foreach (var group in groups)
{
    Console.WriteLine($"距离 {group.Key}: {group.Value.Count} 个点");
}

4.6 性能优化

4.6.1 使用 PreparedGeometry

对于重复的空间关系查询,使用 PreparedGeometry 可以显著提高性能:

using NetTopologySuite.Geometries.Prepared;

var factory = new GeometryFactory();

// 创建一个复杂多边形
var complexPolygon = factory.CreatePolygon(/* 大量坐标点 */);

// 准备几何(预计算空间索引)
var preparedPolygon = PreparedGeometryFactory.Prepare(complexPolygon);

// 创建大量测试点
var testPoints = Enumerable.Range(0, 10000)
    .Select(i => factory.CreatePoint(new Coordinate(
        Random.Shared.NextDouble() * 100,
        Random.Shared.NextDouble() * 100)))
    .ToList();

// 使用 PreparedGeometry 进行查询(快速)
var startTime = DateTime.Now;
var containedPoints = testPoints
    .Where(p => preparedPolygon.Contains(p))
    .ToList();
var preparedTime = DateTime.Now - startTime;

Console.WriteLine($"PreparedGeometry 耗时: {preparedTime.TotalMilliseconds}ms");
Console.WriteLine($"包含的点数量: {containedPoints.Count}");

// PreparedGeometry 支持的操作
// preparedPolygon.Contains(geometry)
// preparedPolygon.ContainsProperly(geometry)
// preparedPolygon.Covers(geometry)
// preparedPolygon.CoveredBy(geometry)
// preparedPolygon.Intersects(geometry)
// preparedPolygon.Within(geometry)

4.6.2 使用空间索引

using NetTopologySuite.Index.Strtree;

// 创建 STRtree 空间索引
var index = new STRtree<Geometry>();

// 添加几何到索引
var polygons = Enumerable.Range(0, 1000)
    .Select(i => factory.CreatePolygon(new Coordinate[]
    {
        new Coordinate(i * 10, 0), new Coordinate(i * 10 + 10, 0),
        new Coordinate(i * 10 + 10, 10), new Coordinate(i * 10, 10),
        new Coordinate(i * 10, 0)
    }))
    .ToList();

foreach (var polygon in polygons)
{
    index.Insert(polygon.EnvelopeInternal, polygon);
}

// 必须在查询前构建索引
index.Build();

// 使用索引进行范围查询
var searchEnvelope = new Envelope(50, 150, 0, 10);
var candidates = index.Query(searchEnvelope);

Console.WriteLine($"索引查询结果数量: {candidates.Count}");

// 进一步筛选精确相交的几何
var searchArea = factory.CreatePolygon(new Coordinate[]
{
    new Coordinate(50, 0), new Coordinate(150, 0),
    new Coordinate(150, 10), new Coordinate(50, 10), new Coordinate(50, 0)
});

var exactMatches = candidates.Where(g => g.Intersects(searchArea)).ToList();
Console.WriteLine($"精确匹配数量: {exactMatches.Count}");

4.7 本章小结

本章详细介绍了 NetTopologySuite 的空间关系与谓词操作:

  1. 基本空间谓词:Equals、Disjoint、Intersects、Touches、Contains、Within、Covers、CoveredBy、Crosses、Overlaps
  2. DE-9IM 模型:深入理解九交模型及其模式匹配
  3. 距离计算:距离查询和最近点计算
  4. 实际应用:选址分析、缓冲区分析、相交分析、邻近分析
  5. 性能优化:PreparedGeometry 和空间索引的使用

4.8 下一步

下一章我们将学习几何运算与叠加分析,包括:

  • 布尔运算(Union、Intersection、Difference、SymmetricDifference)
  • 缓冲区操作
  • 凸包和质心计算
  • 几何简化和验证

相关资源

posted @ 2025-12-29 10:22  我才是银古  阅读(4)  评论(0)    收藏  举报