10-高级应用与性能优化

第十章:高级应用与性能优化

10.1 概述

在实际项目中,除了正确使用 geometry-api-net 的 API,还需要考虑性能、可维护性和扩展性。本章将介绍高级使用技巧和性能优化方法。

10.2 架构最佳实践

10.2.1 服务层封装

将几何操作封装为服务,提高代码复用性和可测试性:

public interface IGeometryService
{
    bool IsPointInRegion(Point point, Polygon region);
    double CalculateDistanceMeters(Point p1, Point p2);
    Polygon CreateBuffer(Geometry geometry, double distanceMeters);
    Geometry Simplify(Geometry geometry, double tolerance);
}

public class GeometryService : IGeometryService
{
    private readonly SpatialReference _defaultSR = SpatialReference.Wgs84();
    
    public bool IsPointInRegion(Point point, Polygon region)
    {
        if (point == null || region == null)
            throw new ArgumentNullException();
        
        return GeometryEngine.Contains(region, point);
    }
    
    public double CalculateDistanceMeters(Point p1, Point p2)
    {
        if (p1 == null || p2 == null)
            throw new ArgumentNullException();
        
        return GeometryEngine.GeodesicDistance(p1, p2);
    }
    
    public Polygon CreateBuffer(Geometry geometry, double distanceMeters)
    {
        // 将米转换为度(近似值)
        double distanceDegrees = distanceMeters / 111000.0;
        return GeometryEngine.Buffer(geometry, distanceDegrees);
    }
    
    public Geometry Simplify(Geometry geometry, double tolerance)
    {
        return GeometryEngine.Simplify(geometry, tolerance);
    }
}

// 依赖注入配置
services.AddSingleton<IGeometryService, GeometryService>();

10.2.2 仓储模式

public interface ISpatialRepository<T>
{
    IEnumerable<T> FindInRegion(Polygon region);
    IEnumerable<T> FindNearby(Point location, double radiusMeters);
    T FindNearest(Point location);
}

public class SpatialRepository<T> : ISpatialRepository<T> 
    where T : ISpatialEntity
{
    private readonly List<T> _entities;
    
    public IEnumerable<T> FindInRegion(Polygon region)
    {
        return _entities.Where(e => 
            GeometryEngine.Contains(region, e.Location));
    }
    
    public IEnumerable<T> FindNearby(Point location, double radiusMeters)
    {
        return _entities.Where(e => 
            GeometryEngine.GeodesicDistance(location, e.Location) <= radiusMeters);
    }
    
    public T FindNearest(Point location)
    {
        return _entities
            .OrderBy(e => GeometryEngine.GeodesicDistance(location, e.Location))
            .FirstOrDefault();
    }
}

public interface ISpatialEntity
{
    Point Location { get; }
}

10.2.3 扩展方法

public static class GeometryExtensions
{
    // 链式 API
    public static Geometry Buffer(this Geometry geometry, double distance)
    {
        return GeometryEngine.Buffer(geometry, distance);
    }
    
    public static Geometry Simplify(this Geometry geometry, double tolerance)
    {
        return GeometryEngine.Simplify(geometry, tolerance);
    }
    
    public static string ToWkt(this Geometry geometry)
    {
        return GeometryEngine.GeometryToWkt(geometry);
    }
    
    public static string ToGeoJson(this Geometry geometry)
    {
        return GeometryEngine.GeometryToGeoJson(geometry);
    }
    
    // 便捷方法
    public static bool IsWithin(this Point point, Polygon polygon)
    {
        return GeometryEngine.Contains(polygon, point);
    }
    
    public static double DistanceToMeters(this Point p1, Point p2)
    {
        return GeometryEngine.GeodesicDistance(p1, p2);
    }
    
    // 验证方法
    public static bool IsValidPolygon(this Polygon polygon)
    {
        if (polygon.IsEmpty || polygon.RingCount == 0)
            return false;
        
        var ring = polygon.GetRing(0);
        if (ring.Count < 4)  // 至少 3 个点 + 闭合点
            return false;
        
        // 检查闭合
        var first = ring[0];
        var last = ring[ring.Count - 1];
        return first.Equals(last, 0.0001);
    }
}

// 使用
var simplified = polygon
    .Simplify(0.001)
    .Buffer(0.01);

string wkt = simplified.ToWkt();

10.3 性能优化技巧

10.3.1 包络矩形预过滤

在进行复杂空间操作前,先使用包络矩形进行快速过滤:

public class OptimizedSpatialQuery
{
    private List<Polygon> _regions;
    private Dictionary<Polygon, Envelope> _envelopeCache;
    
    public OptimizedSpatialQuery(List<Polygon> regions)
    {
        _regions = regions;
        // 预计算所有包络矩形
        _envelopeCache = regions.ToDictionary(
            r => r, 
            r => r.GetEnvelope());
    }
    
    public List<Polygon> FindContaining(Point point)
    {
        var results = new List<Polygon>();
        
        foreach (var region in _regions)
        {
            // 快速过滤:检查点是否在包络内
            if (!_envelopeCache[region].Contains(point))
                continue;
            
            // 精确检查
            if (GeometryEngine.Contains(region, point))
            {
                results.Add(region);
            }
        }
        
        return results;
    }
    
    public List<Polygon> FindIntersecting(Polygon query)
    {
        var queryEnvelope = query.GetEnvelope();
        
        return _regions
            .Where(r => _envelopeCache[r].Intersects(queryEnvelope))  // 快速过滤
            .Where(r => GeometryEngine.Intersects(r, query))          // 精确检查
            .ToList();
    }
}

10.3.2 对象复用

避免频繁创建几何对象:

public class GeometryPool
{
    private readonly Stack<Point> _pointPool = new();
    private readonly Stack<Envelope> _envelopePool = new();
    
    public Point RentPoint(double x, double y)
    {
        if (_pointPool.TryPop(out var point))
        {
            point.X = x;
            point.Y = y;
            point.Z = null;
            point.M = null;
            return point;
        }
        return new Point(x, y);
    }
    
    public void ReturnPoint(Point point)
    {
        _pointPool.Push(point);
    }
    
    public Envelope RentEnvelope(double xMin, double yMin, double xMax, double yMax)
    {
        if (_envelopePool.TryPop(out var envelope))
        {
            envelope.XMin = xMin;
            envelope.YMin = yMin;
            envelope.XMax = xMax;
            envelope.YMax = yMax;
            return envelope;
        }
        return new Envelope(xMin, yMin, xMax, yMax);
    }
    
    public void ReturnEnvelope(Envelope envelope)
    {
        _envelopePool.Push(envelope);
    }
}

// 使用
var pool = new GeometryPool();
var point = pool.RentPoint(10, 20);
// 使用 point...
pool.ReturnPoint(point);

10.3.3 批量操作

public class BatchProcessor
{
    public List<bool> BatchContainsTest(Polygon region, List<Point> points)
    {
        var envelope = region.GetEnvelope();
        var results = new List<bool>(points.Count);
        
        foreach (var point in points)
        {
            // 快速过滤
            if (!envelope.Contains(point))
            {
                results.Add(false);
                continue;
            }
            
            results.Add(GeometryEngine.Contains(region, point));
        }
        
        return results;
    }
    
    public List<double> BatchDistanceCalculation(Point origin, List<Point> targets)
    {
        // 使用并行处理
        return targets
            .AsParallel()
            .AsOrdered()
            .Select(t => GeometryEngine.GeodesicDistance(origin, t))
            .ToList();
    }
}

10.3.4 缓存策略

public class GeometryCache
{
    private readonly Dictionary<string, Geometry> _wktCache = new();
    private readonly Dictionary<Geometry, string> _reverseCache = new();
    private readonly Dictionary<Geometry, double> _areaCache = new();
    private readonly Dictionary<Geometry, Envelope> _envelopeCache = new();
    
    public Geometry ParseWkt(string wkt)
    {
        if (_wktCache.TryGetValue(wkt, out var cached))
            return cached;
        
        var geometry = GeometryEngine.GeometryFromWkt(wkt);
        _wktCache[wkt] = geometry;
        return geometry;
    }
    
    public double GetArea(Geometry geometry)
    {
        if (_areaCache.TryGetValue(geometry, out var area))
            return area;
        
        area = GeometryEngine.Area(geometry);
        _areaCache[geometry] = area;
        return area;
    }
    
    public Envelope GetEnvelope(Geometry geometry)
    {
        if (_envelopeCache.TryGetValue(geometry, out var envelope))
            return envelope;
        
        envelope = geometry.GetEnvelope();
        _envelopeCache[geometry] = envelope;
        return envelope;
    }
    
    public void Clear()
    {
        _wktCache.Clear();
        _reverseCache.Clear();
        _areaCache.Clear();
        _envelopeCache.Clear();
    }
}

10.4 错误处理

10.4.1 验证输入

public static class GeometryValidator
{
    public static ValidationResult Validate(Geometry geometry)
    {
        if (geometry == null)
            return ValidationResult.Error("几何对象为空");
        
        if (geometry.IsEmpty)
            return ValidationResult.Warning("几何对象为空几何");
        
        if (geometry is Polygon polygon)
        {
            return ValidatePolygon(polygon);
        }
        
        if (geometry is Polyline polyline)
        {
            return ValidatePolyline(polyline);
        }
        
        return ValidationResult.Success();
    }
    
    private static ValidationResult ValidatePolygon(Polygon polygon)
    {
        if (polygon.RingCount == 0)
            return ValidationResult.Error("多边形没有环");
        
        var ring = polygon.GetRing(0);
        if (ring.Count < 4)
            return ValidationResult.Error("多边形环至少需要 4 个点(含闭合点)");
        
        // 检查闭合
        var first = ring[0];
        var last = ring[ring.Count - 1];
        if (!first.Equals(last, 0.0001))
            return ValidationResult.Warning("多边形环未闭合");
        
        // 检查面积
        if (polygon.Area < 0.00000001)
            return ValidationResult.Warning("多边形面积过小");
        
        return ValidationResult.Success();
    }
    
    private static ValidationResult ValidatePolyline(Polyline polyline)
    {
        if (polyline.PathCount == 0)
            return ValidationResult.Error("折线没有路径");
        
        var path = polyline.GetPath(0);
        if (path.Count < 2)
            return ValidationResult.Error("折线路径至少需要 2 个点");
        
        if (polyline.Length < 0.00000001)
            return ValidationResult.Warning("折线长度过短");
        
        return ValidationResult.Success();
    }
}

public class ValidationResult
{
    public bool IsValid { get; }
    public string Message { get; }
    public ValidationLevel Level { get; }
    
    public static ValidationResult Success() => new(true, null, ValidationLevel.None);
    public static ValidationResult Warning(string msg) => new(true, msg, ValidationLevel.Warning);
    public static ValidationResult Error(string msg) => new(false, msg, ValidationLevel.Error);
    
    private ValidationResult(bool isValid, string message, ValidationLevel level)
    {
        IsValid = isValid;
        Message = message;
        Level = level;
    }
}

public enum ValidationLevel { None, Warning, Error }

10.4.2 安全包装

public static class SafeGeometryEngine
{
    public static Result<bool> TryContains(Geometry g1, Geometry g2)
    {
        try
        {
            if (g1 == null || g2 == null)
                return Result<bool>.Failure("输入几何对象为空");
            
            return Result<bool>.Ok(GeometryEngine.Contains(g1, g2));
        }
        catch (Exception ex)
        {
            return Result<bool>.Failure($"Contains 操作失败: {ex.Message}");
        }
    }
    
    public static Result<Geometry> TryParseWkt(string wkt)
    {
        try
        {
            if (string.IsNullOrWhiteSpace(wkt))
                return Result<Geometry>.Failure("WKT 字符串为空");
            
            var geometry = GeometryEngine.GeometryFromWkt(wkt);
            return Result<Geometry>.Ok(geometry);
        }
        catch (FormatException ex)
        {
            return Result<Geometry>.Failure($"WKT 格式错误: {ex.Message}");
        }
        catch (Exception ex)
        {
            return Result<Geometry>.Failure($"解析失败: {ex.Message}");
        }
    }
}

public class Result<T>
{
    public bool IsSuccess { get; }
    public T Value { get; }
    public string Error { get; }
    
    public static Result<T> Ok(T value) => new(true, value, null);
    public static Result<T> Failure(string error) => new(false, default, error);
    
    private Result(bool success, T value, string error)
    {
        IsSuccess = success;
        Value = value;
        Error = error;
    }
}

10.5 测试策略

10.5.1 单元测试

using Xunit;

public class GeometryServiceTests
{
    private readonly IGeometryService _service;
    
    public GeometryServiceTests()
    {
        _service = new GeometryService();
    }
    
    [Fact]
    public void IsPointInRegion_PointInside_ReturnsTrue()
    {
        // Arrange
        var polygon = CreateSquarePolygon(0, 0, 100, 100);
        var point = new Point(50, 50);
        
        // Act
        bool result = _service.IsPointInRegion(point, polygon);
        
        // Assert
        Assert.True(result);
    }
    
    [Fact]
    public void IsPointInRegion_PointOutside_ReturnsFalse()
    {
        var polygon = CreateSquarePolygon(0, 0, 100, 100);
        var point = new Point(150, 150);
        
        bool result = _service.IsPointInRegion(point, polygon);
        
        Assert.False(result);
    }
    
    [Theory]
    [InlineData(0, 0, 3, 4, 5)]        // 3-4-5 三角形
    [InlineData(0, 0, 0, 10, 10)]      // 垂直线
    [InlineData(0, 0, 10, 0, 10)]      // 水平线
    public void Distance_KnownValues_ReturnsExpected(
        double x1, double y1, double x2, double y2, double expected)
    {
        var p1 = new Point(x1, y1);
        var p2 = new Point(x2, y2);
        
        double distance = GeometryEngine.Distance(p1, p2);
        
        Assert.Equal(expected, distance, precision: 6);
    }
    
    [Fact]
    public void WktRoundTrip_PreservesGeometry()
    {
        var original = CreateSquarePolygon(0, 0, 100, 100);
        
        string wkt = GeometryEngine.GeometryToWkt(original);
        var parsed = GeometryEngine.GeometryFromWkt(wkt);
        
        Assert.True(GeometryEngine.Equals(original, parsed));
    }
    
    private Polygon CreateSquarePolygon(double x1, double y1, double x2, double y2)
    {
        var polygon = new Polygon();
        polygon.AddRing(new List<Point>
        {
            new Point(x1, y1),
            new Point(x2, y1),
            new Point(x2, y2),
            new Point(x1, y2),
            new Point(x1, y1)
        });
        return polygon;
    }
}

10.5.2 性能测试

using BenchmarkDotNet.Attributes;

[MemoryDiagnoser]
public class GeometryBenchmarks
{
    private Polygon _polygon;
    private List<Point> _testPoints;
    
    [GlobalSetup]
    public void Setup()
    {
        _polygon = CreateComplexPolygon(1000);  // 1000 个顶点
        _testPoints = GenerateRandomPoints(10000);
    }
    
    [Benchmark]
    public int ContainsTest_WithPrefilter()
    {
        var envelope = _polygon.GetEnvelope();
        int count = 0;
        
        foreach (var point in _testPoints)
        {
            if (envelope.Contains(point) && 
                GeometryEngine.Contains(_polygon, point))
            {
                count++;
            }
        }
        
        return count;
    }
    
    [Benchmark]
    public int ContainsTest_WithoutPrefilter()
    {
        int count = 0;
        
        foreach (var point in _testPoints)
        {
            if (GeometryEngine.Contains(_polygon, point))
            {
                count++;
            }
        }
        
        return count;
    }
}

10.6 调试技巧

10.6.1 几何可视化

public static class GeometryDebugger
{
    public static void PrintGeometry(Geometry geometry, string name = "Geometry")
    {
        Console.WriteLine($"=== {name} ===");
        Console.WriteLine($"Type: {geometry.Type}");
        Console.WriteLine($"IsEmpty: {geometry.IsEmpty}");
        Console.WriteLine($"Dimension: {geometry.Dimension}");
        
        var envelope = geometry.GetEnvelope();
        Console.WriteLine($"Envelope: ({envelope.XMin:F4}, {envelope.YMin:F4}) - ({envelope.XMax:F4}, {envelope.YMax:F4})");
        Console.WriteLine($"WKT: {GeometryEngine.GeometryToWkt(geometry)}");
        
        if (geometry is Polygon polygon)
        {
            Console.WriteLine($"RingCount: {polygon.RingCount}");
            Console.WriteLine($"Area: {polygon.Area:F4}");
        }
        
        if (geometry is Polyline polyline)
        {
            Console.WriteLine($"PathCount: {polyline.PathCount}");
            Console.WriteLine($"Length: {polyline.Length:F4}");
        }
        
        Console.WriteLine();
    }
    
    public static string ToSvg(Geometry geometry, double width = 200, double height = 200)
    {
        var envelope = geometry.GetEnvelope();
        double scaleX = width / envelope.Width;
        double scaleY = height / envelope.Height;
        double scale = Math.Min(scaleX, scaleY);
        
        var sb = new StringBuilder();
        sb.AppendLine($"<svg width=\"{width}\" height=\"{height}\" xmlns=\"http://www.w3.org/2000/svg\">");
        
        if (geometry is Polygon polygon)
        {
            foreach (var ring in polygon.GetRings())
            {
                var points = string.Join(" ", ring.Select(p => 
                    $"{(p.X - envelope.XMin) * scale:F2},{height - (p.Y - envelope.YMin) * scale:F2}"));
                sb.AppendLine($"  <polygon points=\"{points}\" fill=\"blue\" fill-opacity=\"0.3\" stroke=\"blue\" stroke-width=\"1\"/>");
            }
        }
        
        sb.AppendLine("</svg>");
        return sb.ToString();
    }
}

10.7 小结

本章介绍了 geometry-api-net 的高级应用和性能优化:

架构最佳实践

  • 服务层封装
  • 仓储模式
  • 扩展方法

性能优化

  • 包络矩形预过滤
  • 对象复用
  • 批量操作
  • 缓存策略

错误处理

  • 输入验证
  • 安全包装

测试策略

  • 单元测试
  • 性能测试

在下一章中,我们将通过实战案例,展示如何在实际项目中综合运用这些技术。

posted @ 2025-12-03 16:29  我才是银古  阅读(5)  评论(0)    收藏  举报