using System.Text.Json; using TakeoutSaaS.Application.App.Stores.Dto; using TakeoutSaaS.Application.App.Stores.Services; using TakeoutSaaS.Domain.Stores.Entities; namespace TakeoutSaaS.Infrastructure.App.Services; /// /// 配送范围检测服务实现。 /// public sealed class DeliveryZoneService : IDeliveryZoneService { /// public StoreDeliveryCheckResultDto CheckPointInZones( IReadOnlyList zones, double longitude, double latitude) { // 1. 无配送区域直接返回 if (zones is null || zones.Count == 0) { return new StoreDeliveryCheckResultDto { InRange = false }; } // 2. (空行后) 逐个检测多边形命中 foreach (var zone in zones) { if (!TryReadPolygon(zone.PolygonGeoJson, out var polygon)) { continue; } if (IsPointInPolygon(polygon, longitude, latitude)) { return new StoreDeliveryCheckResultDto { InRange = true, DeliveryZoneId = zone.Id, DeliveryZoneName = zone.ZoneName }; } } // 3. (空行后) 未命中任何区域 return new StoreDeliveryCheckResultDto { InRange = false }; } private static bool TryReadPolygon(string geoJson, out List polygon) { polygon = []; if (string.IsNullOrWhiteSpace(geoJson)) { return false; } try { using var document = JsonDocument.Parse(geoJson); var root = document.RootElement; if (root.ValueKind != JsonValueKind.Object) { return false; } if (!root.TryGetProperty("coordinates", out var coordinatesElement) || coordinatesElement.ValueKind != JsonValueKind.Array) { return false; } if (coordinatesElement.GetArrayLength() == 0) { return false; } var ringElement = coordinatesElement[0]; if (ringElement.ValueKind != JsonValueKind.Array) { return false; } foreach (var pointElement in ringElement.EnumerateArray()) { if (pointElement.ValueKind != JsonValueKind.Array || pointElement.GetArrayLength() < 2) { return false; } if (!pointElement[0].TryGetDouble(out var x) || !pointElement[1].TryGetDouble(out var y)) { return false; } polygon.Add(new Point(x, y)); } if (polygon.Count >= 2 && AreSamePoint(polygon[0], polygon[^1])) { polygon.RemoveAt(polygon.Count - 1); } return polygon.Count >= 3; } catch (JsonException) { return false; } } private static bool IsPointInPolygon(IReadOnlyList polygon, double x, double y) { var inside = false; for (var i = 0; i < polygon.Count; i++) { var j = i == 0 ? polygon.Count - 1 : i - 1; var xi = polygon[i].Longitude; var yi = polygon[i].Latitude; var xj = polygon[j].Longitude; var yj = polygon[j].Latitude; var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi + double.Epsilon) + xi); if (intersect) { inside = !inside; } } return inside; } private static bool AreSamePoint(Point first, Point second) => Math.Abs(first.Longitude - second.Longitude) <= 1e-6 && Math.Abs(first.Latitude - second.Latitude) <= 1e-6; private readonly record struct Point(double Longitude, double Latitude); }