惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Register - Security
The Register - Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security @ Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
L
Lohrmann on Cybersecurity
T
Troy Hunt's Blog
T
Threat Research - Cisco Blogs
A
About on SuperTechFans
T
Threatpost
AWS News Blog
AWS News Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
W
WeLiveSecurity
博客园 - 叶小钗
K
Kaspersky official blog
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
Hacker News - Newest:
Hacker News - Newest: "LLM"
Engineering at Meta
Engineering at Meta
有赞技术团队
有赞技术团队
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
小众软件
小众软件
D
Docker
爱范儿
爱范儿
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第五章:几何运算操作符
我才是银古 · 2026-06-22 · via 博客园 - 我才是银古

第五章:几何运算操作符

5.1 概述

几何运算操作符用于对几何对象进行变换和计算,生成新的几何对象或计算几何属性。这些操作符是空间分析的核心工具。

5.1.1 操作符分类

类别 操作符 说明
创建型 BufferOperator 创建缓冲区
创建型 ConvexHullOperator 计算凸包
创建型 BoundaryOperator 获取边界
创建型 OffsetOperator 创建偏移几何
计算型 AreaOperator 计算面积
计算型 LengthOperator 计算长度
计算型 CentroidOperator 计算质心
变换型 SimplifyOperator 简化几何
变换型 GeneralizeOperator 概化几何
变换型 DensifyOperator 密化几何
变换型 ClipOperator 裁剪几何

5.2 Buffer(缓冲区)

5.2.1 概念

缓冲区操作在几何对象周围创建指定距离的区域。这是 GIS 分析中最常用的操作之一。

应用场景

  • 创建服务区域(如配送范围)
  • 环境影响分析(如噪音影响范围)
  • 安全距离分析(如河流泛滥区)

5.2.2 API

public class BufferOperator : IGeometryOperator<Polygon>
{
    public static BufferOperator Instance { get; }
    
    public Polygon Execute(Geometry geometry, double distance, 
                          SpatialReference? spatialRef = null);
}

5.2.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 点缓冲区
var point = new Point(100, 100);
var buffer = GeometryEngine.Buffer(point, 10);

Console.WriteLine($"缓冲区类型:{buffer.Type}");  // Polygon
Console.WriteLine($"环数:{buffer.RingCount}");   // 1

// 包络矩形缓冲区
var envelope = new Envelope(0, 0, 50, 30);
var envBuffer = GeometryEngine.Buffer(envelope, 5);

// 缓冲区扩展了各方向 5 个单位
var bufferEnv = envBuffer.GetEnvelope();
Console.WriteLine($"缓冲区范围:({bufferEnv.XMin}, {bufferEnv.YMin}) - ({bufferEnv.XMax}, {bufferEnv.YMax})");
// 输出:(-5, -5) - (55, 35)

5.2.4 实现原理

当前实现为简化版本,创建矩形缓冲区:

private Polygon BufferPoint(Point point, double distance)
{
    // 在点周围创建正方形缓冲区
    var polygon = new Polygon();
    var ring = new List<Point>
    {
        new Point(point.X - distance, point.Y - distance),
        new Point(point.X + distance, point.Y - distance),
        new Point(point.X + distance, point.Y + distance),
        new Point(point.X - distance, point.Y + distance),
        new Point(point.X - distance, point.Y - distance)  // 闭合
    };
    polygon.AddRing(ring);
    return polygon;
}

private Polygon BufferEnvelope(Envelope envelope, double distance)
{
    // 在各个方向上扩展包络
    var polygon = new Polygon();
    var ring = new List<Point>
    {
        new Point(envelope.XMin - distance, envelope.YMin - distance),
        new Point(envelope.XMax + distance, envelope.YMin - distance),
        new Point(envelope.XMax + distance, envelope.YMax + distance),
        new Point(envelope.XMin - distance, envelope.YMax + distance),
        new Point(envelope.XMin - distance, envelope.YMin - distance)
    };
    polygon.AddRing(ring);
    return polygon;
}

5.2.5 应用示例

// 场景:创建商店的配送范围
var storeLocation = new Point(116.4074, 39.9042);
var deliveryRadius = 0.02;  // 约 2 公里(经纬度)

var deliveryZone = GeometryEngine.Buffer(storeLocation, deliveryRadius);

// 检查客户是否在配送范围内
var customerLocation = new Point(116.41, 39.91);
bool canDeliver = GeometryEngine.Contains(deliveryZone, customerLocation);
Console.WriteLine($"可以配送:{canDeliver}");

5.3 ConvexHull(凸包)

5.3.1 概念

凸包是包含给定点集的最小凸多边形。可以想象为用橡皮筋套住一组钉子形成的形状。

应用场景

  • 计算点集的最小外接多边形
  • 碰撞检测的预处理
  • 地理范围估计

5.3.2 API

public class ConvexHullOperator : IGeometryOperator<Geometry>
{
    public static ConvexHullOperator Instance { get; }
    
    public Geometry Execute(Geometry geometry, 
                           SpatialReference? spatialRef = null);
}

5.3.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 多点的凸包
var multiPoint = new MultiPoint();
multiPoint.Add(new Point(0, 0));
multiPoint.Add(new Point(10, 0));
multiPoint.Add(new Point(5, 5));
multiPoint.Add(new Point(10, 10));
multiPoint.Add(new Point(0, 10));
multiPoint.Add(new Point(5, 3));  // 内部点

var hull = GeometryEngine.ConvexHull(multiPoint);

if (hull is Polygon polygon)
{
    Console.WriteLine($"凸包顶点数:{polygon.GetRing(0).Count}");
    // 内部点 (5, 3) 不在凸包边界上
}

// 折线的凸包
var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
    new Point(0, 0),
    new Point(5, 10),
    new Point(10, 0)
});

var lineHull = GeometryEngine.ConvexHull(polyline);  // 返回三角形

5.3.4 实现原理:Graham 扫描算法

private List<Point> GrahamScan(List<Point> points)
{
    if (points.Count < 3) return points;

    // 1. 找到 Y 坐标最小的点(最低点)
    var lowestPoint = points.OrderBy(p => p.Y).ThenBy(p => p.X).First();

    // 2. 按极角排序其他点
    var sortedPoints = points
        .Where(p => p != lowestPoint)
        .OrderBy(p => Math.Atan2(p.Y - lowestPoint.Y, p.X - lowestPoint.X))
        .ThenBy(p => p.Distance(lowestPoint))
        .ToList();

    // 3. 构建凸包
    var hull = new List<Point> { lowestPoint };

    foreach (var point in sortedPoints)
    {
        // 移除产生右转的点
        while (hull.Count > 1 && !IsLeftTurn(hull[hull.Count - 2], hull[hull.Count - 1], point))
        {
            hull.RemoveAt(hull.Count - 1);
        }
        hull.Add(point);
    }

    return hull;
}

private bool IsLeftTurn(Point p1, Point p2, Point p3)
{
    // 使用叉积判断是否左转
    var cross = (p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X);
    return cross > 0;
}

算法复杂度:O(n log n),其中 n 是点的数量。

5.3.5 返回类型

根据输入几何的不同,凸包返回不同类型:

输入点数 返回类型
0 空 Polygon
1 Point
2 Line
≥3 Polygon

5.4 Area 和 Length(面积和长度)

5.4.1 概念

  • Area(面积):计算多边形或包络矩形的二维面积
  • Length(长度):计算折线的总长度或多边形的周长

5.4.2 API

public class AreaOperator : IGeometryOperator<double>
{
    public static AreaOperator Instance { get; }
    
    public double Execute(Geometry geometry, 
                         SpatialReference? spatialRef = null);
}

public class LengthOperator : IGeometryOperator<double>
{
    public static LengthOperator Instance { get; }
    
    public double Execute(Geometry geometry, 
                         SpatialReference? spatialRef = null);
}

5.4.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 计算多边形面积
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
    new Point(0, 0),
    new Point(10, 0),
    new Point(10, 10),
    new Point(0, 10),
    new Point(0, 0)
});

double area = GeometryEngine.Area(polygon);
Console.WriteLine($"多边形面积:{area}");  // 100

// 计算包络矩形面积
var envelope = new Envelope(0, 0, 100, 50);
double envArea = GeometryEngine.Area(envelope);
Console.WriteLine($"包络面积:{envArea}");  // 5000

// 计算折线长度
var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
    new Point(0, 0),
    new Point(3, 4),  // 距离原点 5
    new Point(3, 14)  // 向上 10
});

double length = GeometryEngine.Length(polyline);
Console.WriteLine($"折线长度:{length}");  // 15

// 计算多边形周长
double perimeter = GeometryEngine.Length(polygon);
Console.WriteLine($"多边形周长:{perimeter}");  // 40

5.4.4 面积计算:鞋带公式

public double Execute(Geometry geometry, SpatialReference? spatialRef = null)
{
    if (geometry is Polygon polygon)
    {
        double area = 0;
        foreach (var ring in polygon.GetRings())
        {
            var count = ring.Count;
            if (count < 3) continue;
            
            double ringArea = 0;
            for (var i = 0; i < count - 1; i++)
            {
                // 鞋带公式
                ringArea += ring[i].X * ring[i + 1].Y - ring[i + 1].X * ring[i].Y;
            }
            // 闭合环
            ringArea += ring[count - 1].X * ring[0].Y - ring[0].X * ring[count - 1].Y;
            area += Math.Abs(ringArea) * 0.5;
        }
        return area;
    }
    
    if (geometry is Envelope envelope)
    {
        return envelope.Width * envelope.Height;
    }
    
    return 0;  // 点和线没有面积
}

5.4.5 长度计算

public double Execute(Geometry geometry, SpatialReference? spatialRef = null)
{
    if (geometry is Polyline polyline)
    {
        double length = 0;
        foreach (var path in polyline.GetPaths())
        {
            for (var i = 0; i < path.Count - 1; i++)
            {
                length += path[i].Distance(path[i + 1]);
            }
        }
        return length;
    }
    
    if (geometry is Polygon polygon)
    {
        double length = 0;
        foreach (var ring in polygon.GetRings())
        {
            for (var i = 0; i < ring.Count - 1; i++)
            {
                length += ring[i].Distance(ring[i + 1]);
            }
            // 闭合段
            if (ring.Count > 1)
            {
                length += ring[ring.Count - 1].Distance(ring[0]);
            }
        }
        return length;
    }
    
    return 0;  // 点没有长度
}

5.5 Simplify(简化)

5.5.1 概念

使用 Douglas-Peucker 算法减少几何对象的顶点数量,同时保持其基本形状。常用于减少数据量,提高渲染性能。

应用场景

  • 地图缩放时简化显示
  • 减少存储和传输的数据量
  • 提高地图渲染速度

5.5.2 API

public class SimplifyOperator : IGeometryOperator<Geometry>
{
    public static SimplifyOperator Instance { get; }
    
    public Geometry Execute(Geometry geometry, double tolerance, 
                           SpatialReference? spatialRef = null);
}

5.5.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 创建包含多个点的折线
var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
    new Point(0, 0),
    new Point(1, 0.1),   // 几乎在直线上
    new Point(2, 0.05),  // 几乎在直线上
    new Point(3, 0.08),  // 几乎在直线上
    new Point(4, 0),
    new Point(5, 5),     // 明显转折
    new Point(6, 4.9),   // 几乎在直线上
    new Point(7, 5.1),   // 几乎在直线上
    new Point(8, 5),
    new Point(9, 0),
    new Point(10, 0)
});

Console.WriteLine($"原始点数:{polyline.GetPath(0).Count}");  // 11

// 使用容差 0.2 简化
var simplified = GeometryEngine.Simplify(polyline, 0.2);

if (simplified is Polyline simplifiedLine)
{
    Console.WriteLine($"简化后点数:{simplifiedLine.GetPath(0).Count}");  // 减少
}

// 使用更大的容差会更激进地简化
var moreSimplified = GeometryEngine.Simplify(polyline, 1.0);

5.5.4 实现原理:Douglas-Peucker 算法

private List<Point> DouglasPeucker(List<Point> points, double tolerance)
{
    if (points.Count < 3) return points;

    // 1. 找到距离首尾连线最远的点
    double maxDistance = 0;
    int maxIndex = 0;
    int end = points.Count - 1;

    for (int i = 1; i < end; i++)
    {
        double distance = PerpendicularDistance(points[i], points[0], points[end]);
        if (distance > maxDistance)
        {
            maxDistance = distance;
            maxIndex = i;
        }
    }

    // 2. 如果最大距离超过容差,递归处理两段
    if (maxDistance > tolerance)
    {
        var left = DouglasPeucker(points.GetRange(0, maxIndex + 1), tolerance);
        var right = DouglasPeucker(points.GetRange(maxIndex, end - maxIndex + 1), tolerance);

        // 合并结果(去掉重复的中间点)
        var result = new List<Point>(left);
        result.AddRange(right.Skip(1));
        return result;
    }

    // 3. 否则只保留端点
    return new List<Point> { points[0], points[end] };
}

// 计算点到线段的垂直距离
private double PerpendicularDistance(Point point, Point lineStart, Point lineEnd)
{
    double dx = lineEnd.X - lineStart.X;
    double dy = lineEnd.Y - lineStart.Y;
    double mag = Math.Sqrt(dx * dx + dy * dy);
    
    if (mag < GeometryConstants.Epsilon)
        return point.Distance(lineStart);

    // 参数化位置
    double u = ((point.X - lineStart.X) * dx + (point.Y - lineStart.Y) * dy) / (mag * mag);

    if (u < 0) return point.Distance(lineStart);
    if (u > 1) return point.Distance(lineEnd);

    // 最近点
    double ix = lineStart.X + u * dx;
    double iy = lineStart.Y + u * dy;
    return point.Distance(new Point(ix, iy));
}

算法复杂度:平均 O(n log n),最坏 O(n²)。

5.6 Centroid(质心)

5.6.1 概念

质心是几何对象的几何中心点(质量中心)。

5.6.2 API

public class CentroidOperator : IGeometryOperator<Point>
{
    public static CentroidOperator Instance { get; }
    
    public Point Execute(Geometry geometry, 
                        SpatialReference? spatialRef = null);
}

5.6.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 多边形的质心
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
    new Point(0, 0),
    new Point(10, 0),
    new Point(10, 10),
    new Point(0, 10),
    new Point(0, 0)
});

var centroid = GeometryEngine.Centroid(polygon);
Console.WriteLine($"质心:({centroid.X}, {centroid.Y})");  // (5, 5)

// 多点的质心(平均值)
var multiPoint = new MultiPoint();
multiPoint.Add(new Point(0, 0));
multiPoint.Add(new Point(10, 0));
multiPoint.Add(new Point(10, 10));
multiPoint.Add(new Point(0, 10));

var mpCentroid = GeometryEngine.Centroid(multiPoint);
Console.WriteLine($"多点质心:({mpCentroid.X}, {mpCentroid.Y})");  // (5, 5)

// 三角形的质心
var triangle = new Polygon();
triangle.AddRing(new List<Point>
{
    new Point(0, 0),
    new Point(6, 0),
    new Point(3, 6),
    new Point(0, 0)
});

var triangleCentroid = GeometryEngine.Centroid(triangle);
Console.WriteLine($"三角形质心:({triangleCentroid.X}, {triangleCentroid.Y})");  // (3, 2)

5.6.4 实现原理

public Point Execute(Geometry geometry, SpatialReference? spatialRef = null)
{
    if (geometry.IsEmpty)
        return new Point();

    switch (geometry.Type)
    {
        case GeometryType.Point:
            return (Point)geometry;

        case GeometryType.MultiPoint:
            return CalculateMultiPointCentroid((MultiPoint)geometry);

        case GeometryType.Polygon:
            return CalculatePolygonCentroid((Polygon)geometry);

        case GeometryType.Envelope:
            return ((Envelope)geometry).Center;

        default:
            // 对于其他类型,返回包络的中心
            return geometry.GetEnvelope().Center;
    }
}

private Point CalculateMultiPointCentroid(MultiPoint multiPoint)
{
    double sumX = 0, sumY = 0;
    int count = 0;

    foreach (var point in multiPoint.GetPoints())
    {
        sumX += point.X;
        sumY += point.Y;
        count++;
    }

    return new Point(sumX / count, sumY / count);
}

private Point CalculatePolygonCentroid(Polygon polygon)
{
    // 使用多边形质心公式
    double sumX = 0, sumY = 0, sumArea = 0;

    foreach (var ring in polygon.GetRings())
    {
        for (int i = 0; i < ring.Count - 1; i++)
        {
            double cross = ring[i].X * ring[i + 1].Y - ring[i + 1].X * ring[i].Y;
            sumX += (ring[i].X + ring[i + 1].X) * cross;
            sumY += (ring[i].Y + ring[i + 1].Y) * cross;
            sumArea += cross;
        }
    }

    double area = sumArea / 2;
    return new Point(sumX / (6 * area), sumY / (6 * area));
}

5.7 Boundary(边界)

5.7.1 概念

边界操作返回几何对象的边界几何。根据 OGC 规范:

  • 点的边界是空
  • 线的边界是端点
  • 多边形的边界是其环

5.7.2 API

public class BoundaryOperator : IGeometryOperator<Geometry>
{
    public static BoundaryOperator Instance { get; }
    
    public Geometry Execute(Geometry geometry, 
                           SpatialReference? spatialRef = null);
}

5.7.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 点的边界是空
var point = new Point(10, 20);
var pointBoundary = GeometryEngine.Boundary(point);
Console.WriteLine($"点边界为空:{pointBoundary.IsEmpty}");  // true

// 折线的边界是端点
var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
    new Point(0, 0),
    new Point(10, 10),
    new Point(20, 0)
});

var lineBoundary = GeometryEngine.Boundary(polyline);
if (lineBoundary is MultiPoint boundaryPoints)
{
    Console.WriteLine($"折线边界点数:{boundaryPoints.Count}");  // 2(起点和终点)
}

// 多边形的边界是折线
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
    new Point(0, 0),
    new Point(10, 0),
    new Point(10, 10),
    new Point(0, 10),
    new Point(0, 0)
});

var polyBoundary = GeometryEngine.Boundary(polygon);
if (polyBoundary is Polyline boundaryLine)
{
    Console.WriteLine($"多边形边界长度:{boundaryLine.Length}");  // 40
}

5.8 Generalize(概化)

5.8.1 概念

概化操作通过删除顶点来简化几何对象,同时保持其总体形状。与 Simplify 类似,但参数含义不同:

  • Simplify:tolerance 是点到简化线的最大距离
  • Generalize:maxDeviation 是允许的最大偏差

5.8.2 API

public class GeneralizeOperator : IGeometryOperator<Geometry>
{
    public static GeneralizeOperator Instance { get; }
    
    public Geometry Execute(Geometry geometry, double maxDeviation, 
                           SpatialReference? spatialRef = null);
}

5.8.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
    new Point(0, 0),
    new Point(1, 0.5),
    new Point(2, 0),
    new Point(3, 0.3),
    new Point(4, 0),
    new Point(5, 1),
    new Point(6, 0)
});

Console.WriteLine($"原始点数:{polyline.GetPath(0).Count}");  // 7

var generalized = GeometryEngine.Generalize(polyline, 0.6);
if (generalized is Polyline genLine)
{
    Console.WriteLine($"概化后点数:{genLine.GetPath(0).Count}");  // 减少
}

5.9 Densify(密化)

5.9.1 概念

密化操作在几何对象的线段上添加额外的顶点,确保没有线段超过指定的最大长度。这是概化的逆操作。

应用场景

  • 为投影转换准备几何
  • 沿曲线均匀采样
  • 提高空间分析精度

5.9.2 API

public class DensifyOperator : IGeometryOperator<Geometry>
{
    public static DensifyOperator Instance { get; }
    
    public Geometry Execute(Geometry geometry, double maxSegmentLength, 
                           SpatialReference? spatialRef = null);
}

5.9.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 简单的两点折线
var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
    new Point(0, 0),
    new Point(10, 0)  // 长度为 10 的线段
});

Console.WriteLine($"原始点数:{polyline.GetPath(0).Count}");  // 2

// 密化,最大线段长度为 2
var densified = GeometryEngine.Densify(polyline, 2);

if (densified is Polyline denLine)
{
    Console.WriteLine($"密化后点数:{denLine.GetPath(0).Count}");  // 6
    // 点:(0,0), (2,0), (4,0), (6,0), (8,0), (10,0)
}

// 多边形密化
var square = new Polygon();
square.AddRing(new List<Point>
{
    new Point(0, 0),
    new Point(10, 0),
    new Point(10, 10),
    new Point(0, 10),
    new Point(0, 0)
});

var densifiedSquare = GeometryEngine.Densify(square, 3);

5.9.4 实现原理

private List<Point> DensifyPath(IReadOnlyList<Point> path, double maxLength)
{
    var result = new List<Point>();

    for (int i = 0; i < path.Count - 1; i++)
    {
        result.Add(path[i]);
        
        var start = path[i];
        var end = path[i + 1];
        double length = start.Distance(end);

        if (length > maxLength)
        {
            // 计算需要插入的点数
            int numSegments = (int)Math.Ceiling(length / maxLength);
            
            for (int j = 1; j < numSegments; j++)
            {
                double t = (double)j / numSegments;
                result.Add(new Point(
                    start.X + t * (end.X - start.X),
                    start.Y + t * (end.Y - start.Y)
                ));
            }
        }
    }

    result.Add(path[path.Count - 1]);
    return result;
}

5.10 Clip(裁剪)

5.10.1 概念

裁剪操作将几何对象裁剪到指定的包络矩形范围内。常用于视口裁剪和数据范围限制。

5.10.2 API

public class ClipOperator : IGeometryOperator<Geometry>
{
    public static ClipOperator Instance { get; }
    
    public Geometry Execute(Geometry geometry, Envelope clipEnvelope);
}

5.10.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 创建裁剪范围
var clipEnvelope = new Envelope(0, 0, 50, 50);

// 裁剪点
var pointInside = new Point(25, 25);
var pointOutside = new Point(100, 100);

var clippedIn = GeometryEngine.Clip(pointInside, clipEnvelope);   // 返回原点
var clippedOut = GeometryEngine.Clip(pointOutside, clipEnvelope); // 返回空点

// 裁剪折线(Cohen-Sutherland 算法)
var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
    new Point(-10, 25),  // 裁剪区外
    new Point(25, 25),   // 裁剪区内
    new Point(75, 25)    // 裁剪区外
});

var clippedLine = GeometryEngine.Clip(polyline, clipEnvelope);
// 结果:从 (0, 25) 到 (50, 25) 的线段

5.10.4 实现原理:Cohen-Sutherland 算法

// 区域码定义
private const int INSIDE = 0;  // 0000
private const int LEFT = 1;    // 0001
private const int RIGHT = 2;   // 0010
private const int BOTTOM = 4;  // 0100
private const int TOP = 8;     // 1000

private int ComputeOutCode(double x, double y, Envelope clip)
{
    int code = INSIDE;
    
    if (x < clip.XMin) code |= LEFT;
    else if (x > clip.XMax) code |= RIGHT;
    
    if (y < clip.YMin) code |= BOTTOM;
    else if (y > clip.YMax) code |= TOP;
    
    return code;
}

private Line? ClipLine(Point p1, Point p2, Envelope clip)
{
    int outcode1 = ComputeOutCode(p1.X, p1.Y, clip);
    int outcode2 = ComputeOutCode(p2.X, p2.Y, clip);
    
    double x1 = p1.X, y1 = p1.Y;
    double x2 = p2.X, y2 = p2.Y;
    
    while (true)
    {
        if ((outcode1 | outcode2) == 0)
        {
            // 两点都在内部
            return new Line(new Point(x1, y1), new Point(x2, y2));
        }
        else if ((outcode1 & outcode2) != 0)
        {
            // 线段完全在外部
            return null;
        }
        else
        {
            // 需要裁剪
            int outcodeOut = outcode1 != 0 ? outcode1 : outcode2;
            double x, y;
            
            if ((outcodeOut & TOP) != 0)
            {
                x = x1 + (x2 - x1) * (clip.YMax - y1) / (y2 - y1);
                y = clip.YMax;
            }
            else if ((outcodeOut & BOTTOM) != 0)
            {
                x = x1 + (x2 - x1) * (clip.YMin - y1) / (y2 - y1);
                y = clip.YMin;
            }
            else if ((outcodeOut & RIGHT) != 0)
            {
                y = y1 + (y2 - y1) * (clip.XMax - x1) / (x2 - x1);
                x = clip.XMax;
            }
            else
            {
                y = y1 + (y2 - y1) * (clip.XMin - x1) / (x2 - x1);
                x = clip.XMin;
            }
            
            // 更新端点
            if (outcodeOut == outcode1)
            {
                x1 = x; y1 = y;
                outcode1 = ComputeOutCode(x1, y1, clip);
            }
            else
            {
                x2 = x; y2 = y;
                outcode2 = ComputeOutCode(x2, y2, clip);
            }
        }
    }
}

5.11 Offset(偏移)

5.11.1 概念

偏移操作创建几何对象的偏移版本。正值向外偏移,负值向内偏移。

5.11.2 API

public class OffsetOperator : IGeometryOperator<Geometry>
{
    public static OffsetOperator Instance { get; }
    
    public Geometry Execute(Geometry geometry, double distance, 
                           SpatialReference? spatialRef = null);
}

5.11.3 使用示例

using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;

// 多边形偏移
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
    new Point(0, 0),
    new Point(100, 0),
    new Point(100, 100),
    new Point(0, 100),
    new Point(0, 0)
});

// 向外偏移 10
var offsetPolygon = GeometryEngine.Offset(polygon, 10);

// 向内偏移 10
var insetPolygon = GeometryEngine.Offset(polygon, -10);

5.12 小结

本章详细介绍了 geometry-api-net 的几何运算操作符:

操作符 功能 核心算法
Buffer 创建缓冲区 几何扩展
ConvexHull 计算凸包 Graham 扫描
Area 计算面积 鞋带公式
Length 计算长度 欧几里得距离
Simplify 简化几何 Douglas-Peucker
Centroid 计算质心 多边形质心公式
Boundary 获取边界 OGC 规范
Generalize 概化几何 Douglas-Peucker 变体
Densify 密化几何 线段插值
Clip 裁剪几何 Cohen-Sutherland
Offset 偏移几何 几何偏移

这些操作符是进行空间分析的核心工具。在下一章中,我们将学习集合操作符,用于计算几何对象之间的并集、交集和差集。