第6章_实际应用案例与最佳实践

第六章 实际应用案例与最佳实践

6.1 引言

在前面的章节中,我们系统地学习了 Clipper1 的核心功能、数据结构和高级特性。本章将通过多个完整的实际应用案例,展示如何将这些知识综合运用到真实项目中。同时,我们还将探讨性能优化、错误处理、代码组织等最佳实践,帮助您在生产环境中高效、可靠地使用 Clipper1。

本章涵盖的应用场景包括:

  • GIS 地理信息系统
  • CAD 图形设计
  • 游戏开发
  • 制造业应用
  • 数据可视化

通过这些案例,您将学会如何分析需求、设计解决方案、实现代码并优化性能。

6.2 案例一:GIS 地理信息系统

6.2.1 需求分析

实现一个地理信息系统的核心功能模块,包括:

  1. 地块查询和分析
  2. 缓冲区分析
  3. 叠加分析
  4. 面积统计
  5. 拓扑关系检查

6.2.2 系统设计

using System;
using System.Collections.Generic;
using System.Linq;
using ClipperLib;

using Path = System.Collections.Generic.List<ClipperLib.IntPoint>;
using Paths = System.Collections.Generic.List<System.Collections.Generic.List<ClipperLib.IntPoint>>;

namespace GISApplication
{
    /// <summary>
    /// 地理要素类
    /// </summary>
    public class GeoFeature
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Type { get; set; }
        public Path Geometry { get; set; }
        public Dictionary<string, object> Attributes { get; set; }

        public GeoFeature()
        {
            Attributes = new Dictionary<string, object>();
        }
    }

    /// <summary>
    /// GIS 分析引擎
    /// </summary>
    public class GISAnalyzer
    {
        private const double SCALE = 1000000.0;  // 米级精度

        /// <summary>
        /// 创建缓冲区
        /// </summary>
        public static GeoFeature CreateBuffer(GeoFeature feature, double distanceMeters)
        {
            ClipperOffset offset = new ClipperOffset();
            offset.AddPath(feature.Geometry, JoinType.jtRound, EndType.etClosedPolygon);

            Paths buffer = new Paths();
            offset.Execute(ref buffer, distanceMeters * SCALE);

            if (buffer.Count == 0)
                return null;

            return new GeoFeature
            {
                Id = feature.Id,
                Name = $"{feature.Name}_Buffer_{distanceMeters}m",
                Type = "Buffer",
                Geometry = buffer[0],
                Attributes = new Dictionary<string, object>(feature.Attributes)
            };
        }

        /// <summary>
        /// 空间查询:查找相交的要素
        /// </summary>
        public static List<GeoFeature> FindIntersecting(
            GeoFeature queryFeature,
            List<GeoFeature> targetFeatures)
        {
            var results = new List<GeoFeature>();

            foreach (var target in targetFeatures)
            {
                Clipper clipper = new Clipper();
                clipper.AddPath(queryFeature.Geometry, PolyType.ptSubject, true);
                clipper.AddPath(target.Geometry, PolyType.ptClip, true);

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

                if (intersection.Count > 0)
                {
                    results.Add(target);
                }
            }

            return results;
        }

        /// <summary>
        /// 空间查询:查找完全包含在内的要素
        /// </summary>
        public static List<GeoFeature> FindContained(
            GeoFeature containerFeature,
            List<GeoFeature> targetFeatures)
        {
            var results = new List<GeoFeature>();

            foreach (var target in targetFeatures)
            {
                // 计算交集
                Clipper clipper = new Clipper();
                clipper.AddPath(containerFeature.Geometry, PolyType.ptSubject, true);
                clipper.AddPath(target.Geometry, PolyType.ptClip, true);

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

                if (intersection.Count == 0)
                    continue;

                // 检查交集面积是否等于目标要素面积
                double targetArea = Math.Abs(Clipper.Area(target.Geometry));
                double intersectionArea = 0;
                foreach (var path in intersection)
                {
                    intersectionArea += Math.Abs(Clipper.Area(path));
                }

                // 允许0.1%的误差
                if (Math.Abs(targetArea - intersectionArea) / targetArea < 0.001)
                {
                    results.Add(target);
                }
            }

            return results;
        }

        /// <summary>
        /// 叠加分析:计算交集并保留属性
        /// </summary>
        public static List<GeoFeature> OverlayAnalysis(
            List<GeoFeature> layer1,
            List<GeoFeature> layer2,
            ClipType operation)
        {
            var results = new List<GeoFeature>();
            int resultId = 1;

            foreach (var feature1 in layer1)
            {
                foreach (var feature2 in layer2)
                {
                    Clipper clipper = new Clipper();
                    clipper.AddPath(feature1.Geometry, PolyType.ptSubject, true);
                    clipper.AddPath(feature2.Geometry, PolyType.ptClip, true);

                    Paths result = new Paths();
                    clipper.Execute(operation, result);

                    foreach (var path in result)
                    {
                        var newFeature = new GeoFeature
                        {
                            Id = resultId++,
                            Name = $"{feature1.Name}_{feature2.Name}",
                            Type = operation.ToString(),
                            Geometry = path
                        };

                        // 合并属性
                        foreach (var attr in feature1.Attributes)
                        {
                            newFeature.Attributes[$"Layer1_{attr.Key}"] = attr.Value;
                        }
                        foreach (var attr in feature2.Attributes)
                        {
                            newFeature.Attributes[$"Layer2_{attr.Key}"] = attr.Value;
                        }

                        // 计算新的面积
                        double area = Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
                        newFeature.Attributes["Area_m2"] = area;

                        results.Add(newFeature);
                    }
                }
            }

            return results;
        }

        /// <summary>
        /// 计算要素统计信息
        /// </summary>
        public static Dictionary<string, object> CalculateStatistics(List<GeoFeature> features)
        {
            var stats = new Dictionary<string, object>();

            if (features.Count == 0)
                return stats;

            double totalArea = 0;
            double minArea = double.MaxValue;
            double maxArea = double.MinValue;
            int totalVertices = 0;

            foreach (var feature in features)
            {
                double area = Math.Abs(Clipper.Area(feature.Geometry)) / (SCALE * SCALE);
                totalArea += area;
                minArea = Math.Min(minArea, area);
                maxArea = Math.Max(maxArea, area);
                totalVertices += feature.Geometry.Count;
            }

            stats["Count"] = features.Count;
            stats["TotalArea_m2"] = totalArea;
            stats["AverageArea_m2"] = totalArea / features.Count;
            stats["MinArea_m2"] = minArea;
            stats["MaxArea_m2"] = maxArea;
            stats["TotalVertices"] = totalVertices;
            stats["AverageVertices"] = (double)totalVertices / features.Count;

            return stats;
        }

        /// <summary>
        /// 拓扑检查:查找重叠
        /// </summary>
        public static List<(int id1, int id2, double overlapArea)> FindOverlaps(
            List<GeoFeature> features)
        {
            var overlaps = new List<(int, int, double)>();

            for (int i = 0; i < features.Count; i++)
            {
                for (int j = i + 1; j < features.Count; j++)
                {
                    Clipper clipper = new Clipper();
                    clipper.AddPath(features[i].Geometry, PolyType.ptSubject, true);
                    clipper.AddPath(features[j].Geometry, PolyType.ptClip, true);

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

                    if (intersection.Count > 0)
                    {
                        double overlapArea = 0;
                        foreach (var path in intersection)
                        {
                            overlapArea += Math.Abs(Clipper.Area(path));
                        }
                        overlapArea /= (SCALE * SCALE);

                        if (overlapArea > 0.01)  // 忽略极小的重叠
                        {
                            overlaps.Add((features[i].Id, features[j].Id, overlapArea));
                        }
                    }
                }
            }

            return overlaps;
        }
    }
}

6.2.3 使用示例

class GISApplicationDemo
{
    static void RunGISDemo()
    {
        Console.WriteLine("=== GIS 应用示例 ===\n");

        // 创建测试数据
        var parcels = CreateSampleParcels();
        var roads = CreateSampleRoads();

        Console.WriteLine($"地块数量: {parcels.Count}");
        Console.WriteLine($"道路数量: {roads.Count}\n");

        // 1. 缓冲区分析
        Console.WriteLine("--- 缓冲区分析 ---");
        var bufferedRoads = new List<GeoFeature>();
        foreach (var road in roads)
        {
            var buffer = GISAnalyzer.CreateBuffer(road, 10.0);  // 10米缓冲区
            if (buffer != null)
            {
                bufferedRoads.Add(buffer);
            }
        }
        Console.WriteLine($"创建了 {bufferedRoads.Count} 个道路缓冲区\n");

        // 2. 空间查询
        Console.WriteLine("--- 空间查询 ---");
        if (parcels.Count > 0 && bufferedRoads.Count > 0)
        {
            var affectedParcels = GISAnalyzer.FindIntersecting(
                bufferedRoads[0],
                parcels
            );
            Console.WriteLine($"受道路缓冲区影响的地块: {affectedParcels.Count}\n");
        }

        // 3. 叠加分析
        Console.WriteLine("--- 叠加分析 ---");
        var intersectionResults = GISAnalyzer.OverlayAnalysis(
            parcels.Take(3).ToList(),
            bufferedRoads,
            ClipType.ctIntersection
        );
        Console.WriteLine($"交集分析产生 {intersectionResults.Count} 个要素\n");

        // 4. 统计分析
        Console.WriteLine("--- 统计分析 ---");
        var stats = GISAnalyzer.CalculateStatistics(parcels);
        foreach (var stat in stats)
        {
            Console.WriteLine($"  {stat.Key}: {stat.Value}");
        }
        Console.WriteLine();

        // 5. 拓扑检查
        Console.WriteLine("--- 拓扑检查 ---");
        var overlaps = GISAnalyzer.FindOverlaps(parcels);
        if (overlaps.Count > 0)
        {
            Console.WriteLine($"发现 {overlaps.Count} 处重叠:");
            foreach (var overlap in overlaps)
            {
                Console.WriteLine($"  地块 {overlap.id1} 与 地块 {overlap.id2}: {overlap.overlapArea:F2} m²");
            }
        }
        else
        {
            Console.WriteLine("未发现重叠");
        }
    }

    static List<GeoFeature> CreateSampleParcels()
    {
        const double SCALE = 1000000.0;
        var parcels = new List<GeoFeature>();

        // 创建几个示例地块
        for (int i = 0; i < 5; i++)
        {
            double x = i * 50.0;
            double y = 0;

            var parcel = new GeoFeature
            {
                Id = i + 1,
                Name = $"Parcel_{i + 1}",
                Type = "Parcel",
                Geometry = new Path
                {
                    new IntPoint((long)(x * SCALE), (long)(y * SCALE)),
                    new IntPoint((long)((x + 40) * SCALE), (long)(y * SCALE)),
                    new IntPoint((long)((x + 40) * SCALE), (long)((y + 60) * SCALE)),
                    new IntPoint((long)(x * SCALE), (long)((y + 60) * SCALE))
                }
            };

            parcel.Attributes["Owner"] = $"Owner_{i + 1}";
            parcel.Attributes["LandUse"] = i % 2 == 0 ? "Residential" : "Commercial";

            parcels.Add(parcel);
        }

        return parcels;
    }

    static List<GeoFeature> CreateSampleRoads()
    {
        const double SCALE = 1000000.0;
        var roads = new List<GeoFeature>();

        // 创建一条横向道路
        var road = new GeoFeature
        {
            Id = 1,
            Name = "Main_Street",
            Type = "Road",
            Geometry = new Path
            {
                new IntPoint(0, (long)(30 * SCALE)),
                new IntPoint((long)(250 * SCALE), (long)(30 * SCALE)),
                new IntPoint((long)(250 * SCALE), (long)(35 * SCALE)),
                new IntPoint(0, (long)(35 * SCALE))
            }
        };

        road.Attributes["RoadType"] = "Primary";
        road.Attributes["Width_m"] = 5.0;

        roads.Add(road);

        return roads;
    }
}

6.3 案例二:CAD 图形设计工具

6.3.1 需求分析

实现一个简单的 CAD 图形设计工具,支持:

  1. 基本图形绘制
  2. 图形的布尔运算
  3. 偏移操作
  4. 图层管理
  5. 导出功能

6.3.2 系统实现

namespace CADApplication
{
    /// <summary>
    /// CAD 图形对象
    /// </summary>
    public class CADShape
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Layer { get; set; }
        public Path Geometry { get; set; }
        public bool IsVisible { get; set; }
        public bool IsLocked { get; set; }

        public CADShape()
        {
            Id = Guid.NewGuid();
            IsVisible = true;
            IsLocked = false;
        }
    }

    /// <summary>
    /// CAD 文档
    /// </summary>
    public class CADDocument
    {
        private const double SCALE = 1000.0;
        private List<CADShape> shapes;
        private Dictionary<string, bool> layers;

        public CADDocument()
        {
            shapes = new List<CADShape>();
            layers = new Dictionary<string, bool>();
            layers["Default"] = true;
        }

        /// <summary>
        /// 添加形状
        /// </summary>
        public void AddShape(CADShape shape)
        {
            if (!layers.ContainsKey(shape.Layer))
            {
                layers[shape.Layer] = true;
            }
            shapes.Add(shape);
        }

        /// <summary>
        /// 删除形状
        /// </summary>
        public bool RemoveShape(Guid id)
        {
            var shape = shapes.FirstOrDefault(s => s.Id == id);
            if (shape != null && !shape.IsLocked)
            {
                shapes.Remove(shape);
                return true;
            }
            return false;
        }

        /// <summary>
        /// 获取图层上的所有形状
        /// </summary>
        public List<CADShape> GetShapesInLayer(string layerName)
        {
            return shapes.Where(s => s.Layer == layerName).ToList();
        }

        /// <summary>
        /// 布尔运算
        /// </summary>
        public CADShape BooleanOperation(
            CADShape shape1,
            CADShape shape2,
            ClipType operation,
            string resultLayer = "Result")
        {
            Clipper clipper = new Clipper();
            clipper.AddPath(shape1.Geometry, PolyType.ptSubject, true);
            clipper.AddPath(shape2.Geometry, PolyType.ptClip, true);

            Paths solution = new Paths();
            clipper.Execute(operation, solution);

            if (solution.Count == 0)
                return null;

            return new CADShape
            {
                Name = $"{shape1.Name}_{operation}_{shape2.Name}",
                Layer = resultLayer,
                Geometry = solution[0]
            };
        }

        /// <summary>
        /// 偏移操作
        /// </summary>
        public CADShape OffsetShape(
            CADShape shape,
            double distance,
            JoinType joinType = JoinType.jtRound)
        {
            ClipperOffset offset = new ClipperOffset();
            offset.AddPath(shape.Geometry, joinType, EndType.etClosedPolygon);

            Paths solution = new Paths();
            offset.Execute(ref solution, distance * SCALE);

            if (solution.Count == 0)
                return null;

            return new CADShape
            {
                Name = $"{shape.Name}_Offset_{distance}",
                Layer = shape.Layer,
                Geometry = solution[0]
            };
        }

        /// <summary>
        /// 阵列复制
        /// </summary>
        public List<CADShape> ArrayCopy(
            CADShape shape,
            int rows,
            int columns,
            double rowSpacing,
            double columnSpacing)
        {
            var copies = new List<CADShape>();

            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < columns; j++)
                {
                    if (i == 0 && j == 0)
                        continue;  // 跳过原始位置

                    long dx = (long)(j * columnSpacing * SCALE);
                    long dy = (long)(i * rowSpacing * SCALE);

                    Path copiedGeometry = new Path();
                    foreach (var pt in shape.Geometry)
                    {
                        copiedGeometry.Add(new IntPoint(pt.X + dx, pt.Y + dy));
                    }

                    var copy = new CADShape
                    {
                        Name = $"{shape.Name}_Array_{i}_{j}",
                        Layer = shape.Layer,
                        Geometry = copiedGeometry
                    };

                    copies.Add(copy);
                }
            }

            return copies;
        }

        /// <summary>
        /// 镜像
        /// </summary>
        public CADShape Mirror(CADShape shape, bool horizontal)
        {
            // 计算中心点
            long centerX = 0, centerY = 0;
            foreach (var pt in shape.Geometry)
            {
                centerX += pt.X;
                centerY += pt.Y;
            }
            centerX /= shape.Geometry.Count;
            centerY /= shape.Geometry.Count;

            Path mirrored = new Path();
            foreach (var pt in shape.Geometry)
            {
                if (horizontal)
                {
                    // 水平镜像
                    mirrored.Add(new IntPoint(
                        2 * centerX - pt.X,
                        pt.Y
                    ));
                }
                else
                {
                    // 垂直镜像
                    mirrored.Add(new IntPoint(
                        pt.X,
                        2 * centerY - pt.Y
                    ));
                }
            }

            return new CADShape
            {
                Name = $"{shape.Name}_Mirror",
                Layer = shape.Layer,
                Geometry = mirrored
            };
        }

        /// <summary>
        /// 合并图层中的所有形状
        /// </summary>
        public CADShape MergeLayer(string layerName)
        {
            var layerShapes = GetShapesInLayer(layerName);
            if (layerShapes.Count == 0)
                return null;

            Clipper clipper = new Clipper();
            foreach (var shape in layerShapes)
            {
                clipper.AddPath(shape.Geometry, PolyType.ptSubject, true);
            }

            Paths merged = new Paths();
            clipper.Execute(ClipType.ctUnion, merged);

            if (merged.Count == 0)
                return null;

            return new CADShape
            {
                Name = $"{layerName}_Merged",
                Layer = layerName,
                Geometry = merged[0]
            };
        }

        /// <summary>
        /// 导出为文本格式
        /// </summary>
        public string ExportToText()
        {
            var sb = new System.Text.StringBuilder();
            sb.AppendLine($"CAD Document - {shapes.Count} shapes");
            sb.AppendLine($"Layers: {string.Join(", ", layers.Keys)}");
            sb.AppendLine();

            foreach (var shape in shapes)
            {
                sb.AppendLine($"Shape: {shape.Name}");
                sb.AppendLine($"  Layer: {shape.Layer}");
                sb.AppendLine($"  Vertices: {shape.Geometry.Count}");
                sb.AppendLine($"  Area: {Math.Abs(Clipper.Area(shape.Geometry)) / (SCALE * SCALE):F2}");
                sb.AppendLine();
            }

            return sb.ToString();
        }
    }
}

6.3.3 使用示例

class CADApplicationDemo
{
    static void RunCADDemo()
    {
        Console.WriteLine("=== CAD 应用示例 ===\n");

        const double SCALE = 1000.0;
        var doc = new CADDocument();

        // 1. 创建基础形状
        var rectangle = new CADShape
        {
            Name = "Rectangle",
            Layer = "Base",
            Geometry = CreateRectangle(0, 0, 100, 80, SCALE)
        };
        doc.AddShape(rectangle);

        var circle = new CADShape
        {
            Name = "Circle",
            Layer = "Base",
            Geometry = CreateCircle(50, 40, 30, 32, SCALE)
        };
        doc.AddShape(circle);

        Console.WriteLine("创建了基础形状");

        // 2. 布尔运算
        var difference = doc.BooleanOperation(
            rectangle,
            circle,
            ClipType.ctDifference,
            "Result"
        );
        if (difference != null)
        {
            doc.AddShape(difference);
            Console.WriteLine("执行了差集运算");
        }

        // 3. 偏移操作
        var offsetShape = doc.OffsetShape(rectangle, 5, JoinType.jtRound);
        if (offsetShape != null)
        {
            offsetShape.Layer = "Offset";
            doc.AddShape(offsetShape);
            Console.WriteLine("创建了偏移形状");
        }

        // 4. 阵列复制
        var arrayShapes = doc.ArrayCopy(circle, 2, 3, 60, 70);
        foreach (var shape in arrayShapes)
        {
            shape.Layer = "Array";
            doc.AddShape(shape);
        }
        Console.WriteLine($"创建了 {arrayShapes.Count} 个阵列副本");

        // 5. 镜像
        var mirrored = doc.Mirror(rectangle, true);
        if (mirrored != null)
        {
            mirrored.Layer = "Mirror";
            doc.AddShape(mirrored);
            Console.WriteLine("创建了镜像形状");
        }

        // 6. 导出
        Console.WriteLine("\n" + doc.ExportToText());
    }

    static Path CreateRectangle(double x, double y, double width, double height, double scale)
    {
        return new Path
        {
            new IntPoint((long)(x * scale), (long)(y * scale)),
            new IntPoint((long)((x + width) * scale), (long)(y * scale)),
            new IntPoint((long)((x + width) * scale), (long)((y + height) * scale)),
            new IntPoint((long)(x * scale), (long)((y + height) * scale))
        };
    }

    static Path CreateCircle(double cx, double cy, double radius, int segments, double scale)
    {
        Path circle = new Path();
        for (int i = 0; i < segments; i++)
        {
            double angle = 2 * Math.PI * i / segments;
            double x = cx + radius * Math.Cos(angle);
            double y = cy + radius * Math.Sin(angle);
            circle.Add(new IntPoint((long)(x * scale), (long)(y * scale)));
        }
        return circle;
    }
}

6.4 性能优化最佳实践

6.4.1 输入数据优化

class InputOptimization
{
    /// <summary>
    /// 预处理多边形以提高性能
    /// </summary>
    public static Path PreprocessPolygon(Path input, double scale)
    {
        // 1. 移除重复点
        Path noDuplicates = RemoveDuplicates(input);

        // 2. 清理共线点
        Path cleaned = new Path(noDuplicates);
        Clipper.CleanPolygon(cleaned, 0.1 * scale);

        // 3. 简化自相交
        Paths simplified = Clipper.SimplifyPolygon(cleaned);

        return simplified.Count > 0 ? simplified[0] : cleaned;
    }

    private static Path RemoveDuplicates(Path input)
    {
        if (input.Count < 2)
            return input;

        Path result = new Path { input[0] };

        for (int i = 1; i < input.Count; i++)
        {
            if (input[i].X != result[result.Count - 1].X ||
                input[i].Y != result[result.Count - 1].Y)
            {
                result.Add(input[i]);
            }
        }

        return result;
    }
}

6.4.2 批量操作优化

class BatchOptimization
{
    /// <summary>
    /// 优化的批量布尔运算
    /// </summary>
    public static Paths BatchUnion(List<Path> polygons)
    {
        if (polygons.Count == 0)
            return new Paths();

        if (polygons.Count == 1)
            return new Paths { polygons[0] };

        // 使用分治法
        return DivideAndConquerUnion(polygons, 0, polygons.Count);
    }

    private static Paths DivideAndConquerUnion(List<Path> polygons, int start, int end)
    {
        int count = end - start;

        if (count == 1)
        {
            return new Paths { polygons[start] };
        }

        if (count == 2)
        {
            Clipper clipper = new Clipper();
            clipper.AddPath(polygons[start], PolyType.ptSubject, true);
            clipper.AddPath(polygons[start + 1], PolyType.ptClip, true);

            Paths result = new Paths();
            clipper.Execute(ClipType.ctUnion, result);
            return result;
        }

        // 分治
        int mid = start + count / 2;
        Paths left = DivideAndConquerUnion(polygons, start, mid);
        Paths right = DivideAndConquerUnion(polygons, mid, end);

        // 合并
        Clipper clipper2 = new Clipper();
        clipper2.AddPaths(left, PolyType.ptSubject, true);
        clipper2.AddPaths(right, PolyType.ptClip, true);

        Paths merged = new Paths();
        clipper2.Execute(ClipType.ctUnion, merged);
        return merged;
    }
}

6.4.3 空间索引优化

class SpatialIndex
{
    private class BoundingBox
    {
        public long MinX, MinY, MaxX, MaxY;

        public bool Intersects(BoundingBox other)
        {
            return !(MaxX < other.MinX || other.MaxX < MinX ||
                    MaxY < other.MinY || other.MaxY < MinY);
        }
    }

    private Dictionary<int, BoundingBox> index;

    public SpatialIndex()
    {
        index = new Dictionary<int, BoundingBox>();
    }

    public void AddPolygon(int id, Path polygon)
    {
        var bounds = CalculateBounds(polygon);
        index[id] = bounds;
    }

    public List<int> QueryIntersecting(Path queryPolygon)
    {
        var queryBounds = CalculateBounds(queryPolygon);
        var candidates = new List<int>();

        foreach (var kvp in index)
        {
            if (queryBounds.Intersects(kvp.Value))
            {
                candidates.Add(kvp.Key);
            }
        }

        return candidates;
    }

    private BoundingBox CalculateBounds(Path polygon)
    {
        var bounds = new BoundingBox
        {
            MinX = long.MaxValue,
            MinY = long.MaxValue,
            MaxX = long.MinValue,
            MaxY = long.MinValue
        };

        foreach (var pt in polygon)
        {
            bounds.MinX = Math.Min(bounds.MinX, pt.X);
            bounds.MinY = Math.Min(bounds.MinY, pt.Y);
            bounds.MaxX = Math.Max(bounds.MaxX, pt.X);
            bounds.MaxY = Math.Max(bounds.MaxY, pt.Y);
        }

        return bounds;
    }
}

6.5 错误处理和健壮性

6.5.1 全面的错误处理

class RobustClipperOperations
{
    private const double SCALE = 1000.0;

    public class ClipperResult<T>
    {
        public bool Success { get; set; }
        public T Data { get; set; }
        public string ErrorMessage { get; set; }
        public Exception Exception { get; set; }
    }

    /// <summary>
    /// 安全的布尔运算
    /// </summary>
    public static ClipperResult<Paths> SafeBooleanOperation(
        Path subject,
        Path clip,
        ClipType operation)
    {
        try
        {
            // 验证输入
            var validation = ValidateInput(subject, clip);
            if (!validation.Success)
            {
                return new ClipperResult<Paths>
                {
                    Success = false,
                    ErrorMessage = validation.ErrorMessage
                };
            }

            // 执行运算
            Clipper clipper = new Clipper();
            clipper.AddPath(subject, PolyType.ptSubject, true);
            clipper.AddPath(clip, PolyType.ptClip, true);

            Paths solution = new Paths();
            bool success = clipper.Execute(operation, solution);

            if (!success)
            {
                return new ClipperResult<Paths>
                {
                    Success = false,
                    ErrorMessage = "Clipper 运算失败"
                };
            }

            // 验证输出
            if (solution.Count == 0)
            {
                return new ClipperResult<Paths>
                {
                    Success = true,
                    Data = solution,
                    ErrorMessage = "运算产生空结果(这可能是正常的)"
                };
            }

            return new ClipperResult<Paths>
            {
                Success = true,
                Data = solution
            };
        }
        catch (Exception ex)
        {
            return new ClipperResult<Paths>
            {
                Success = false,
                ErrorMessage = $"发生异常: {ex.Message}",
                Exception = ex
            };
        }
    }

    private static ClipperResult<bool> ValidateInput(Path subject, Path clip)
    {
        if (subject == null || clip == null)
        {
            return new ClipperResult<bool>
            {
                Success = false,
                ErrorMessage = "输入多边形为 null"
            };
        }

        if (subject.Count < 3 || clip.Count < 3)
        {
            return new ClipperResult<bool>
            {
                Success = false,
                ErrorMessage = "多边形顶点数不足(至少需要3个)"
            };
        }

        double subjectArea = Math.Abs(Clipper.Area(subject));
        double clipArea = Math.Abs(Clipper.Area(clip));

        if (subjectArea < 0.001 || clipArea < 0.001)
        {
            return new ClipperResult<bool>
            {
                Success = false,
                ErrorMessage = "多边形面积几乎为零"
            };
        }

        return new ClipperResult<bool> { Success = true };
    }
}

6.5.2 日志和调试

class ClipperLogger
{
    public enum LogLevel
    {
        Debug,
        Info,
        Warning,
        Error
    }

    private List<string> logs = new List<string>();
    private LogLevel minLevel = LogLevel.Info;

    public void Log(LogLevel level, string message)
    {
        if (level >= minLevel)
        {
            string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
            string logEntry = $"[{timestamp}] [{level}] {message}";
            logs.Add(logEntry);
            Console.WriteLine(logEntry);
        }
    }

    public void LogOperation(string operation, Path subject, Path clip, Paths result)
    {
        Log(LogLevel.Info, $"操作: {operation}");
        Log(LogLevel.Debug, $"  Subject顶点数: {subject?.Count ?? 0}");
        Log(LogLevel.Debug, $"  Clip顶点数: {clip?.Count ?? 0}");
        Log(LogLevel.Info, $"  结果多边形数: {result?.Count ?? 0}");

        if (result != null && result.Count > 0)
        {
            double totalArea = result.Sum(p => Math.Abs(Clipper.Area(p)));
            Log(LogLevel.Debug, $"  总面积: {totalArea}");
        }
    }

    public string GetLogsAsString()
    {
        return string.Join(Environment.NewLine, logs);
    }

    public void SaveLogsToFile(string filename)
    {
        System.IO.File.WriteAllLines(filename, logs);
    }
}

6.6 代码组织和架构

6.6.1 分层架构示例

// 数据层
namespace ClipperApp.Data
{
    public interface IGeometryRepository
    {
        Path GetGeometry(int id);
        void SaveGeometry(int id, Path geometry);
        List<int> GetAllIds();
    }
}

// 业务逻辑层
namespace ClipperApp.Business
{
    public class GeometryService
    {
        private readonly IGeometryRepository repository;
        private readonly ClipperLogger logger;

        public GeometryService(IGeometryRepository repo, ClipperLogger log)
        {
            repository = repo;
            logger = log;
        }

        public Paths PerformUnion(int id1, int id2)
        {
            logger.Log(ClipperLogger.LogLevel.Info, 
                $"开始并集运算: {id1} ∪ {id2}");

            var geo1 = repository.GetGeometry(id1);
            var geo2 = repository.GetGeometry(id2);

            Clipper clipper = new Clipper();
            clipper.AddPath(geo1, PolyType.ptSubject, true);
            clipper.AddPath(geo2, PolyType.ptClip, true);

            Paths result = new Paths();
            bool success = clipper.Execute(ClipType.ctUnion, result);

            logger.LogOperation("Union", geo1, geo2, result);

            return result;
        }
    }
}

// 表示层
namespace ClipperApp.Presentation
{
    public class GeometryController
    {
        private readonly GeometryService service;

        public GeometryController(GeometryService svc)
        {
            service = svc;
        }

        public void ExecuteUnionCommand(int id1, int id2)
        {
            try
            {
                var result = service.PerformUnion(id1, id2);
                Console.WriteLine($"并集运算成功,产生 {result.Count} 个多边形");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"运算失败: {ex.Message}");
            }
        }
    }
}

6.7 测试策略

6.7.1 单元测试示例

using NUnit.Framework;  // 或使用 xUnit, MSTest

[TestFixture]
public class ClipperOperationsTests
{
    private const double SCALE = 1000.0;

    [Test]
    public void Intersection_TwoSquares_ReturnsCorrectArea()
    {
        // Arrange
        Path square1 = CreateSquare(0, 0, 100);
        Path square2 = CreateSquare(50, 50, 100);

        // Act
        Clipper clipper = new Clipper();
        clipper.AddPath(square1, PolyType.ptSubject, true);
        clipper.AddPath(square2, PolyType.ptClip, true);

        Paths result = new Paths();
        clipper.Execute(ClipType.ctIntersection, result);

        // Assert
        Assert.AreEqual(1, result.Count, "应该产生一个多边形");
        double area = Math.Abs(Clipper.Area(result[0])) / (SCALE * SCALE);
        Assert.AreEqual(2500, area, 1.0, "交集面积应该是2500");
    }

    [Test]
    public void Union_OverlappingSquares_MergesCorrectly()
    {
        // Arrange
        Path square1 = CreateSquare(0, 0, 100);
        Path square2 = CreateSquare(50, 0, 100);

        // Act
        Clipper clipper = new Clipper();
        clipper.AddPath(square1, PolyType.ptSubject, true);
        clipper.AddPath(square2, PolyType.ptClip, true);

        Paths result = new Paths();
        clipper.Execute(ClipType.ctUnion, result);

        // Assert
        Assert.AreEqual(1, result.Count);
        double area = Math.Abs(Clipper.Area(result[0])) / (SCALE * SCALE);
        Assert.AreEqual(15000, area, 1.0, "并集面积应该是15000");
    }

    private Path CreateSquare(double x, double y, double size)
    {
        return new Path
        {
            new IntPoint((long)(x * SCALE), (long)(y * SCALE)),
            new IntPoint((long)((x + size) * SCALE), (long)(y * SCALE)),
            new IntPoint((long)((x + size) * SCALE), (long)((y + size) * SCALE)),
            new IntPoint((long)(x * SCALE), (long)((y + size) * SCALE))
        };
    }
}

6.8 部署和集成建议

6.8.1 配置管理

public class ClipperConfiguration
{
    public double DefaultScale { get; set; } = 1000000.0;
    public double DefaultArcTolerance { get; set; } = 0.25;
    public double DefaultMiterLimit { get; set; } = 2.0;
    public bool EnableLogging { get; set; } = true;
    public string LogFilePath { get; set; } = "clipper.log";

    public static ClipperConfiguration LoadFromFile(string path)
    {
        // 从配置文件加载
        // 可以使用 JSON, XML, 或其他格式
        return new ClipperConfiguration();
    }

    public void SaveToFile(string path)
    {
        // 保存配置到文件
    }
}

6.9 本章小结

在本章中,我们通过实际应用案例学习了:

  1. GIS 系统:完整的地理信息分析模块
  2. CAD 工具:图形设计和编辑功能
  3. 性能优化:输入预处理、批量操作、空间索引
  4. 错误处理:全面的异常处理和验证
  5. 代码组织:分层架构和模块化设计
  6. 测试策略:单元测试和集成测试
  7. 部署建议:配置管理和集成方式

关键要点:

  • 根据应用场景选择合适的算法和参数
  • 重视输入验证和错误处理
  • 采用分层架构提高代码可维护性
  • 通过测试确保功能正确性
  • 持续优化性能

6.10 最佳实践清单

  1. 输入处理

    • 总是验证输入数据的有效性
    • 预处理多边形以提高性能
    • 使用合适的缩放系数
  2. 错误处理

    • 使用 try-catch 捕获异常
    • 提供有意义的错误消息
    • 记录关键操作的日志
  3. 性能优化

    • 对大量数据使用空间索引
    • 批量操作使用分治法
    • 复用 Clipper 对象
  4. 代码质量

    • 遵循 SOLID 原则
    • 编写单元测试
    • 添加适当的注释
  5. 维护性

    • 使用配置文件管理参数
    • 模块化设计便于扩展
    • 保持代码简洁清晰

6.11 进一步学习资源

  1. 官方资源

    • Clipper1 官方文档
    • 源代码和示例
  2. 社区资源

    • Stack Overflow
    • GitHub 项目
    • 技术博客
  3. 相关技术

    • 计算几何学
    • GIS 理论
    • CAD 技术
  4. 后续发展

    • Clipper2 的新特性
    • 其他几何库的对比
    • 特定领域的深入应用

6.12 总结

通过本教程的学习,您已经全面掌握了 Clipper1 的使用方法,从基础概念到高级应用,从理论知识到实践技巧。Clipper1 是一个强大而可靠的几何运算库,在正确使用的情况下,可以解决各种复杂的多边形处理问题。

记住以下几点:

  • 理解算法原理有助于更好地应用
  • 实践是掌握技能的最佳途径
  • 性能优化需要根据具体场景调整
  • 代码质量和可维护性同样重要

祝您在使用 Clipper1 的项目中取得成功!如果遇到问题,可以参考官方文档、社区资源,或回顾本教程的相关章节。

6.13 练习项目

为了巩固所学知识,建议完成以下综合项目:

  1. 地图编辑器

    • 支持多边形的绘制和编辑
    • 实现各种布尔运算
    • 提供图层管理功能
  2. 路径规划系统

    • 计算障碍物缓冲区
    • 生成可行走区域
    • 优化路径计算
  3. 制造业应用

    • CNC 刀具路径生成
    • 嵌套排样优化
    • 材料利用率分析
  4. 数据分析工具

    • 地理数据的空间分析
    • 可视化结果展示
    • 批量处理功能

通过这些项目,您将能够综合运用 Clipper1 的各种功能,并积累宝贵的实践经验。

posted @ 2025-12-18 09:26  我才是银古  阅读(15)  评论(0)    收藏  举报