znlgis 博客

GIS开发与技术分享

第20章:实际应用与最佳实践

20.1 概述

本章将介绍 Clipper 在实际项目中的应用场景、性能优化技巧和最佳实践。

20.2 典型应用场景

20.2.1 CAD/CAM 应用

刀具补偿

// CNC 刀具补偿
Path toolPath = GetOriginalPath();
double toolRadius = 5.0;  // 刀具半径

ClipperOffset co = new ClipperOffset();
co.AddPath(toolPath, JoinType.jtRound, EndType.etClosedPolygon);

Paths compensated = new Paths();
co.Execute(ref compensated, -toolRadius);  // 内缩
// compensated 是补偿后的刀具路径

层叠切割

// 多层切割路径生成
Path outerBoundary = GetMaterialBoundary();
double stepover = 3.0;  // 切削步距

List<Paths> layers = new List<Paths>();
ClipperOffset co = new ClipperOffset();

double offset = 0;
while (true)
{
    co.Clear();
    co.AddPath(outerBoundary, JoinType.jtRound, EndType.etClosedPolygon);
    
    Paths layer = new Paths();
    co.Execute(ref layer, -offset);
    
    if (layer.Count == 0) break;
    layers.Add(layer);
    offset += stepover;
}

20.2.2 GIS 应用

缓冲区分析

// 河流缓冲区
Path riverCenterline = LoadRiverPath();
double bufferDistance = 100;  // 100米缓冲区

ClipperOffset co = new ClipperOffset();
co.AddPath(riverCenterline, JoinType.jtRound, EndType.etOpenRound);

Paths floodZone = new Paths();
co.Execute(ref floodZone, bufferDistance);

多边形叠加分析

// 土地利用叠加
Paths landuse = LoadLandusePolygons();
Paths zoning = LoadZoningPolygons();

Clipper c = new Clipper();
c.AddPaths(landuse, PolyType.ptSubject, true);
c.AddPaths(zoning, PolyType.ptClip, true);

Paths intersection = new Paths();
c.Execute(ClipType.ctIntersection, intersection);

20.2.3 图形设计

文字描边

// 为文字添加轮廓
Path textGlyph = GetTextGlyphPath('A');

ClipperOffset co = new ClipperOffset();
co.AddPath(textGlyph, JoinType.jtRound, EndType.etClosedPolygon);

// 内外轮廓
Paths outerStroke = new Paths();
Paths innerText = new Paths();

co.Execute(ref outerStroke, 3);   // 外扩
co.Clear();
co.AddPath(textGlyph, JoinType.jtRound, EndType.etClosedPolygon);
co.Execute(ref innerText, -1);    // 内缩(可选的瘦体效果)

20.2.4 游戏开发

碰撞检测

// 检测两个多边形是否重叠
bool CheckCollision(Path poly1, Path poly2)
{
    Clipper c = new Clipper();
    c.AddPath(poly1, PolyType.ptSubject, true);
    c.AddPath(poly2, PolyType.ptClip, true);
    
    Paths intersection = new Paths();
    c.Execute(ClipType.ctIntersection, intersection);
    
    return intersection.Count > 0;
}

// 计算重叠面积
double GetOverlapArea(Path poly1, Path poly2)
{
    Clipper c = new Clipper();
    c.AddPath(poly1, PolyType.ptSubject, true);
    c.AddPath(poly2, PolyType.ptClip, true);
    
    Paths intersection = new Paths();
    c.Execute(ClipType.ctIntersection, intersection);
    
    double area = 0;
    foreach (var p in intersection)
        area += Math.Abs(Clipper.Area(p));
    return area;
}

视野计算

// 视野锥与障碍物的交集
Path viewCone = CreateViewCone(position, direction, angle, distance);
Paths obstacles = GetObstacles();

Clipper c = new Clipper();
c.AddPath(viewCone, PolyType.ptSubject, true);
c.AddPaths(obstacles, PolyType.ptClip, true);

Paths visibleArea = new Paths();
c.Execute(ClipType.ctDifference, visibleArea);

20.3 性能优化

20.3.1 坐标缩放

// 使用整数坐标时的缩放策略
public class ScaledClipper
{
    private const double Scale = 1000.0;  // 缩放因子
    
    public static IntPoint ToClipperPoint(double x, double y)
    {
        return new IntPoint((cInt)(x * Scale), (cInt)(y * Scale));
    }
    
    public static PointF FromClipperPoint(IntPoint pt)
    {
        return new PointF((float)(pt.X / Scale), (float)(pt.Y / Scale));
    }
    
    public static Path ScalePath(IEnumerable<PointF> points)
    {
        Path result = new Path();
        foreach (var pt in points)
            result.Add(ToClipperPoint(pt.X, pt.Y));
        return result;
    }
    
    public static List<PointF> UnscalePath(Path path)
    {
        var result = new List<PointF>();
        foreach (var pt in path)
            result.Add(FromClipperPoint(pt));
        return result;
    }
}

20.3.2 批量操作

// 批量处理多个布尔运算
public static Paths BatchUnion(List<Path> polygons)
{
    if (polygons.Count == 0) return new Paths();
    if (polygons.Count == 1) return new Paths { polygons[0] };
    
    // 分治法合并
    while (polygons.Count > 1)
    {
        List<Path> merged = new List<Path>();
        Clipper c = new Clipper();
        
        for (int i = 0; i < polygons.Count; i += 2)
        {
            c.Clear();
            c.AddPath(polygons[i], PolyType.ptSubject, true);
            if (i + 1 < polygons.Count)
                c.AddPath(polygons[i + 1], PolyType.ptClip, true);
            
            Paths result = new Paths();
            c.Execute(ClipType.ctUnion, result);
            
            foreach (var p in result)
                merged.Add(p);
        }
        polygons = merged;
    }
    
    return new Paths { polygons[0] };
}

20.3.3 预分配内存

// 预分配容量
Path polygon = new Path(expectedPointCount);
Paths result = new Paths(expectedPathCount);

// 复用 Clipper 实例
Clipper clipper = new Clipper();
for (int i = 0; i < iterations; i++)
{
    clipper.Clear();  // 清理但保留分配的内存
    // ... 执行操作 ...
}

20.3.4 避免高精度模式

// 检查是否需要高精度
bool RequiresHighPrecision(Paths paths)
{
    foreach (var path in paths)
        foreach (var pt in path)
            if (Math.Abs(pt.X) > ClipperBase.loRange ||
                Math.Abs(pt.Y) > ClipperBase.loRange)
                return true;
    return false;
}

// 如果可能,缩放坐标以避免高精度
void NormalizeCoordinates(Paths paths, out double scale)
{
    long maxCoord = 1;
    foreach (var path in paths)
        foreach (var pt in path)
        {
            maxCoord = Math.Max(maxCoord, Math.Abs(pt.X));
            maxCoord = Math.Max(maxCoord, Math.Abs(pt.Y));
        }
    
    if (maxCoord > ClipperBase.loRange)
    {
        scale = (double)ClipperBase.loRange / maxCoord * 0.9;
        ScalePaths(paths, scale);
    }
    else
        scale = 1.0;
}

20.4 常见问题与解决方案

20.4.1 自相交处理

// 问题:输入多边形自相交
// 解决:使用 SimplifyPolygon
Path problematic = GetSelfIntersectingPath();
Paths fixed = Clipper.SimplifyPolygon(problematic);

20.4.2 微小间隙

// 问题:微小间隙导致分离
// 解决:先膨胀后收缩
Paths withGaps = GetPathsWithSmallGaps();

ClipperOffset co = new ClipperOffset();
co.AddPaths(withGaps, JoinType.jtRound, EndType.etClosedPolygon);

Paths expanded = new Paths();
co.Execute(ref expanded, 2);

co.Clear();
co.AddPaths(expanded, JoinType.jtRound, EndType.etClosedPolygon);

Paths closed = new Paths();
co.Execute(ref closed, -2);

20.4.3 浮点精度问题

// 问题:浮点坐标导致不一致
// 解决:使用适当的缩放因子
const double Scale = 1e6;  // 保留6位小数

Path ScaleUp(IEnumerable<PointD> points)
{
    Path result = new Path();
    foreach (var pt in points)
        result.Add(new IntPoint((long)(pt.X * Scale), (long)(pt.Y * Scale)));
    return result;
}

List<PointD> ScaleDown(Path path)
{
    var result = new List<PointD>();
    foreach (var pt in path)
        result.Add(new PointD(pt.X / Scale, pt.Y / Scale));
    return result;
}

20.4.4 方向不一致

// 确保所有外轮廓逆时针,孔洞顺时针
void NormalizeOrientation(Paths paths)
{
    foreach (var path in paths)
    {
        double area = Clipper.Area(path);
        // 假设第一个是外轮廓
        if (path == paths[0])
        {
            if (area < 0) path.Reverse();
        }
        else
        {
            // 孔洞应该顺时针
            if (area > 0) path.Reverse();
        }
    }
}

20.5 与其他库的集成

20.5.1 与 System.Drawing 集成

using System.Drawing;
using System.Drawing.Drawing2D;

public static GraphicsPath ToGraphicsPath(Paths paths)
{
    GraphicsPath gp = new GraphicsPath();
    foreach (var path in paths)
    {
        if (path.Count < 3) continue;
        PointF[] points = new PointF[path.Count];
        for (int i = 0; i < path.Count; i++)
            points[i] = new PointF(path[i].X, path[i].Y);
        gp.AddPolygon(points);
    }
    return gp;
}

20.5.2 与 WPF 集成

using System.Windows.Media;

public static PathGeometry ToWpfGeometry(Paths paths)
{
    PathGeometry pg = new PathGeometry();
    foreach (var path in paths)
    {
        if (path.Count < 3) continue;
        PathFigure figure = new PathFigure();
        figure.StartPoint = new Point(path[0].X, path[0].Y);
        figure.IsClosed = true;
        
        PolyLineSegment segment = new PolyLineSegment();
        for (int i = 1; i < path.Count; i++)
            segment.Points.Add(new Point(path[i].X, path[i].Y));
        
        figure.Segments.Add(segment);
        pg.Figures.Add(figure);
    }
    return pg;
}

20.5.3 与 Unity 集成

using UnityEngine;

public static Mesh ToUnityMesh(Paths paths)
{
    // 使用三角剖分库(如 Poly2Tri)
    List<Vector3> vertices = new List<Vector3>();
    List<int> triangles = new List<int>();
    
    // 转换并三角剖分
    // ...
    
    Mesh mesh = new Mesh();
    mesh.vertices = vertices.ToArray();
    mesh.triangles = triangles.ToArray();
    mesh.RecalculateNormals();
    return mesh;
}

20.6 调试技巧

20.6.1 可视化输出

public static void DebugDrawPaths(Paths paths, string filename)
{
    IntRect bounds = ClipperBase.GetBounds(paths);
    int margin = 10;
    int width = (int)(bounds.right - bounds.left + 2 * margin);
    int height = (int)(bounds.bottom - bounds.top + 2 * margin);
    
    using (Bitmap bmp = new Bitmap(width, height))
    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.Clear(Color.White);
        
        foreach (var path in paths)
        {
            if (path.Count < 2) continue;
            PointF[] pts = new PointF[path.Count];
            for (int i = 0; i < path.Count; i++)
                pts[i] = new PointF(
                    path[i].X - bounds.left + margin,
                    path[i].Y - bounds.top + margin);
            
            g.DrawPolygon(Pens.Blue, pts);
        }
        
        bmp.Save(filename);
    }
}

20.6.2 数据验证

public static bool ValidatePaths(Paths paths, out string error)
{
    error = null;
    
    foreach (var path in paths)
    {
        // 检查最小顶点数
        if (path.Count < 3)
        {
            error = "Path has fewer than 3 vertices";
            return false;
        }
        
        // 检查坐标范围
        foreach (var pt in path)
        {
            if (Math.Abs(pt.X) > ClipperBase.hiRange ||
                Math.Abs(pt.Y) > ClipperBase.hiRange)
            {
                error = "Coordinate out of range";
                return false;
            }
        }
        
        // 检查自相交
        Paths simplified = Clipper.SimplifyPolygon(path);
        if (simplified.Count != 1)
        {
            error = "Self-intersecting polygon";
            return false;
        }
    }
    
    return true;
}

20.7 本章小结

本章介绍了 Clipper 的实际应用与最佳实践:

  1. 应用场景
    • CAD/CAM:刀具补偿、层叠切割
    • GIS:缓冲区分析、叠加分析
    • 图形设计:描边、轮廓
    • 游戏开发:碰撞检测、视野计算
  2. 性能优化
    • 坐标缩放
    • 批量处理
    • 预分配内存
    • 避免高精度模式
  3. 常见问题
    • 自相交处理
    • 微小间隙
    • 浮点精度
    • 方向一致性
  4. 库集成
    • System.Drawing
    • WPF
    • Unity
  5. 调试技巧
    • 可视化输出
    • 数据验证

上一章:辅助函数与工具 返回目录