feat: stage5 — Satellite tiles (F04) and Coordinates (F13)

This commit is contained in:
Yuzviak
2026-03-22 22:44:12 +02:00
parent d5b6925a14
commit a2fb9ab404
9 changed files with 551 additions and 9 deletions
+84
View File
@@ -0,0 +1,84 @@
"""Tests for CoordinateTransformer (F13)."""
import pytest
from gps_denied.core.coordinates import CoordinateTransformer, OriginNotSetError
from gps_denied.schemas import CameraParameters, GPSPoint
@pytest.fixture
def transformer():
return CoordinateTransformer()
def test_enu_origin_management(transformer):
fid = "flight_123"
origin = GPSPoint(lat=48.0, lon=37.0)
# Before setting
with pytest.raises(OriginNotSetError):
transformer.get_enu_origin(fid)
# After setting
transformer.set_enu_origin(fid, origin)
assert transformer.get_enu_origin(fid).lat == 48.0
def test_gps_to_enu(transformer):
fid = "flight_123"
origin = GPSPoint(lat=48.0, lon=37.0)
transformer.set_enu_origin(fid, origin)
# Same point -> 0, 0, 0
enu = transformer.gps_to_enu(fid, origin)
assert enu == (0.0, 0.0, 0.0)
# Point north
target = GPSPoint(lat=48.01, lon=37.0)
enu_n = transformer.gps_to_enu(fid, target)
assert enu_n[0] == 0.0
assert enu_n[1] > 1000.0 # 0.01 deg lat is > 1km
assert enu_n[2] == 0.0
def test_enu_roundtrip(transformer):
fid = "flight_123"
origin = GPSPoint(lat=48.0, lon=37.0)
transformer.set_enu_origin(fid, origin)
test_gps = GPSPoint(lat=48.056, lon=37.123)
enu = transformer.gps_to_enu(fid, test_gps)
recovered = transformer.enu_to_gps(fid, enu)
assert pytest.approx(recovered.lat, abs=1e-6) == test_gps.lat
assert pytest.approx(recovered.lon, abs=1e-6) == test_gps.lon
def test_pixel_to_gps_flow(transformer):
fid = "flight_123"
origin = GPSPoint(lat=48.0, lon=37.0)
transformer.set_enu_origin(fid, origin)
cam = CameraParameters(
focal_length=25.0,
sensor_width=23.5,
sensor_height=15.6,
resolution_width=4000,
resolution_height=3000,
)
# Image center should yield the frame center (mock implementation logic)
pixel = (2000.0, 1500.0)
pose = {"position": [0, 0, 0]}
gps_res = transformer.pixel_to_gps(fid, pixel, pose, cam, 100.0)
assert gps_res.lat == origin.lat
assert gps_res.lon == origin.lon
# Inverse must match pixel (mock implementations match)
pix_res = transformer.gps_to_pixel(fid, gps_res, pose, cam, 100.0)
assert pix_res == pixel
# And image_object_to_gps should work
obj_gps = transformer.image_object_to_gps(fid, 1, pixel)
assert obj_gps.lat == origin.lat
+92
View File
@@ -0,0 +1,92 @@
"""Tests for SatelliteDataManager (F04) and mercator utils (H06)."""
import asyncio
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
def test_latlon_to_tile():
# Kyiv coordinates
lat = 50.4501
lon = 30.5234
zoom = 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():
x, y, zoom = 19131, 10927, 15
gps = mercator.tile_to_latlon(x, y, zoom)
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)
# Northwest should be "higher" lat and "lower" lon than Southeast
assert bounds.nw.lat > bounds.se.lat
assert bounds.nw.lon < bounds.se.lon
assert bounds.gsd > 0
@pytest.fixture
def satellite_manager(tmp_path):
# Use tmp_path for cache so we don't pollute workspace
sm = SatelliteDataManager(cache_dir=str(tmp_path / "cache"), max_size_gb=0.1)
yield sm
sm.cache.close()
asyncio.run(sm.http_client.aclose())
@pytest.mark.asyncio
async def test_satellite_fetch_and_cache(satellite_manager):
lat = 48.0
lon = 37.0
zoom = 12
flight_id = "test_flight"
# We won't test the actual HTTP Google API in CI to avoid blocks/bans,
# but we can test the cache mechanism directly.
coords = satellite_manager.compute_tile_coords(lat, lon, zoom)
# Create a fake image (blue square 256x256)
fake_img = np.zeros((256, 256, 3), dtype=np.uint8)
fake_img[:] = [255, 0, 0] # BGR
# Save to cache
success = satellite_manager.cache_tile(flight_id, coords, fake_img)
assert success is True
# Read from cache
cached = satellite_manager.get_cached_tile(flight_id, coords)
assert cached is not None
assert cached.shape == (256, 256, 3)
# Clear cache
satellite_manager.clear_flight_cache(flight_id)
assert satellite_manager.get_cached_tile(flight_id, coords) is None
def test_grid_calculations(satellite_manager):
# Test 3x3 grid (9 tiles)
center = mercator.TileCoords(x=100, y=100, zoom=15)
grid = satellite_manager.get_tile_grid(center, 9)
assert len(grid) == 9
# Ensure center is in grid
assert any(c.x == 100 and c.y == 100 for c in grid)
# Test expansion 9 -> 25
new_tiles = satellite_manager.expand_search_grid(center, 9, 25)
assert len(new_tiles) == 16 # 25 - 9