Files
pbf/navsea_export_mobile_core_sqlite.py

225 lines
7.0 KiB
Python

from __future__ import annotations
import argparse
import datetime as dt
import sqlite3
from pathlib import Path
DEFAULT_SOURCE_DB = Path("out/navsea_kyushu_navigability_full_run.sqlite")
DEFAULT_OUT_DB = Path("out/navsea_kyushu_mobile_core.sqlite")
DEFAULT_SNAPSHOT_DB = Path("out/navsea_kyushu_navigability_full_run.export_snapshot.sqlite")
def open_readonly(path: Path) -> sqlite3.Connection:
if not path.exists():
raise SystemExit(f"missing source db: {path}")
conn = sqlite3.connect(f"file:{path}?mode=ro", uri=True)
conn.execute("PRAGMA busy_timeout=5000")
return conn
def create_snapshot(source_db: Path, snapshot_db: Path) -> Path:
snapshot_db.parent.mkdir(parents=True, exist_ok=True)
if snapshot_db.exists():
snapshot_db.unlink()
src = sqlite3.connect(source_db)
src.execute("PRAGMA busy_timeout=5000")
dst = sqlite3.connect(snapshot_db)
with dst:
src.backup(dst)
dst.close()
src.close()
return snapshot_db
def open_output(path: Path) -> sqlite3.Connection:
if path.exists():
path.unlink()
path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(path)
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("PRAGMA temp_store=MEMORY")
conn.execute("PRAGMA cache_size=100000")
return conn
def read_meta(conn: sqlite3.Connection, key: str, default: str = "") -> str:
row = conn.execute("SELECT value FROM meta WHERE key = ?", (key,)).fetchone()
return str(row[0]) if row else default
def create_schema(conn: sqlite3.Connection) -> None:
conn.executescript(
"""
CREATE TABLE meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE grid_cell (
grid_level INTEGER NOT NULL,
grid_col INTEGER NOT NULL,
grid_row INTEGER NOT NULL,
parent_grid_col INTEGER,
parent_grid_row INTEGER,
state TEXT NOT NULL,
blocked INTEGER NOT NULL DEFAULT 0,
state_reason TEXT,
risk_score INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (grid_level, grid_col, grid_row)
);
CREATE INDEX idx_grid_cell_level_state
ON grid_cell(grid_level, state, blocked);
CREATE TABLE package_aoi (
aoi_id TEXT PRIMARY KEY,
package_type TEXT NOT NULL,
minx REAL NOT NULL,
miny REAL NOT NULL,
maxx REAL NOT NULL,
maxy REAL NOT NULL
);
"""
)
conn.commit()
def copy_sparse_grid(src: sqlite3.Connection, dst: sqlite3.Connection) -> int:
sql = """
SELECT
grid_level,
grid_col,
grid_row,
parent_grid_col,
parent_grid_row,
state,
blocked,
state_reason,
risk_score
FROM grid_cell
WHERE state <> 'open_water'
ORDER BY grid_level, grid_col, grid_row
"""
count = 0
batch: list[tuple] = []
batch_size = 20000
for row in src.execute(sql):
batch.append(tuple(row))
count += 1
if len(batch) >= batch_size:
dst.executemany(
"""
INSERT INTO grid_cell(
grid_level, grid_col, grid_row, parent_grid_col, parent_grid_row,
state, blocked, state_reason, risk_score
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
batch,
)
dst.commit()
batch.clear()
print(f"已导出 {count} 条移动端核心格", flush=True)
if batch:
dst.executemany(
"""
INSERT INTO grid_cell(
grid_level, grid_col, grid_row, parent_grid_col, parent_grid_row,
state, blocked, state_reason, risk_score
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
batch,
)
dst.commit()
return count
def copy_package_aoi(src: sqlite3.Connection, dst: sqlite3.Connection) -> int:
sql = """
SELECT
printf('port:%d', aoi_id) AS aoi_id,
'port_refine' AS package_type,
center_x - radius_m,
center_y - radius_m,
center_x + radius_m,
center_y + radius_m
FROM port_aoi
ORDER BY aoi_id
"""
rows = [tuple(row) for row in src.execute(sql)]
if rows:
dst.executemany(
"""
INSERT INTO package_aoi(
aoi_id, package_type, minx, miny, maxx, maxy
) VALUES (?, ?, ?, ?, ?, ?)
""",
rows,
)
dst.commit()
return len(rows)
def write_meta(src: sqlite3.Connection, dst: sqlite3.Connection, *, source_db: Path, exported_cells: int, exported_aoi: int) -> None:
entries = [
("source_db", str(source_db)),
("source_grid_profile", read_meta(src, "grid_profile", "")),
("source_checkpoint_at", read_meta(src, "last_checkpoint_at", "")),
("source_phase", read_meta(src, "run_phase", "")),
("export_time", dt.datetime.now().isoformat(timespec="seconds")),
("package_type", "mobile_core"),
("storage_profile", "sparse_non_open_water_only"),
("default_state", "open_water"),
("exported_grid_cell_count", str(exported_cells)),
("exported_package_aoi_count", str(exported_aoi)),
("notes", "移动端核心包:仅保留非 open_water 格和港口 AOI 包索引"),
]
dst.executemany("INSERT INTO meta(key, value) VALUES (?, ?)", entries)
dst.commit()
def print_summary(conn: sqlite3.Connection) -> None:
print("导出结果概览:", flush=True)
for row in conn.execute(
"""
SELECT grid_level, state, COUNT(*)
FROM grid_cell
GROUP BY grid_level, state
ORDER BY grid_level, state
"""
):
print(row, flush=True)
def main() -> None:
parser = argparse.ArgumentParser(description="Export a sparse mobile-core SQLite from the Kyushu engineering navigability DB.")
parser.add_argument("--source-db", type=Path, default=DEFAULT_SOURCE_DB)
parser.add_argument("--out-db", type=Path, default=DEFAULT_OUT_DB)
parser.add_argument("--snapshot-db", type=Path, default=DEFAULT_SNAPSHOT_DB)
args = parser.parse_args()
snapshot_db = create_snapshot(args.source_db, args.snapshot_db)
print(f"已生成一致性快照: {snapshot_db}", flush=True)
src = open_readonly(snapshot_db)
dst = open_output(args.out_db)
create_schema(dst)
exported_cells = copy_sparse_grid(src, dst)
exported_aoi = copy_package_aoi(src, dst)
write_meta(src, dst, source_db=args.source_db, exported_cells=exported_cells, exported_aoi=exported_aoi)
dst.execute("ANALYZE")
dst.commit()
print_summary(dst)
print(f"完成: cells={exported_cells}, aoi={exported_aoi}", flush=True)
print(f"输出文件: {args.out_db.resolve()}", flush=True)
print(f"快照文件: {snapshot_db.resolve()}", flush=True)
dst.close()
src.close()
if __name__ == "__main__":
main()