# Refactor: consolidate Haversine + tile-coord parsing into Common/Utils

**Task**: AZ-366_refactor_consolidate_haversine_parser
**Name**: Single Haversine + co-located tile-filename parser
**Description**: Delete the duplicate Haversine implementation in `RouteProcessingService` and move `ExtractTileCoordinatesFromFilename` next to `StorageConfig.GetTileFilePath`.
**Complexity**: 2 points
**Dependencies**: None
**Component**: Common + Services.RouteManagement
**Tracker**: AZ-366
**Epic**: AZ-350

## Problem

`SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs:596-608` re-implements `GeoUtils.CalculateDistance(GeoPoint, GeoPoint)` as `CalculateDistance(lat1, lon1, lat2, lon2)`. Lines 610-630 host `ExtractTileCoordinatesFromFilename`, a parser tied to the `tile_{ts}_{x}_{y}.jpg` pattern that's *generated* by `StorageConfig.GetTileFilePath` in another assembly. Coupling by string convention only.

## Outcome

- One Haversine implementation in the codebase (in `GeoUtils`).
- The tile-filename writer and parser live in the same module.
- 37 unit + 5 smoke tests stay green.

## Scope

### Included
- Delete `RouteProcessingService.CalculateDistance(double, double, double, double)`.
- Replace its call sites with `GeoUtils.CalculateDistance(GeoPoint a, GeoPoint b)`.
- Move `ExtractTileCoordinatesFromFilename` to live next to `StorageConfig.GetTileFilePath` (or onto `StorageConfig` itself as a static method).
- Update consumers' `using` directives.

### Excluded
- Changing the filename pattern.
- Changing the `GeoUtils.CalculateDistance` algorithm.

## Acceptance Criteria

**AC-1: One Haversine**
Given the post-refactor source
When grepped for `Math.Sin\(.*lat\)` (or any Haversine-like pattern)
Then matches are confined to `GeoUtils.cs`.

**AC-2: Writer and parser co-located**
Given the post-refactor source
When `StorageConfig.GetTileFilePath` and `ExtractTileCoordinatesFromFilename` are inspected
Then they live in the same file or class.

**AC-3: Tests stay green**
Given the post-refactor build
When `scripts/run-tests.sh --smoke` runs
Then all 37 unit + 5 smoke scenarios pass.

## Constraints

- No public API change beyond moving a static method.

## Risks & Mitigation

**Risk 1: the parser's empty-catch behavior was already changed by C02**
- *Risk*: ordering matters — if C13 ships before C02 the empty catch still exists.
- *Mitigation*: implement C02 first (already in Phase 1); C13 inherits the cleaned-up parser.

Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C13).
