第6章_实际应用案例与最佳实践
第六章 实际应用案例与最佳实践
6.1 引言
在前面的章节中,我们系统地学习了 Clipper1 的核心功能、数据结构和高级特性。本章将通过多个完整的实际应用案例,展示如何将这些知识综合运用到真实项目中。同时,我们还将探讨性能优化、错误处理、代码组织等最佳实践,帮助您在生产环境中高效、可靠地使用 Clipper1。
本章涵盖的应用场景包括:
- GIS 地理信息系统
- CAD 图形设计
- 游戏开发
- 制造业应用
- 数据可视化
通过这些案例,您将学会如何分析需求、设计解决方案、实现代码并优化性能。
6.2 案例一:GIS 地理信息系统
6.2.1 需求分析
实现一个地理信息系统的核心功能模块,包括:
- 地块查询和分析
- 缓冲区分析
- 叠加分析
- 面积统计
- 拓扑关系检查
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 图形设计工具,支持:
- 基本图形绘制
- 图形的布尔运算
- 偏移操作
- 图层管理
- 导出功能
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 本章小结
在本章中,我们通过实际应用案例学习了:
- GIS 系统:完整的地理信息分析模块
- CAD 工具:图形设计和编辑功能
- 性能优化:输入预处理、批量操作、空间索引
- 错误处理:全面的异常处理和验证
- 代码组织:分层架构和模块化设计
- 测试策略:单元测试和集成测试
- 部署建议:配置管理和集成方式
关键要点:
- 根据应用场景选择合适的算法和参数
- 重视输入验证和错误处理
- 采用分层架构提高代码可维护性
- 通过测试确保功能正确性
- 持续优化性能
6.10 最佳实践清单
-
输入处理
- 总是验证输入数据的有效性
- 预处理多边形以提高性能
- 使用合适的缩放系数
-
错误处理
- 使用 try-catch 捕获异常
- 提供有意义的错误消息
- 记录关键操作的日志
-
性能优化
- 对大量数据使用空间索引
- 批量操作使用分治法
- 复用 Clipper 对象
-
代码质量
- 遵循 SOLID 原则
- 编写单元测试
- 添加适当的注释
-
维护性
- 使用配置文件管理参数
- 模块化设计便于扩展
- 保持代码简洁清晰
6.11 进一步学习资源
-
官方资源
- Clipper1 官方文档
- 源代码和示例
-
社区资源
- Stack Overflow
- GitHub 项目
- 技术博客
-
相关技术
- 计算几何学
- GIS 理论
- CAD 技术
-
后续发展
- Clipper2 的新特性
- 其他几何库的对比
- 特定领域的深入应用
6.12 总结
通过本教程的学习,您已经全面掌握了 Clipper1 的使用方法,从基础概念到高级应用,从理论知识到实践技巧。Clipper1 是一个强大而可靠的几何运算库,在正确使用的情况下,可以解决各种复杂的多边形处理问题。
记住以下几点:
- 理解算法原理有助于更好地应用
- 实践是掌握技能的最佳途径
- 性能优化需要根据具体场景调整
- 代码质量和可维护性同样重要
祝您在使用 Clipper1 的项目中取得成功!如果遇到问题,可以参考官方文档、社区资源,或回顾本教程的相关章节。
6.13 练习项目
为了巩固所学知识,建议完成以下综合项目:
-
地图编辑器
- 支持多边形的绘制和编辑
- 实现各种布尔运算
- 提供图层管理功能
-
路径规划系统
- 计算障碍物缓冲区
- 生成可行走区域
- 优化路径计算
-
制造业应用
- CNC 刀具路径生成
- 嵌套排样优化
- 材料利用率分析
-
数据分析工具
- 地理数据的空间分析
- 可视化结果展示
- 批量处理功能
通过这些项目,您将能够综合运用 Clipper1 的各种功能,并积累宝贵的实践经验。

浙公网安备 33010602011771号