11-实战案例
第十一章:实战案例
11.1 概述
本章通过三个完整的实战案例,展示如何在实际项目中综合运用 geometry-api-net 的各项功能。
11.2 案例一:位置服务系统
11.2.1 需求描述
构建一个位置服务系统,实现以下功能:
- 查找用户附近的商店
- 判断用户是否在配送区域内
- 计算配送距离和预估时间
- 管理地理围栏告警
11.2.2 核心模型
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
using Esri.Geometry.Core.SpatialReference;
// 商店实体
public class Store
{
public int Id { get; set; }
public string Name { get; set; }
public Point Location { get; set; }
public Polygon DeliveryZone { get; set; }
public bool IsOpen { get; set; }
}
// 用户位置
public class UserLocation
{
public string UserId { get; set; }
public Point Position { get; set; }
public DateTime Timestamp { get; set; }
}
// 配送订单
public class DeliveryOrder
{
public int OrderId { get; set; }
public Store Store { get; set; }
public Point DeliveryAddress { get; set; }
public double DistanceMeters { get; set; }
public int EstimatedMinutes { get; set; }
}
11.2.3 位置服务实现
public interface ILocationService
{
List<Store> FindNearbyStores(Point userLocation, double radiusMeters);
Store FindNearestStore(Point userLocation);
bool IsInDeliveryZone(Store store, Point address);
DeliveryOrder CalculateDelivery(Store store, Point address);
}
public class LocationService : ILocationService
{
private readonly List<Store> _stores;
private const double AVERAGE_SPEED_MPS = 10.0; // 平均速度 10 米/秒
public LocationService(List<Store> stores)
{
_stores = stores ?? throw new ArgumentNullException(nameof(stores));
}
public List<Store> FindNearbyStores(Point userLocation, double radiusMeters)
{
var nearbyStores = new List<Store>();
foreach (var store in _stores.Where(s => s.IsOpen))
{
double distance = GeometryEngine.GeodesicDistance(userLocation, store.Location);
if (distance <= radiusMeters)
{
nearbyStores.Add(store);
}
}
// 按距离排序
return nearbyStores
.OrderBy(s => GeometryEngine.GeodesicDistance(userLocation, s.Location))
.ToList();
}
public Store FindNearestStore(Point userLocation)
{
Store nearest = null;
double minDistance = double.MaxValue;
foreach (var store in _stores.Where(s => s.IsOpen))
{
double distance = GeometryEngine.GeodesicDistance(userLocation, store.Location);
if (distance < minDistance)
{
minDistance = distance;
nearest = store;
}
}
return nearest;
}
public bool IsInDeliveryZone(Store store, Point address)
{
if (store?.DeliveryZone == null || address == null)
return false;
return GeometryEngine.Contains(store.DeliveryZone, address);
}
public DeliveryOrder CalculateDelivery(Store store, Point address)
{
if (!IsInDeliveryZone(store, address))
{
throw new InvalidOperationException("地址不在配送范围内");
}
double distanceMeters = GeometryEngine.GeodesicDistance(store.Location, address);
int estimatedMinutes = (int)Math.Ceiling(distanceMeters / AVERAGE_SPEED_MPS / 60);
return new DeliveryOrder
{
Store = store,
DeliveryAddress = address,
DistanceMeters = distanceMeters,
EstimatedMinutes = estimatedMinutes
};
}
}
11.2.4 地理围栏服务
public interface IGeofenceService
{
void AddGeofence(string id, Polygon boundary);
void RemoveGeofence(string id);
List<string> CheckPosition(Point position);
event EventHandler<GeofenceEventArgs> OnEnter;
event EventHandler<GeofenceEventArgs> OnExit;
}
public class GeofenceEventArgs : EventArgs
{
public string GeofenceId { get; set; }
public string UserId { get; set; }
public Point Position { get; set; }
public bool IsEntry { get; set; }
}
public class GeofenceService : IGeofenceService
{
private readonly Dictionary<string, Polygon> _geofences = new();
private readonly Dictionary<string, HashSet<string>> _userInGeofence = new();
public event EventHandler<GeofenceEventArgs> OnEnter;
public event EventHandler<GeofenceEventArgs> OnExit;
public void AddGeofence(string id, Polygon boundary)
{
_geofences[id] = boundary;
_userInGeofence[id] = new HashSet<string>();
}
public void RemoveGeofence(string id)
{
_geofences.Remove(id);
_userInGeofence.Remove(id);
}
public List<string> CheckPosition(Point position)
{
return _geofences
.Where(kvp => GeometryEngine.Contains(kvp.Value, position))
.Select(kvp => kvp.Key)
.ToList();
}
public void UpdateUserPosition(string userId, Point position)
{
foreach (var kvp in _geofences)
{
var geofenceId = kvp.Key;
var boundary = kvp.Value;
var usersInside = _userInGeofence[geofenceId];
bool isInside = GeometryEngine.Contains(boundary, position);
bool wasInside = usersInside.Contains(userId);
if (isInside && !wasInside)
{
// 进入围栏
usersInside.Add(userId);
OnEnter?.Invoke(this, new GeofenceEventArgs
{
GeofenceId = geofenceId,
UserId = userId,
Position = position,
IsEntry = true
});
}
else if (!isInside && wasInside)
{
// 离开围栏
usersInside.Remove(userId);
OnExit?.Invoke(this, new GeofenceEventArgs
{
GeofenceId = geofenceId,
UserId = userId,
Position = position,
IsEntry = false
});
}
}
}
}
11.2.5 使用示例
// 初始化商店数据
var stores = new List<Store>
{
new Store
{
Id = 1,
Name = "总店",
Location = new Point(116.4074, 39.9042),
DeliveryZone = CreateCirclePolygon(116.4074, 39.9042, 0.02),
IsOpen = true
},
new Store
{
Id = 2,
Name = "分店A",
Location = new Point(116.42, 39.91),
DeliveryZone = CreateCirclePolygon(116.42, 39.91, 0.015),
IsOpen = true
}
};
var locationService = new LocationService(stores);
// 查找附近商店
var userLocation = new Point(116.41, 39.905);
var nearbyStores = locationService.FindNearbyStores(userLocation, 2000);
Console.WriteLine($"附近 2 公里内有 {nearbyStores.Count} 家商店");
// 计算配送
var nearestStore = locationService.FindNearestStore(userLocation);
if (locationService.IsInDeliveryZone(nearestStore, userLocation))
{
var order = locationService.CalculateDelivery(nearestStore, userLocation);
Console.WriteLine($"配送距离:{order.DistanceMeters:F0} 米");
Console.WriteLine($"预计时间:{order.EstimatedMinutes} 分钟");
}
// 设置地理围栏
var geofenceService = new GeofenceService();
geofenceService.OnEnter += (s, e) => Console.WriteLine($"用户 {e.UserId} 进入 {e.GeofenceId}");
geofenceService.OnExit += (s, e) => Console.WriteLine($"用户 {e.UserId} 离开 {e.GeofenceId}");
geofenceService.AddGeofence("office", CreateCirclePolygon(116.4, 39.9, 0.005));
geofenceService.UpdateUserPosition("user1", new Point(116.4, 39.9));
// 辅助方法:创建圆形多边形(近似)
static Polygon CreateCirclePolygon(double centerX, double centerY, double radius)
{
var polygon = new Polygon();
var points = new List<Point>();
const int segments = 32;
for (int i = 0; i <= segments; i++)
{
double angle = 2 * Math.PI * i / segments;
points.Add(new Point(
centerX + radius * Math.Cos(angle),
centerY + radius * Math.Sin(angle)
));
}
polygon.AddRing(points);
return polygon;
}
11.3 案例二:轨迹分析系统
11.3.1 需求描述
构建一个 GPS 轨迹分析系统,实现以下功能:
- 轨迹数据的简化和平滑
- 计算行程距离和时间
- 识别停留点
- 轨迹与道路的匹配
11.3.2 轨迹分析服务
public class TrackPoint
{
public Point Location { get; set; }
public DateTime Timestamp { get; set; }
public double? Speed { get; set; } // 米/秒
}
public class TrackSegment
{
public List<TrackPoint> Points { get; set; } = new();
public double TotalDistanceMeters { get; set; }
public TimeSpan Duration { get; set; }
public double AverageSpeedMps { get; set; }
}
public class StayPoint
{
public Point Location { get; set; }
public DateTime ArrivalTime { get; set; }
public DateTime DepartureTime { get; set; }
public TimeSpan Duration { get; set; }
}
public interface ITrackAnalysisService
{
Polyline SimplifyTrack(List<TrackPoint> points, double tolerance);
TrackSegment AnalyzeSegment(List<TrackPoint> points);
List<StayPoint> DetectStayPoints(List<TrackPoint> points, double radiusMeters, TimeSpan minDuration);
}
public class TrackAnalysisService : ITrackAnalysisService
{
public Polyline SimplifyTrack(List<TrackPoint> points, double tolerance)
{
// 转换为 Polyline
var polyline = new Polyline();
polyline.AddPath(points.Select(p => p.Location).ToList());
// 使用 Douglas-Peucker 算法简化
var simplified = GeometryEngine.Simplify(polyline, tolerance);
return simplified as Polyline ?? polyline;
}
public TrackSegment AnalyzeSegment(List<TrackPoint> points)
{
if (points == null || points.Count < 2)
return new TrackSegment();
double totalDistance = 0;
for (int i = 1; i < points.Count; i++)
{
totalDistance += GeometryEngine.GeodesicDistance(
points[i - 1].Location,
points[i].Location);
}
var duration = points.Last().Timestamp - points.First().Timestamp;
return new TrackSegment
{
Points = points,
TotalDistanceMeters = totalDistance,
Duration = duration,
AverageSpeedMps = duration.TotalSeconds > 0
? totalDistance / duration.TotalSeconds
: 0
};
}
public List<StayPoint> DetectStayPoints(
List<TrackPoint> points,
double radiusMeters,
TimeSpan minDuration)
{
var stayPoints = new List<StayPoint>();
int i = 0;
while (i < points.Count)
{
int j = i + 1;
var center = points[i].Location;
// 查找在半径范围内的连续点
while (j < points.Count)
{
double distance = GeometryEngine.GeodesicDistance(center, points[j].Location);
if (distance > radiusMeters)
break;
j++;
}
// 检查停留时间
var duration = points[j - 1].Timestamp - points[i].Timestamp;
if (duration >= minDuration)
{
// 计算停留点的平均位置
double avgX = 0, avgY = 0;
for (int k = i; k < j; k++)
{
avgX += points[k].Location.X;
avgY += points[k].Location.Y;
}
avgX /= (j - i);
avgY /= (j - i);
stayPoints.Add(new StayPoint
{
Location = new Point(avgX, avgY),
ArrivalTime = points[i].Timestamp,
DepartureTime = points[j - 1].Timestamp,
Duration = duration
});
}
i = j;
}
return stayPoints;
}
}
11.3.3 使用示例
// 模拟 GPS 轨迹数据
var trackPoints = new List<TrackPoint>
{
new TrackPoint { Location = new Point(116.40, 39.90), Timestamp = DateTime.Parse("2024-01-01 09:00:00") },
new TrackPoint { Location = new Point(116.41, 39.91), Timestamp = DateTime.Parse("2024-01-01 09:10:00") },
new TrackPoint { Location = new Point(116.42, 39.92), Timestamp = DateTime.Parse("2024-01-01 09:20:00") },
// 停留点
new TrackPoint { Location = new Point(116.42, 39.92), Timestamp = DateTime.Parse("2024-01-01 09:30:00") },
new TrackPoint { Location = new Point(116.421, 39.921), Timestamp = DateTime.Parse("2024-01-01 09:40:00") },
new TrackPoint { Location = new Point(116.42, 39.92), Timestamp = DateTime.Parse("2024-01-01 09:50:00") },
// 继续移动
new TrackPoint { Location = new Point(116.45, 39.95), Timestamp = DateTime.Parse("2024-01-01 10:00:00") }
};
var service = new TrackAnalysisService();
// 分析轨迹
var segment = service.AnalyzeSegment(trackPoints);
Console.WriteLine($"总距离:{segment.TotalDistanceMeters:F0} 米");
Console.WriteLine($"总时长:{segment.Duration.TotalMinutes:F0} 分钟");
Console.WriteLine($"平均速度:{segment.AverageSpeedMps * 3.6:F1} 公里/小时");
// 检测停留点
var stayPoints = service.DetectStayPoints(trackPoints, 100, TimeSpan.FromMinutes(15));
Console.WriteLine($"\n发现 {stayPoints.Count} 个停留点:");
foreach (var sp in stayPoints)
{
Console.WriteLine($" 位置:({sp.Location.X:F4}, {sp.Location.Y:F4})");
Console.WriteLine($" 停留时间:{sp.Duration.TotalMinutes:F0} 分钟");
}
// 简化轨迹
var simplified = service.SimplifyTrack(trackPoints, 0.001);
Console.WriteLine($"\n原始点数:{trackPoints.Count}");
Console.WriteLine($"简化后点数:{simplified.GetPath(0).Count}");
11.4 案例三:区域分析系统
11.4.1 需求描述
构建一个区域分析系统,实现以下功能:
- 区域的叠加分析
- 区域统计
- 热力图数据生成
- 区域边界管理
11.4.2 区域分析服务
public class Region
{
public string Id { get; set; }
public string Name { get; set; }
public Polygon Boundary { get; set; }
public Dictionary<string, object> Properties { get; set; } = new();
}
public class OverlapResult
{
public Region Region1 { get; set; }
public Region Region2 { get; set; }
public Geometry OverlapArea { get; set; }
public double OverlapRatio { get; set; }
}
public interface IRegionAnalysisService
{
List<OverlapResult> FindOverlaps(List<Region> regions);
Region MergeRegions(List<Region> regions, string newId, string newName);
Dictionary<string, int> PointsPerRegion(List<Region> regions, List<Point> points);
double[,] GenerateHeatmapGrid(List<Point> points, Envelope bounds, int gridSizeX, int gridSizeY);
}
public class RegionAnalysisService : IRegionAnalysisService
{
public List<OverlapResult> FindOverlaps(List<Region> regions)
{
var results = new List<OverlapResult>();
for (int i = 0; i < regions.Count; i++)
{
for (int j = i + 1; j < regions.Count; j++)
{
// 快速过滤
var env1 = regions[i].Boundary.GetEnvelope();
var env2 = regions[j].Boundary.GetEnvelope();
if (!env1.Intersects(env2))
continue;
// 精确检查
if (GeometryEngine.Intersects(regions[i].Boundary, regions[j].Boundary))
{
var overlap = GeometryEngine.Intersection(
regions[i].Boundary,
regions[j].Boundary);
if (!overlap.IsEmpty)
{
double overlapArea = GeometryEngine.Area(overlap);
double totalArea = regions[i].Boundary.Area + regions[j].Boundary.Area;
results.Add(new OverlapResult
{
Region1 = regions[i],
Region2 = regions[j],
OverlapArea = overlap,
OverlapRatio = overlapArea / (totalArea - overlapArea)
});
}
}
}
}
return results;
}
public Region MergeRegions(List<Region> regions, string newId, string newName)
{
if (regions == null || regions.Count == 0)
throw new ArgumentException("No regions to merge");
Geometry merged = regions[0].Boundary;
for (int i = 1; i < regions.Count; i++)
{
merged = GeometryEngine.Union(merged, regions[i].Boundary);
}
// 将结果转换为 Polygon
var envelope = merged.GetEnvelope();
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
new Point(envelope.XMin, envelope.YMin),
new Point(envelope.XMax, envelope.YMin),
new Point(envelope.XMax, envelope.YMax),
new Point(envelope.XMin, envelope.YMax),
new Point(envelope.XMin, envelope.YMin)
});
return new Region
{
Id = newId,
Name = newName,
Boundary = polygon
};
}
public Dictionary<string, int> PointsPerRegion(List<Region> regions, List<Point> points)
{
var result = new Dictionary<string, int>();
// 预计算包络矩形
var envelopes = regions.ToDictionary(r => r.Id, r => r.Boundary.GetEnvelope());
foreach (var region in regions)
{
result[region.Id] = 0;
}
foreach (var point in points)
{
foreach (var region in regions)
{
// 快速过滤
if (!envelopes[region.Id].Contains(point))
continue;
// 精确检查
if (GeometryEngine.Contains(region.Boundary, point))
{
result[region.Id]++;
break; // 假设点只能在一个区域内
}
}
}
return result;
}
public double[,] GenerateHeatmapGrid(
List<Point> points,
Envelope bounds,
int gridSizeX,
int gridSizeY)
{
var grid = new double[gridSizeY, gridSizeX];
double cellWidth = bounds.Width / gridSizeX;
double cellHeight = bounds.Height / gridSizeY;
foreach (var point in points)
{
if (!bounds.Contains(point))
continue;
int cellX = (int)((point.X - bounds.XMin) / cellWidth);
int cellY = (int)((point.Y - bounds.YMin) / cellHeight);
cellX = Math.Clamp(cellX, 0, gridSizeX - 1);
cellY = Math.Clamp(cellY, 0, gridSizeY - 1);
grid[cellY, cellX]++;
}
// 归一化
double maxValue = 0;
for (int y = 0; y < gridSizeY; y++)
{
for (int x = 0; x < gridSizeX; x++)
{
maxValue = Math.Max(maxValue, grid[y, x]);
}
}
if (maxValue > 0)
{
for (int y = 0; y < gridSizeY; y++)
{
for (int x = 0; x < gridSizeX; x++)
{
grid[y, x] /= maxValue;
}
}
}
return grid;
}
}
11.4.3 使用示例
// 创建区域
var regions = new List<Region>
{
new Region
{
Id = "A",
Name = "区域 A",
Boundary = CreateRectangle(0, 0, 50, 50)
},
new Region
{
Id = "B",
Name = "区域 B",
Boundary = CreateRectangle(40, 40, 100, 100)
},
new Region
{
Id = "C",
Name = "区域 C",
Boundary = CreateRectangle(80, 0, 120, 40)
}
};
var service = new RegionAnalysisService();
// 查找重叠区域
var overlaps = service.FindOverlaps(regions);
Console.WriteLine($"发现 {overlaps.Count} 个重叠区域:");
foreach (var overlap in overlaps)
{
Console.WriteLine($" {overlap.Region1.Name} 与 {overlap.Region2.Name},重叠比例:{overlap.OverlapRatio:P2}");
}
// 随机生成一些点
var random = new Random(42);
var points = Enumerable.Range(0, 1000)
.Select(_ => new Point(random.NextDouble() * 120, random.NextDouble() * 100))
.ToList();
// 统计每个区域的点数
var pointCounts = service.PointsPerRegion(regions, points);
Console.WriteLine("\n各区域点数统计:");
foreach (var kvp in pointCounts)
{
Console.WriteLine($" {kvp.Key}: {kvp.Value} 个点");
}
// 生成热力图数据
var bounds = new Envelope(0, 0, 120, 100);
var heatmap = service.GenerateHeatmapGrid(points, bounds, 12, 10);
Console.WriteLine("\n热力图数据(10x12 网格):");
for (int y = 9; y >= 0; y--)
{
for (int x = 0; x < 12; x++)
{
char c = heatmap[y, x] switch
{
>= 0.8 => '█',
>= 0.6 => '▓',
>= 0.4 => '▒',
>= 0.2 => '░',
_ => ' '
};
Console.Write(c);
}
Console.WriteLine();
}
static Polygon CreateRectangle(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;
}
11.5 小结
本章通过三个实战案例展示了 geometry-api-net 在实际项目中的应用:
- 位置服务系统:商店查找、配送区域判断、地理围栏
- 轨迹分析系统:轨迹简化、距离计算、停留点检测
- 区域分析系统:区域叠加、点统计、热力图生成
这些案例综合运用了:
- 几何对象的创建和操作
- 空间关系测试(Contains、Intersects)
- 几何运算(Simplify、Buffer、Union、Intersection)
- 大地测量计算(GeodesicDistance)
- 邻近分析(GetNearestCoordinate)
通过这些实践,您应该能够更好地理解如何在实际项目中应用 geometry-api-net。

浙公网安备 33010602011771号