mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-04-22 22:06:39 +00:00
284 lines
11 KiB
C#
284 lines
11 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using SatelliteProvider.Common.DTO;
|
|
using SatelliteProvider.Common.Interfaces;
|
|
using SatelliteProvider.Common.Utils;
|
|
using SatelliteProvider.DataAccess.Models;
|
|
using SatelliteProvider.DataAccess.Repositories;
|
|
|
|
namespace SatelliteProvider.Services;
|
|
|
|
public class RouteService : IRouteService
|
|
{
|
|
private readonly IRouteRepository _routeRepository;
|
|
private readonly IRegionService _regionService;
|
|
private readonly ILogger<RouteService> _logger;
|
|
private const double MAX_POINT_SPACING_METERS = 200.0;
|
|
|
|
public RouteService(
|
|
IRouteRepository routeRepository,
|
|
IRegionService regionService,
|
|
ILogger<RouteService> logger)
|
|
{
|
|
_routeRepository = routeRepository;
|
|
_regionService = regionService;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<RouteResponse> CreateRouteAsync(CreateRouteRequest request)
|
|
{
|
|
if (request.Points.Count < 2)
|
|
{
|
|
throw new ArgumentException("Route must have at least 2 points");
|
|
}
|
|
|
|
if (request.RegionSizeMeters < 100 || request.RegionSizeMeters > 10000)
|
|
{
|
|
throw new ArgumentException("Region size must be between 100 and 10000 meters");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(request.Name))
|
|
{
|
|
throw new ArgumentException("Route name is required");
|
|
}
|
|
|
|
|
|
var allPoints = new List<RoutePointDto>();
|
|
var totalDistance = 0.0;
|
|
var sequenceNumber = 0;
|
|
|
|
for (int segmentIndex = 0; segmentIndex < request.Points.Count; segmentIndex++)
|
|
{
|
|
var currentPoint = request.Points[segmentIndex];
|
|
var isStart = segmentIndex == 0;
|
|
var isEnd = segmentIndex == request.Points.Count - 1;
|
|
|
|
var geoPoint = new GeoPoint(currentPoint.Latitude, currentPoint.Longitude);
|
|
|
|
double? distanceFromPrevious = null;
|
|
if (allPoints.Count > 0)
|
|
{
|
|
var lastAddedPoint = allPoints[^1];
|
|
var prevGeoPoint = new GeoPoint(lastAddedPoint.Latitude, lastAddedPoint.Longitude);
|
|
distanceFromPrevious = GeoUtils.CalculateDistance(prevGeoPoint, geoPoint);
|
|
totalDistance += distanceFromPrevious.Value;
|
|
}
|
|
|
|
var pointType = isStart ? "start" : (isEnd ? "end" : "action");
|
|
|
|
var routePointDto = new RoutePointDto
|
|
{
|
|
Latitude = currentPoint.Latitude,
|
|
Longitude = currentPoint.Longitude,
|
|
PointType = pointType,
|
|
SequenceNumber = sequenceNumber++,
|
|
SegmentIndex = segmentIndex,
|
|
DistanceFromPrevious = distanceFromPrevious
|
|
};
|
|
|
|
allPoints.Add(routePointDto);
|
|
|
|
if (!isEnd)
|
|
{
|
|
var nextPoint = request.Points[segmentIndex + 1];
|
|
var startGeo = new GeoPoint(currentPoint.Latitude, currentPoint.Longitude);
|
|
var endGeo = new GeoPoint(nextPoint.Latitude, nextPoint.Longitude);
|
|
|
|
var intermediatePoints = GeoUtils.CalculateIntermediatePoints(startGeo, endGeo, MAX_POINT_SPACING_METERS);
|
|
|
|
foreach (var intermediateGeo in intermediatePoints)
|
|
{
|
|
var lastAddedPoint = allPoints[^1];
|
|
var prevGeo = new GeoPoint(lastAddedPoint.Latitude, lastAddedPoint.Longitude);
|
|
|
|
var distFromPrev = GeoUtils.CalculateDistance(prevGeo, intermediateGeo);
|
|
totalDistance += distFromPrev;
|
|
|
|
allPoints.Add(new RoutePointDto
|
|
{
|
|
Latitude = intermediateGeo.Lat,
|
|
Longitude = intermediateGeo.Lon,
|
|
PointType = "intermediate",
|
|
SequenceNumber = sequenceNumber++,
|
|
SegmentIndex = segmentIndex,
|
|
DistanceFromPrevious = distFromPrev
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
var now = DateTime.UtcNow;
|
|
var routeEntity = new RouteEntity
|
|
{
|
|
Id = request.Id,
|
|
Name = request.Name,
|
|
Description = request.Description,
|
|
RegionSizeMeters = request.RegionSizeMeters,
|
|
ZoomLevel = request.ZoomLevel,
|
|
TotalDistanceMeters = totalDistance,
|
|
TotalPoints = allPoints.Count,
|
|
RequestMaps = request.RequestMaps,
|
|
CreateTilesZip = request.CreateTilesZip,
|
|
MapsReady = false,
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
await _routeRepository.InsertRouteAsync(routeEntity);
|
|
|
|
var pointEntities = allPoints.Select(p => new RoutePointEntity
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
RouteId = request.Id,
|
|
SequenceNumber = p.SequenceNumber,
|
|
Latitude = p.Latitude,
|
|
Longitude = p.Longitude,
|
|
PointType = p.PointType,
|
|
SegmentIndex = p.SegmentIndex,
|
|
DistanceFromPrevious = p.DistanceFromPrevious,
|
|
CreatedAt = now
|
|
}).ToList();
|
|
|
|
await _routeRepository.InsertRoutePointsAsync(pointEntities);
|
|
|
|
if (request.Geofences?.Polygons != null && request.Geofences.Polygons.Count > 0)
|
|
{
|
|
for (int polygonIndex = 0; polygonIndex < request.Geofences.Polygons.Count; polygonIndex++)
|
|
{
|
|
var polygon = request.Geofences.Polygons[polygonIndex];
|
|
|
|
if (polygon.NorthWest is null || polygon.SouthEast is null)
|
|
{
|
|
throw new ArgumentException("Geofence polygon coordinates are required");
|
|
}
|
|
|
|
if ((Math.Abs(polygon.NorthWest.Lat) < 0.0001 && Math.Abs(polygon.NorthWest.Lon) < 0.0001) ||
|
|
(Math.Abs(polygon.SouthEast.Lat) < 0.0001 && Math.Abs(polygon.SouthEast.Lon) < 0.0001))
|
|
{
|
|
throw new ArgumentException("Geofence polygon coordinates cannot be (0,0)");
|
|
}
|
|
|
|
if (polygon.NorthWest.Lat < -90 || polygon.NorthWest.Lat > 90 ||
|
|
polygon.SouthEast.Lat < -90 || polygon.SouthEast.Lat > 90 ||
|
|
polygon.NorthWest.Lon < -180 || polygon.NorthWest.Lon > 180 ||
|
|
polygon.SouthEast.Lon < -180 || polygon.SouthEast.Lon > 180)
|
|
{
|
|
throw new ArgumentException("Geofence polygon coordinates must be valid (lat: -90 to 90, lon: -180 to 180)");
|
|
}
|
|
|
|
if (polygon.NorthWest.Lat <= polygon.SouthEast.Lat)
|
|
{
|
|
throw new ArgumentException("Geofence northWest latitude must be greater than southEast latitude");
|
|
}
|
|
|
|
var geofenceRegions = CreateGeofenceRegionGrid(polygon.NorthWest, polygon.SouthEast, request.RegionSizeMeters);
|
|
|
|
foreach (var geofencePoint in geofenceRegions)
|
|
{
|
|
var geofenceRegionId = Guid.NewGuid();
|
|
|
|
await _regionService.RequestRegionAsync(
|
|
geofenceRegionId,
|
|
geofencePoint.Lat,
|
|
geofencePoint.Lon,
|
|
request.RegionSizeMeters,
|
|
request.ZoomLevel,
|
|
stitchTiles: false);
|
|
|
|
await _routeRepository.LinkRouteToRegionAsync(request.Id, geofenceRegionId, isGeofence: true, geofencePolygonIndex: polygonIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
return new RouteResponse
|
|
{
|
|
Id = routeEntity.Id,
|
|
Name = routeEntity.Name,
|
|
Description = routeEntity.Description,
|
|
RegionSizeMeters = routeEntity.RegionSizeMeters,
|
|
ZoomLevel = routeEntity.ZoomLevel,
|
|
TotalDistanceMeters = routeEntity.TotalDistanceMeters,
|
|
TotalPoints = routeEntity.TotalPoints,
|
|
Points = allPoints,
|
|
RequestMaps = routeEntity.RequestMaps,
|
|
MapsReady = routeEntity.MapsReady,
|
|
CsvFilePath = routeEntity.CsvFilePath,
|
|
SummaryFilePath = routeEntity.SummaryFilePath,
|
|
StitchedImagePath = routeEntity.StitchedImagePath,
|
|
TilesZipPath = routeEntity.TilesZipPath,
|
|
CreatedAt = routeEntity.CreatedAt,
|
|
UpdatedAt = routeEntity.UpdatedAt
|
|
};
|
|
}
|
|
|
|
public async Task<RouteResponse?> GetRouteAsync(Guid id)
|
|
{
|
|
var route = await _routeRepository.GetByIdAsync(id);
|
|
if (route == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var points = await _routeRepository.GetRoutePointsAsync(id);
|
|
|
|
return new RouteResponse
|
|
{
|
|
Id = route.Id,
|
|
Name = route.Name,
|
|
Description = route.Description,
|
|
RegionSizeMeters = route.RegionSizeMeters,
|
|
ZoomLevel = route.ZoomLevel,
|
|
TotalDistanceMeters = route.TotalDistanceMeters,
|
|
TotalPoints = route.TotalPoints,
|
|
Points = points.Select(p => new RoutePointDto
|
|
{
|
|
Latitude = p.Latitude,
|
|
Longitude = p.Longitude,
|
|
PointType = p.PointType,
|
|
SequenceNumber = p.SequenceNumber,
|
|
SegmentIndex = p.SegmentIndex,
|
|
DistanceFromPrevious = p.DistanceFromPrevious
|
|
}).ToList(),
|
|
RequestMaps = route.RequestMaps,
|
|
MapsReady = route.MapsReady,
|
|
CsvFilePath = route.CsvFilePath,
|
|
SummaryFilePath = route.SummaryFilePath,
|
|
StitchedImagePath = route.StitchedImagePath,
|
|
TilesZipPath = route.TilesZipPath,
|
|
CreatedAt = route.CreatedAt,
|
|
UpdatedAt = route.UpdatedAt
|
|
};
|
|
}
|
|
|
|
private List<GeoPoint> CreateGeofenceRegionGrid(GeoPoint northWest, GeoPoint southEast, double regionSizeMeters)
|
|
{
|
|
var regions = new List<GeoPoint>();
|
|
|
|
var northPoint = new GeoPoint(northWest.Lat, (northWest.Lon + southEast.Lon) / 2);
|
|
var southPoint = new GeoPoint(southEast.Lat, (northWest.Lon + southEast.Lon) / 2);
|
|
var heightMeters = GeoUtils.CalculateDistance(northPoint, southPoint);
|
|
|
|
var westPoint = new GeoPoint((northWest.Lat + southEast.Lat) / 2, northWest.Lon);
|
|
var eastPoint = new GeoPoint((northWest.Lat + southEast.Lat) / 2, southEast.Lon);
|
|
var widthMeters = GeoUtils.CalculateDistance(westPoint, eastPoint);
|
|
|
|
var numLatSteps = Math.Max(1, (int)Math.Ceiling(heightMeters / regionSizeMeters));
|
|
var numLonSteps = Math.Max(1, (int)Math.Ceiling(widthMeters / regionSizeMeters));
|
|
|
|
var latStep = (northWest.Lat - southEast.Lat) / numLatSteps;
|
|
var lonStep = (southEast.Lon - northWest.Lon) / numLonSteps;
|
|
|
|
for (int latIdx = 0; latIdx < numLatSteps; latIdx++)
|
|
{
|
|
for (int lonIdx = 0; lonIdx < numLonSteps; lonIdx++)
|
|
{
|
|
var lat = northWest.Lat - (latIdx + 0.5) * latStep;
|
|
var lon = northWest.Lon + (lonIdx + 0.5) * lonStep;
|
|
regions.Add(new GeoPoint(lat, lon));
|
|
}
|
|
}
|
|
|
|
return regions;
|
|
}
|
|
}
|
|
|