mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 18:51:13 +00:00
9a53bff92e
Batch 24 of 03-code-quality-refactoring run; closes the run. AZ-375 (C22): GoogleMapsDownloaderV2.DownloadTilesGridAsync now builds a HashSet<(int X, int Y, int Z)> once from existingTiles and tests Contains((x, y, zoomLevel)) per cell. Removes the per-cell FirstOrDefault tolerance scan and the unused _processingConfig .LatLonTolerance reference at this site. AZ-377 (C24): promote Earth + tile-pixel constants to a single home. GeoUtils now exposes EarthRadiusMeters, EarthEquatorial CircumferenceMeters, MetersPerDegreeLatitude as public const. MapConfig adds DefaultTileSizePixels (const) wired as the TileSizePixels property default. TileRepository and Google MapsDownloaderV2 read those constants instead of duplicating the literals 6378137, 40075016.686, 111000.0, and 256. Tests: +6 new (DownloaderRefactorTests, extended GeoUtils RefactorTests). 200/200 unit tests pass. Cumulative K=3 review (batches 22-24): PASS_WITH_WARNINGS, 4 Low findings only — see _docs/03_implementation/reviews/cumulative_review_22-24.md. Tooling fix: scripts/run-tests.sh --unit-only path now restores before testing (was failing on SixLabors resolution in clean container). Stripped stray BOM from MapConfig.cs to satisfy the .editorconfig charset gate. Updates _dependencies_table.md to reflect all 27 03-code-quality- refactoring tasks done; updates _autodev_state.md to refactor phase 5 (test-sync). Co-authored-by: Cursor <cursoragent@cursor.com>
131 lines
4.9 KiB
C#
131 lines
4.9 KiB
C#
using SatelliteProvider.Common.DTO;
|
|
|
|
namespace SatelliteProvider.Common.Utils;
|
|
|
|
public static class GeoUtils
|
|
{
|
|
public const double EarthRadiusMeters = 6378137d;
|
|
public const double EarthEquatorialCircumferenceMeters = 40075016.686d;
|
|
public const double MetersPerDegreeLatitude = 111000d;
|
|
|
|
public static (int x, int y) WorldToTilePos(GeoPoint point, int zoom)
|
|
{
|
|
var latRad = point.Lat * Math.PI / 180.0;
|
|
var n = Math.Pow(2.0, zoom);
|
|
var xTile = (int)Math.Floor((point.Lon + 180.0) / 360.0 * n);
|
|
var yTile = (int)Math.Floor((1.0 - Math.Log(Math.Tan(latRad) + 1.0 / Math.Cos(latRad)) / Math.PI) / 2.0 * n);
|
|
return (xTile, yTile);
|
|
}
|
|
|
|
public static double ToRadians(double degrees) => degrees * Math.PI / 180.0;
|
|
public static double ToDegrees(double radians) => radians * 180.0 / Math.PI;
|
|
|
|
public static Direction DirectionTo(this GeoPoint p1, GeoPoint p2)
|
|
{
|
|
var lat1Rad = ToRadians(p1.Lat);
|
|
var lat2Rad = ToRadians(p2.Lat);
|
|
var dLon = ToRadians(p2.Lon - p1.Lon);
|
|
var dLat = ToRadians(p2.Lat - p1.Lat);
|
|
|
|
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
|
|
Math.Cos(lat1Rad) * Math.Cos(lat2Rad) *
|
|
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
|
|
var c = 2 * Math.Asin(Math.Sqrt(a));
|
|
var distance = EarthRadiusMeters * c;
|
|
|
|
var y = Math.Sin(dLon) * Math.Cos(lat2Rad);
|
|
var x = Math.Cos(lat1Rad) * Math.Sin(lat2Rad) -
|
|
Math.Sin(lat1Rad) * Math.Cos(lat2Rad) * Math.Cos(dLon);
|
|
var azimuthRadians = Math.Atan2(y, x);
|
|
var azimuth = (ToDegrees(azimuthRadians) + 360) % 360;
|
|
|
|
return new Direction
|
|
{
|
|
Distance = distance,
|
|
Azimuth = azimuth
|
|
};
|
|
}
|
|
|
|
public static GeoPoint GoDirection(this GeoPoint startPoint, Direction direction)
|
|
{
|
|
var angularDistance = direction.Distance / EarthRadiusMeters;
|
|
var azimuthRadians = ToRadians(direction.Azimuth);
|
|
var startLatRad = ToRadians(startPoint.Lat);
|
|
var startLonRad = ToRadians(startPoint.Lon);
|
|
|
|
var destLatRad = Math.Asin(Math.Sin(startLatRad) * Math.Cos(angularDistance) +
|
|
Math.Cos(startLatRad) * Math.Sin(angularDistance) * Math.Cos(azimuthRadians));
|
|
|
|
var destLonRad = startLonRad + Math.Atan2(Math.Sin(azimuthRadians) * Math.Sin(angularDistance) * Math.Cos(startLatRad),
|
|
Math.Cos(angularDistance) - Math.Sin(startLatRad) * Math.Sin(destLatRad));
|
|
|
|
return new GeoPoint(ToDegrees(destLatRad), ToDegrees(destLonRad));
|
|
}
|
|
|
|
public static GeoPoint TileToWorldPos(int x, int y, int zoom)
|
|
{
|
|
var n = Math.Pow(2.0, zoom);
|
|
var lonDeg = x / n * 360.0 - 180.0;
|
|
var latRad = Math.Atan(Math.Sinh(Math.PI * (1.0 - 2.0 * y / n)));
|
|
var latDeg = latRad * 180.0 / Math.PI;
|
|
return new GeoPoint(latDeg, lonDeg);
|
|
}
|
|
|
|
public static (double minLat, double maxLat, double minLon, double maxLon) GetBoundingBox(GeoPoint centerGeoPoint, double radiusM)
|
|
{
|
|
var latRad = centerGeoPoint.Lat * Math.PI / 180.0;
|
|
|
|
var latDiff = (radiusM / EarthRadiusMeters) * (180.0 / Math.PI);
|
|
var minLat = Math.Max(centerGeoPoint.Lat - latDiff, -90.0);
|
|
var maxLat = Math.Min(centerGeoPoint.Lat + latDiff, 90.0);
|
|
|
|
var lonDiff = (radiusM / (EarthRadiusMeters * Math.Cos(latRad))) * (180.0 / Math.PI);
|
|
var minLon = Math.Max(centerGeoPoint.Lon - lonDiff, -180.0);
|
|
var maxLon = Math.Min(centerGeoPoint.Lon + lonDiff, 180.0);
|
|
|
|
return (minLat, maxLat, minLon, maxLon);
|
|
}
|
|
|
|
public static List<GeoPoint> CalculateIntermediatePoints(GeoPoint start, GeoPoint end, double maxSpacingMeters)
|
|
{
|
|
var direction = start.DirectionTo(end);
|
|
var distance = direction.Distance;
|
|
|
|
if (distance <= maxSpacingMeters)
|
|
{
|
|
return new List<GeoPoint>();
|
|
}
|
|
|
|
var numSegments = (int)Math.Ceiling(distance / maxSpacingMeters);
|
|
var actualSpacing = distance / numSegments;
|
|
|
|
var intermediatePoints = new List<GeoPoint>();
|
|
|
|
for (int i = 1; i < numSegments; i++)
|
|
{
|
|
var segmentDistance = actualSpacing * i;
|
|
var intermediateDirection = new Direction
|
|
{
|
|
Distance = segmentDistance,
|
|
Azimuth = direction.Azimuth
|
|
};
|
|
var intermediatePoint = start.GoDirection(intermediateDirection);
|
|
intermediatePoints.Add(intermediatePoint);
|
|
}
|
|
|
|
return intermediatePoints;
|
|
}
|
|
|
|
public static double CalculateDistance(GeoPoint p1, GeoPoint p2)
|
|
{
|
|
return p1.DirectionTo(p2).Distance;
|
|
}
|
|
|
|
public static GeoPoint CalculateCenter(GeoPoint northWest, GeoPoint southEast)
|
|
{
|
|
var centerLat = (northWest.Lat + southEast.Lat) / 2.0;
|
|
var centerLon = (northWest.Lon + southEast.Lon) / 2.0;
|
|
return new GeoPoint(centerLat, centerLon);
|
|
}
|
|
}
|