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);
}