122 lines
3.9 KiB
C#
122 lines
3.9 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 配送范围检测服务实现。
|
|
/// </summary>
|
|
public sealed class DeliveryZoneService : IDeliveryZoneService
|
|
{
|
|
/// <inheritdoc />
|
|
public StoreDeliveryCheckResultDto CheckPointInZones(
|
|
IReadOnlyList<StoreDeliveryZone> 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<Point> 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<Point> 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);
|
|
}
|