mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-22 22:46:36 +00:00
dd9835c0cd
- ruff --fix: removed trailing whitespace (W293), sorted imports (I001) - Manual: broke long lines (E501) in eskf, rotation, vo, gpr, metric, pipeline, rotation tests - Removed unused imports (F401) in models.py, schemas/__init__.py - pyproject.toml: line-length 100→120, E501 ignore for abstract interfaces ruff check: 0 errors. pytest: 195 passed / 8 skipped. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
160 lines
5.8 KiB
Python
160 lines
5.8 KiB
Python
"""Tests for SatelliteDataManager (F04) — SAT-01/02 and mercator utils (H06)."""
|
||
|
||
import numpy as np
|
||
import pytest
|
||
|
||
from gps_denied.core.satellite import SatelliteDataManager
|
||
from gps_denied.schemas import GPSPoint
|
||
from gps_denied.utils import mercator
|
||
|
||
# ---------------------------------------------------------------
|
||
# Mercator utils
|
||
# ---------------------------------------------------------------
|
||
|
||
def test_latlon_to_tile():
|
||
lat, lon, zoom = 50.4501, 30.5234, 15
|
||
coords = mercator.latlon_to_tile(lat, lon, zoom)
|
||
assert coords.zoom == 15
|
||
assert coords.x > 0
|
||
assert coords.y > 0
|
||
|
||
|
||
def test_tile_to_latlon():
|
||
gps = mercator.tile_to_latlon(19131, 10927, 15)
|
||
assert 50.0 < gps.lat < 52.0
|
||
assert 30.0 < gps.lon < 31.0
|
||
|
||
|
||
def test_tile_bounds():
|
||
coords = mercator.TileCoords(x=19131, y=10927, zoom=15)
|
||
bounds = mercator.compute_tile_bounds(coords)
|
||
assert bounds.nw.lat > bounds.se.lat
|
||
assert bounds.nw.lon < bounds.se.lon
|
||
assert bounds.gsd > 0
|
||
|
||
|
||
# ---------------------------------------------------------------
|
||
# SAT-01: Local tile storage (no HTTP)
|
||
# ---------------------------------------------------------------
|
||
|
||
@pytest.fixture
|
||
def satellite_manager(tmp_path):
|
||
return SatelliteDataManager(tile_dir=str(tmp_path / "tiles"))
|
||
|
||
|
||
def test_load_local_tile_missing(satellite_manager):
|
||
"""Missing tile returns None — no crash."""
|
||
coords = mercator.TileCoords(x=0, y=0, zoom=12)
|
||
result = satellite_manager.load_local_tile(coords)
|
||
assert result is None
|
||
|
||
|
||
def test_save_and_load_local_tile(satellite_manager):
|
||
"""SAT-01: saved tile can be read back from the local directory."""
|
||
coords = mercator.TileCoords(x=19131, y=10927, zoom=15)
|
||
img = np.zeros((256, 256, 3), dtype=np.uint8)
|
||
img[:] = [0, 128, 255]
|
||
|
||
ok = satellite_manager.save_local_tile(coords, img)
|
||
assert ok is True
|
||
|
||
loaded = satellite_manager.load_local_tile(coords)
|
||
assert loaded is not None
|
||
assert loaded.shape == (256, 256, 3)
|
||
|
||
|
||
def test_mem_cache_hit(satellite_manager):
|
||
"""Tile loaded once should be served from memory on second request."""
|
||
coords = mercator.TileCoords(x=1, y=1, zoom=10)
|
||
img = np.ones((256, 256, 3), dtype=np.uint8) * 42
|
||
satellite_manager.save_local_tile(coords, img)
|
||
|
||
r1 = satellite_manager.load_local_tile(coords)
|
||
r2 = satellite_manager.load_local_tile(coords)
|
||
assert r1 is r2 # same object = came from mem cache
|
||
|
||
|
||
# ---------------------------------------------------------------
|
||
# SAT-02: ESKF ±3σ tile selection
|
||
# ---------------------------------------------------------------
|
||
|
||
def test_select_tiles_small_sigma(satellite_manager):
|
||
"""Very tight sigma → single tile covering the position."""
|
||
gps = GPSPoint(lat=50.45, lon=30.52)
|
||
tiles = satellite_manager.select_tiles_for_eskf_position(gps, sigma_h_m=1.0, zoom=18)
|
||
# Should produce at least the center tile
|
||
assert len(tiles) >= 1
|
||
for t in tiles:
|
||
assert t.zoom == 18
|
||
|
||
|
||
def test_select_tiles_large_sigma(satellite_manager):
|
||
"""Larger sigma → more tiles returned."""
|
||
gps = GPSPoint(lat=50.45, lon=30.52)
|
||
small = satellite_manager.select_tiles_for_eskf_position(gps, sigma_h_m=10.0, zoom=18)
|
||
large = satellite_manager.select_tiles_for_eskf_position(gps, sigma_h_m=200.0, zoom=18)
|
||
assert len(large) >= len(small)
|
||
|
||
|
||
def test_select_tiles_bounding_box(satellite_manager):
|
||
"""Selected tiles must span a bounding box that covers ±3σ."""
|
||
gps = GPSPoint(lat=49.0, lon=32.0)
|
||
sigma = 50.0 # 50 m → 3σ = 150 m
|
||
zoom = 18
|
||
tiles = satellite_manager.select_tiles_for_eskf_position(gps, sigma_h_m=sigma, zoom=zoom)
|
||
assert len(tiles) >= 1
|
||
# All returned tiles must be at the requested zoom
|
||
assert all(t.zoom == zoom for t in tiles)
|
||
|
||
|
||
# ---------------------------------------------------------------
|
||
# SAT-01: Mosaic assembly
|
||
# ---------------------------------------------------------------
|
||
|
||
def test_assemble_mosaic_single(satellite_manager):
|
||
"""Single tile → mosaic equals that tile (resized)."""
|
||
coords = mercator.TileCoords(x=10, y=10, zoom=15)
|
||
img = np.zeros((256, 256, 3), dtype=np.uint8)
|
||
mosaic, bounds = satellite_manager.assemble_mosaic([(coords, img)], target_size=256)
|
||
assert mosaic.shape == (256, 256, 3)
|
||
assert bounds.center is not None
|
||
|
||
|
||
def test_assemble_mosaic_2x2(satellite_manager):
|
||
"""2×2 tile grid assembles into a single mosaic."""
|
||
mercator.TileCoords(x=10, y=10, zoom=15)
|
||
tiles = [
|
||
(mercator.TileCoords(x=10, y=10, zoom=15), np.zeros((256, 256, 3), dtype=np.uint8)),
|
||
(mercator.TileCoords(x=11, y=10, zoom=15), np.zeros((256, 256, 3), dtype=np.uint8)),
|
||
(mercator.TileCoords(x=10, y=11, zoom=15), np.zeros((256, 256, 3), dtype=np.uint8)),
|
||
(mercator.TileCoords(x=11, y=11, zoom=15), np.zeros((256, 256, 3), dtype=np.uint8)),
|
||
]
|
||
mosaic, bounds = satellite_manager.assemble_mosaic(tiles, target_size=512)
|
||
assert mosaic.shape == (512, 512, 3)
|
||
|
||
|
||
def test_assemble_mosaic_empty(satellite_manager):
|
||
result = satellite_manager.assemble_mosaic([])
|
||
assert result is None
|
||
|
||
|
||
# ---------------------------------------------------------------
|
||
# Cache helpers (backward compat)
|
||
# ---------------------------------------------------------------
|
||
|
||
def test_cache_tile_compat(satellite_manager):
|
||
coords = mercator.TileCoords(x=100, y=100, zoom=12)
|
||
img = np.zeros((256, 256, 3), dtype=np.uint8)
|
||
assert satellite_manager.cache_tile("f1", coords, img) is True
|
||
cached = satellite_manager.get_cached_tile("f1", coords)
|
||
assert cached is not None
|
||
|
||
|
||
def test_grid_calculations(satellite_manager):
|
||
center = mercator.TileCoords(x=100, y=100, zoom=15)
|
||
grid = satellite_manager.get_tile_grid(center, 9)
|
||
assert len(grid) == 9
|
||
assert any(c.x == 100 and c.y == 100 for c in grid)
|
||
new_tiles = satellite_manager.expand_search_grid(center, 9, 25)
|
||
assert len(new_tiles) == 16 # 25 - 9
|