Implement story 2.2: coordinate projection and base map loading
Add MapBounds dataclass and equirectangular project() function in projection.py, basemap.load() forcing pixels into memory via .copy(), and full test coverage for both modules (4 new tests). All quality gates pass: 67 tests, ruff clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,9 @@
|
||||
# stub
|
||||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from planemapper.constants import BACKGROUND_PATH
|
||||
|
||||
|
||||
def load() -> Image.Image:
|
||||
return Image.open(BACKGROUND_PATH).copy()
|
||||
|
||||
@@ -1 +1,25 @@
|
||||
# stub
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from planemapper.constants import DISPLAY_HEIGHT, DISPLAY_WIDTH
|
||||
|
||||
|
||||
@dataclass
|
||||
class MapBounds:
|
||||
home_lat: float
|
||||
home_lon: float
|
||||
radius_nm: float
|
||||
width: int = field(default=DISPLAY_WIDTH)
|
||||
height: int = field(default=DISPLAY_HEIGHT)
|
||||
|
||||
|
||||
def project(lat: float, lon: float, bounds: MapBounds) -> tuple[int, int]:
|
||||
deg_per_nm_lat = 1 / 60
|
||||
deg_per_nm_lon = 1 / (60 * math.cos(math.radians(bounds.home_lat)))
|
||||
px_per_nm_x = (bounds.width / 2) / bounds.radius_nm
|
||||
px_per_nm_y = (bounds.height / 2) / bounds.radius_nm
|
||||
x = bounds.width // 2 + int((lon - bounds.home_lon) / deg_per_nm_lon * px_per_nm_x)
|
||||
y = bounds.height // 2 - int((lat - bounds.home_lat) / deg_per_nm_lat * px_per_nm_y)
|
||||
return (x, y)
|
||||
|
||||
Reference in New Issue
Block a user