Files
satellite-provider/SatelliteProvider.Common/Configs/StorageConfig.cs
T
Oleksandr Bezdieniezhnykh 330bccd724 [AZ-366] Refactor C13: consolidate Haversine + tile-coord parsing
RouteProcessingService.CalculateDistance(double, double, double, double)
re-implemented Haversine using EARTH_RADIUS=6371000 alongside the
canonical GeoUtils.CalculateDistance(GeoPoint, GeoPoint) which uses
EARTH_RADIUS=6378137. Two implementations of the same formula for the
same problem.

Separately, ExtractTileCoordinatesFromFilename in RouteProcessingService
parsed the tile_{z}_{x}_{y}_{ts}.jpg filename pattern that's *generated*
by StorageConfig.GetTileFilePath in another assembly — the writer and
parser were coupled by string convention only and a format change in
one would silently break the other.

Both fixes:

(a) Deleted the duplicate Haversine in RouteProcessingService. The
single call site (region-matching nearest-neighbor OrderBy) now uses
GeoUtils.CalculateDistance with GeoPoint instances. The constant
difference is monotonic-equivalent for OrderBy purposes — same region
is picked.

(b) Added static StorageConfig.TryExtractTileCoordinates(string, out
int, out int): bool — pure parser, co-located with GetTileFilePath so
the inverse-pair invariant is structural, not by-convention.
RouteProcessingService.ExtractTileCoordinatesFromFilename becomes a
thin wrapper that delegates to the helper and emits the existing
warning log on malformed input — the AZ-352 tests for warning behavior
all still pass.

Verification:
- 77/77 unit tests green (was 71 → +6 new StorageConfigTests including
  a writer/parser round-trip test for AC-2).
- Smoke + full integration suite green.
- AC-1 grep verification: Math.Sin/EARTH_RADIUS patterns are now
  confined to GeoUtils.cs only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 00:56:46 +03:00

48 lines
1.6 KiB
C#

namespace SatelliteProvider.Common.Configs;
public class StorageConfig
{
public string TilesDirectory { get; set; } = "/tiles";
public string ReadyDirectory { get; set; } = "/ready";
public string GetTileSubdirectoryPath(int zoomLevel, int tileX, int tileY)
{
var xBucket = tileX / 1000;
var yBucket = tileY / 1000;
return Path.Combine(TilesDirectory, zoomLevel.ToString(), xBucket.ToString(), yBucket.ToString());
}
public string GetTileFilePath(int zoomLevel, int tileX, int tileY, string timestamp)
{
var subdirectory = GetTileSubdirectoryPath(zoomLevel, tileX, tileY);
var fileName = $"tile_{zoomLevel}_{tileX}_{tileY}_{timestamp}.jpg";
return Path.Combine(subdirectory, fileName);
}
// Inverse of GetTileFilePath: parses tile_{zoom}_{x}_{y}_{ts}.jpg.
// Co-located with the writer per AZ-366 / C13 so format changes can never
// drift between the two ends. Pure: no I/O, no logging, no exceptions for
// malformed input — caller decides how to react to a `false` return.
public static bool TryExtractTileCoordinates(string filePath, out int tileX, out int tileY)
{
tileX = -1;
tileY = -1;
ArgumentNullException.ThrowIfNull(filePath);
var filename = Path.GetFileNameWithoutExtension(filePath);
var parts = filename.Split('_');
if (parts.Length >= 4 && parts[0] == "tile" &&
int.TryParse(parts[2], out var x) &&
int.TryParse(parts[3], out var y))
{
tileX = x;
tileY = y;
return true;
}
return false;
}
}