# Feature: Tile Fetching

## Description
Handles HTTP-based satellite tile retrieval from external provider API with multiple fetching patterns: single tile, grid, progressive expansion, and route corridor prefetching. Integrates with cache for performance optimization and supports parallel fetching for throughput.

## Component APIs Implemented
- `fetch_tile(lat: float, lon: float, zoom: int) -> Optional[np.ndarray]`
- `fetch_tile_grid(center_lat: float, center_lon: float, grid_size: int, zoom: int) -> Dict[str, np.ndarray]`
- `prefetch_route_corridor(waypoints: List[GPSPoint], corridor_width_m: float, zoom: int) -> bool`
- `progressive_fetch(center_lat: float, center_lon: float, grid_sizes: List[int], zoom: int) -> Iterator[Dict[str, np.ndarray]]`

## External Tools and Services
- **Satellite Provider API**: HTTP tile source (`GET /api/satellite/tiles/latlon`)
- **httpx** or **requests**: HTTP client with async support
- **numpy**: Image array handling

## Internal Dependencies
- **01_feature_tile_cache_management**: cache_tile, get_cached_tile
- **02_feature_tile_coordinate_operations**: compute_tile_coords, get_tile_grid

## Internal Methods
- `_fetch_from_api(tile_coords: TileCoords) -> Optional[np.ndarray]`: HTTP GET to satellite provider, handles response parsing
- `_fetch_with_retry(tile_coords: TileCoords, max_retries: int = 3) -> Optional[np.ndarray]`: Wraps _fetch_from_api with retry logic
- `_fetch_tiles_parallel(tiles: List[TileCoords], max_concurrent: int = 20) -> Dict[str, np.ndarray]`: Parallel fetching with connection pooling
- `_compute_corridor_tiles(waypoints: List[GPSPoint], corridor_width_m: float, zoom: int) -> List[TileCoords]`: Calculates tiles covering route corridor polygon
- `_generate_tile_id(tile_coords: TileCoords) -> str`: Creates unique tile identifier string

## Unit Tests
1. **fetch_tile_cache_hit**: Tile in cache → returns immediately, no HTTP call
2. **fetch_tile_cache_miss**: Not cached → HTTP fetch, cache, return
3. **fetch_tile_api_error**: HTTP 500 → returns None
4. **fetch_tile_invalid_coords**: Invalid GPS → returns None
5. **fetch_tile_retry_success**: First attempt fails, second succeeds → returns tile
6. **fetch_tile_retry_exhausted**: All 3 attempts fail → returns None
7. **fetch_tile_grid_2x2**: grid_size=4 → returns dict with 4 tiles
8. **fetch_tile_grid_3x3**: grid_size=9 → returns dict with 9 tiles
9. **fetch_tile_grid_partial_failure**: 2 of 9 tiles fail → returns 7 tiles
10. **fetch_tile_grid_all_cached**: All tiles cached → no HTTP calls
11. **prefetch_route_corridor_success**: 10 waypoints → prefetches tiles, returns True
12. **prefetch_route_corridor_partial_failure**: Some tiles fail → continues, returns True
13. **prefetch_route_corridor_complete_failure**: All tiles fail → returns False
14. **progressive_fetch_yields_sequence**: [1,4,9] → yields 3 dicts in order
15. **progressive_fetch_early_termination**: Break after 4 → doesn't fetch 9,16,25

## Integration Tests
1. **fetch_and_cache_verify**: fetch_tile() → get_cached_tile() returns same data
2. **progressive_search_simulation**: progressive_fetch with simulated match on grid 9
3. **grid_expansion_no_refetch**: fetch_tile_grid(4) then expand → no duplicate fetches
4. **corridor_prefetch_coverage**: prefetch_route_corridor → all corridor tiles cached
5. **concurrent_fetch_stress**: Fetch 100 tiles in parallel → all complete within timeout

