完成门店管理后端接口与任务
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user