Initial Vibe coded project
This commit is contained in:
commit
0891cf12a5
21 files changed
+2830
No files matched your search
@@ -0,0 +1,204 @@
|
||||
import json
|
||||
import math
|
||||
import sqlite3
|
||||
import uuid
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from flight_map.geo import Bounds
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Observation:
|
||||
icao24: str
|
||||
callsign: str | None
|
||||
timestamp: float
|
||||
longitude: float
|
||||
latitude: float
|
||||
baro_altitude: float | None
|
||||
geo_altitude: float | None
|
||||
on_ground: bool = False
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if len(self.icao24) != 6 or any(c not in "0123456789abcdef" for c in self.icao24):
|
||||
raise ValueError("Invalid ICAO24 aircraft identifier.")
|
||||
if not all(math.isfinite(v) for v in (self.timestamp, self.longitude, self.latitude)):
|
||||
raise ValueError("Observation contains non-finite coordinates or timestamp.")
|
||||
if not (-180 <= self.longitude <= 180 and -90 <= self.latitude <= 90):
|
||||
raise ValueError("Observation coordinates are outside geographic limits.")
|
||||
for altitude in (self.baro_altitude, self.geo_altitude):
|
||||
if altitude is not None and not math.isfinite(altitude):
|
||||
raise ValueError("Altitude must be finite or missing.")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Session:
|
||||
id: str
|
||||
bounds: Bounds
|
||||
collection_bounds: Bounds
|
||||
interval: float
|
||||
started: float
|
||||
ended: float
|
||||
status: str
|
||||
source: str
|
||||
|
||||
|
||||
@contextmanager
|
||||
def database(
|
||||
path: Path, *, readonly: bool = False, must_exist: bool = False
|
||||
) -> Iterator[sqlite3.Connection]:
|
||||
if readonly or must_exist:
|
||||
mode = "ro" if readonly else "rw"
|
||||
connection = sqlite3.connect(path.resolve().as_uri() + f"?mode={mode}", uri=True)
|
||||
else:
|
||||
connection = sqlite3.connect(path)
|
||||
connection.row_factory = sqlite3.Row
|
||||
connection.execute("PRAGMA foreign_keys = ON")
|
||||
try:
|
||||
yield connection
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def initialize(connection: sqlite3.Connection) -> None:
|
||||
version = connection.execute("PRAGMA user_version").fetchone()[0]
|
||||
if version not in (0, 1):
|
||||
raise ValueError(f"Unsupported flight database version: {version}")
|
||||
connection.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY, bounds TEXT NOT NULL, collection_bounds TEXT NOT NULL,
|
||||
interval REAL NOT NULL, started REAL NOT NULL, ended REAL NOT NULL,
|
||||
status TEXT NOT NULL, source TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS observations (
|
||||
session_id TEXT NOT NULL REFERENCES sessions(id),
|
||||
icao24 TEXT NOT NULL, callsign TEXT, timestamp REAL NOT NULL,
|
||||
longitude REAL NOT NULL, latitude REAL NOT NULL,
|
||||
baro_altitude REAL, geo_altitude REAL, on_ground INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_id, icao24, timestamp)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS observation_time
|
||||
ON observations(session_id, timestamp);
|
||||
PRAGMA user_version = 1;
|
||||
""")
|
||||
|
||||
|
||||
def create_session(
|
||||
connection: sqlite3.Connection, bounds: Bounds, margin: float, interval: float, now: float
|
||||
) -> Session:
|
||||
if not math.isfinite(interval) or interval < 10:
|
||||
raise ValueError("Recording interval must be at least 10 seconds.")
|
||||
session = Session(
|
||||
uuid.uuid4().hex,
|
||||
bounds,
|
||||
bounds.expand(margin),
|
||||
interval,
|
||||
now,
|
||||
now,
|
||||
"recording",
|
||||
"OpenSky Network",
|
||||
)
|
||||
with connection:
|
||||
connection.execute(
|
||||
"INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
session.id,
|
||||
json.dumps(bounds.values),
|
||||
json.dumps(session.collection_bounds.values),
|
||||
interval,
|
||||
now,
|
||||
now,
|
||||
session.status,
|
||||
session.source,
|
||||
),
|
||||
)
|
||||
return session
|
||||
|
||||
|
||||
def get_session(connection: sqlite3.Connection, session_id: str) -> Session:
|
||||
row = connection.execute("SELECT * FROM sessions WHERE id = ?", (session_id,)).fetchone()
|
||||
if row is None:
|
||||
raise ValueError(f"Recording session not found: {session_id}")
|
||||
return Session(
|
||||
row["id"],
|
||||
Bounds(*json.loads(row["bounds"])),
|
||||
Bounds(*json.loads(row["collection_bounds"])),
|
||||
row["interval"],
|
||||
row["started"],
|
||||
row["ended"],
|
||||
row["status"],
|
||||
row["source"],
|
||||
)
|
||||
|
||||
|
||||
def checkpoint(connection: sqlite3.Connection, session_id: str, now: float, status: str) -> None:
|
||||
with connection:
|
||||
connection.execute(
|
||||
"UPDATE sessions SET ended = ?, status = ? WHERE id = ?", (now, status, session_id)
|
||||
)
|
||||
|
||||
|
||||
def purge_session(connection: sqlite3.Connection, session_id: str) -> int:
|
||||
"""Delete one session and its observations in a single write transaction."""
|
||||
with connection:
|
||||
connection.execute("BEGIN IMMEDIATE")
|
||||
exists = connection.execute("SELECT 1 FROM sessions WHERE id = ?", (session_id,)).fetchone()
|
||||
if exists is None:
|
||||
raise ValueError(f"Recording session not found: {session_id}")
|
||||
deleted = connection.execute(
|
||||
"DELETE FROM observations WHERE session_id = ?", (session_id,)
|
||||
).rowcount
|
||||
connection.execute("DELETE FROM sessions WHERE id = ?", (session_id,))
|
||||
return deleted
|
||||
|
||||
|
||||
def save_observations(
|
||||
connection: sqlite3.Connection, session: Session, observations: list[Observation], now: float
|
||||
) -> None:
|
||||
with connection:
|
||||
for item in observations:
|
||||
if (
|
||||
item.timestamp < session.started
|
||||
or item.timestamp > now
|
||||
or not session.collection_bounds.contains(item.longitude, item.latitude)
|
||||
):
|
||||
continue
|
||||
connection.execute(
|
||||
"INSERT OR IGNORE INTO observations VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
session.id,
|
||||
item.icao24,
|
||||
item.callsign,
|
||||
item.timestamp,
|
||||
item.longitude,
|
||||
item.latitude,
|
||||
item.baro_altitude,
|
||||
item.geo_altitude,
|
||||
item.on_ground,
|
||||
),
|
||||
)
|
||||
connection.execute("UPDATE sessions SET ended = ? WHERE id = ?", (now, session.id))
|
||||
|
||||
|
||||
def load_observations(
|
||||
connection: sqlite3.Connection, session_id: str, start: float, end: float
|
||||
) -> list[Observation]:
|
||||
return [
|
||||
Observation(
|
||||
row["icao24"],
|
||||
row["callsign"],
|
||||
row["timestamp"],
|
||||
row["longitude"],
|
||||
row["latitude"],
|
||||
row["baro_altitude"],
|
||||
row["geo_altitude"],
|
||||
bool(row["on_ground"]),
|
||||
)
|
||||
for row in connection.execute(
|
||||
"""SELECT * FROM observations WHERE session_id = ? AND timestamp BETWEEN ? AND ?
|
||||
ORDER BY icao24, timestamp""",
|
||||
(session_id, start, end),
|
||||
)
|
||||
]
|
||||
Reference in new issue
Block a user