465 lines
13 KiB
Python
465 lines
13 KiB
Python
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import sqlite3
|
|
import subprocess
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
|
|
DB_NAME = "pbf_analysis"
|
|
OUT = Path("out/navsea_kyushu_semantic.sqlite")
|
|
|
|
MYSQL_BASE = [
|
|
"mysql",
|
|
"-h",
|
|
"localhost",
|
|
"-u",
|
|
"root",
|
|
"-p2chi9ks2",
|
|
"-D",
|
|
DB_NAME,
|
|
"-B",
|
|
"-N",
|
|
"--raw",
|
|
"--default-character-set=utf8mb4",
|
|
]
|
|
|
|
COMMON_COLS = """
|
|
d.feature_id,
|
|
gl.fid,
|
|
d.source_layer,
|
|
d.canonical_object_type,
|
|
d.object_family,
|
|
d.detection_class,
|
|
d.capability,
|
|
d.detection_key,
|
|
d.class_name,
|
|
d.shape_name,
|
|
d.layer_name,
|
|
d.z,
|
|
d.x,
|
|
d.y,
|
|
d.geom_type
|
|
"""
|
|
|
|
QUERIES: list[tuple[str, str]] = [
|
|
(
|
|
"land",
|
|
f"""
|
|
SELECT
|
|
{COMMON_COLS}
|
|
FROM navsea_detection_objects d
|
|
JOIN feature_geojson_lookup gl
|
|
ON gl.feature_id = d.feature_id
|
|
WHERE d.canonical_object_type IN ('P陸域', 'P穴')
|
|
""",
|
|
),
|
|
(
|
|
"collision",
|
|
f"""
|
|
SELECT
|
|
{COMMON_COLS}
|
|
FROM navsea_collision_objects d
|
|
JOIN feature_geojson_lookup gl
|
|
ON gl.feature_id = d.feature_id
|
|
""",
|
|
),
|
|
(
|
|
"grounding",
|
|
f"""
|
|
SELECT
|
|
{COMMON_COLS}
|
|
FROM navsea_grounding_objects d
|
|
JOIN feature_geojson_lookup gl
|
|
ON gl.feature_id = d.feature_id
|
|
""",
|
|
),
|
|
(
|
|
"navmark",
|
|
f"""
|
|
SELECT
|
|
{COMMON_COLS}
|
|
FROM navsea_navigation_mark_objects d
|
|
JOIN feature_geojson_lookup gl
|
|
ON gl.feature_id = d.feature_id
|
|
""",
|
|
),
|
|
(
|
|
"boundary",
|
|
f"""
|
|
SELECT
|
|
{COMMON_COLS}
|
|
FROM navsea_boundary_reference_objects d
|
|
JOIN feature_geojson_lookup gl
|
|
ON gl.feature_id = d.feature_id
|
|
""",
|
|
),
|
|
(
|
|
"route",
|
|
f"""
|
|
SELECT
|
|
{COMMON_COLS}
|
|
FROM navsea_route_reference_objects d
|
|
JOIN feature_geojson_lookup gl
|
|
ON gl.feature_id = d.feature_id
|
|
""",
|
|
),
|
|
]
|
|
|
|
|
|
def mysql_stream(query: str):
|
|
proc = subprocess.Popen(
|
|
MYSQL_BASE + ["-e", query],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
bufsize=1,
|
|
)
|
|
assert proc.stdout is not None
|
|
assert proc.stderr is not None
|
|
try:
|
|
for raw_line in proc.stdout:
|
|
line = raw_line.rstrip("\n")
|
|
if not line:
|
|
continue
|
|
yield line.split("\t")
|
|
finally:
|
|
rc = proc.wait()
|
|
if rc != 0:
|
|
raise RuntimeError(proc.stderr.read().strip())
|
|
|
|
|
|
def classify(source_tag: str, canonical_object_type: str, capability: str | None) -> tuple[int, int, int, int, int, int, int, int, int]:
|
|
capability = capability or ""
|
|
is_land = 1 if canonical_object_type == "P陸域" else 0
|
|
is_hole = 1 if canonical_object_type == "P穴" else 0
|
|
is_collision = 1 if source_tag == "collision" or capability == "collision" else 0
|
|
is_grounding = 1 if source_tag == "grounding" or capability == "grounding" else 0
|
|
is_navmark = 1 if source_tag == "navmark" or capability == "navigation_mark" else 0
|
|
is_boundary = 1 if source_tag == "boundary" or capability == "boundary_reference" else 0
|
|
is_route = 1 if source_tag == "route" or capability == "route_reference" else 0
|
|
is_anchorage = 1 if capability == "anchorage_reference" else 0
|
|
is_clearance = 1 if capability == "overhead_clearance" else 0
|
|
return (
|
|
is_land,
|
|
is_hole,
|
|
is_collision,
|
|
is_grounding,
|
|
is_navmark,
|
|
is_boundary,
|
|
is_route,
|
|
is_anchorage,
|
|
is_clearance,
|
|
)
|
|
|
|
|
|
def open_db() -> sqlite3.Connection:
|
|
if OUT.exists():
|
|
OUT.unlink()
|
|
OUT.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
conn = sqlite3.connect(OUT)
|
|
conn.execute("PRAGMA journal_mode=OFF")
|
|
conn.execute("PRAGMA synchronous=OFF")
|
|
conn.execute("PRAGMA temp_store=MEMORY")
|
|
conn.execute("PRAGMA cache_size=200000")
|
|
conn.execute("PRAGMA locking_mode=EXCLUSIVE")
|
|
conn.execute("PRAGMA foreign_keys=OFF")
|
|
return conn
|
|
|
|
|
|
def create_schema(conn: sqlite3.Connection) -> None:
|
|
conn.executescript(
|
|
"""
|
|
CREATE TABLE meta (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE semantic_object (
|
|
feature_id INTEGER PRIMARY KEY,
|
|
fid TEXT,
|
|
source_table TEXT NOT NULL,
|
|
source_layer TEXT,
|
|
canonical_object_type TEXT NOT NULL,
|
|
object_family TEXT NOT NULL,
|
|
detection_class TEXT NOT NULL,
|
|
capability TEXT,
|
|
detection_key TEXT,
|
|
class_name TEXT,
|
|
shape_name TEXT,
|
|
layer_name TEXT,
|
|
z INTEGER,
|
|
x INTEGER,
|
|
y INTEGER,
|
|
geom_type TEXT,
|
|
is_land INTEGER NOT NULL DEFAULT 0,
|
|
is_hole INTEGER NOT NULL DEFAULT 0,
|
|
is_collision INTEGER NOT NULL DEFAULT 0,
|
|
is_grounding INTEGER NOT NULL DEFAULT 0,
|
|
is_navmark INTEGER NOT NULL DEFAULT 0,
|
|
is_boundary INTEGER NOT NULL DEFAULT 0,
|
|
is_route INTEGER NOT NULL DEFAULT 0,
|
|
is_anchorage INTEGER NOT NULL DEFAULT 0,
|
|
is_clearance INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE INDEX idx_semantic_object_fid ON semantic_object(fid);
|
|
CREATE INDEX idx_semantic_object_tile ON semantic_object(z, x, y);
|
|
CREATE INDEX idx_semantic_object_capability ON semantic_object(capability, canonical_object_type);
|
|
CREATE INDEX idx_semantic_object_family ON semantic_object(object_family, detection_class);
|
|
|
|
CREATE TABLE grid_cell (
|
|
tile_key TEXT PRIMARY KEY,
|
|
z INTEGER NOT NULL,
|
|
x INTEGER NOT NULL,
|
|
y INTEGER NOT NULL,
|
|
land_count INTEGER NOT NULL DEFAULT 0,
|
|
hole_count INTEGER NOT NULL DEFAULT 0,
|
|
collision_count INTEGER NOT NULL DEFAULT 0,
|
|
grounding_count INTEGER NOT NULL DEFAULT 0,
|
|
navmark_count INTEGER NOT NULL DEFAULT 0,
|
|
boundary_count INTEGER NOT NULL DEFAULT 0,
|
|
route_count INTEGER NOT NULL DEFAULT 0,
|
|
anchorage_count INTEGER NOT NULL DEFAULT 0,
|
|
clearance_count INTEGER NOT NULL DEFAULT 0,
|
|
blocked INTEGER NOT NULL DEFAULT 0,
|
|
blocked_reason TEXT
|
|
);
|
|
|
|
CREATE INDEX idx_grid_cell_zxy ON grid_cell(z, x, y);
|
|
CREATE INDEX idx_grid_cell_blocked ON grid_cell(blocked, z);
|
|
"""
|
|
)
|
|
conn.executemany(
|
|
"INSERT INTO meta(key, value) VALUES (?, ?)",
|
|
[
|
|
("source_db", DB_NAME),
|
|
("source_scope", "九州"),
|
|
("build_time", dt.datetime.now().isoformat(timespec="seconds")),
|
|
("notes", "移动端轻量版语义索引库,保留对象索引和格网占用"),
|
|
(
|
|
"source_filter",
|
|
"land/hole + collision/grounding/navigation_mark/boundary_reference/route_reference",
|
|
),
|
|
],
|
|
)
|
|
conn.commit()
|
|
|
|
|
|
def ingest_query(
|
|
conn: sqlite3.Connection,
|
|
source_tag: str,
|
|
query: str,
|
|
seen: dict[int, tuple],
|
|
cells: dict[str, dict[str, int]],
|
|
batch_size: int = 2000,
|
|
) -> int:
|
|
obj_batch: list[tuple] = []
|
|
inserted = 0
|
|
|
|
for cols in mysql_stream(query):
|
|
if len(cols) != 15:
|
|
raise RuntimeError(f"unexpected column count for {source_tag}: {len(cols)}")
|
|
|
|
(
|
|
feature_id,
|
|
fid,
|
|
source_layer,
|
|
canonical_object_type,
|
|
object_family,
|
|
detection_class,
|
|
capability,
|
|
detection_key,
|
|
class_name,
|
|
shape_name,
|
|
layer_name,
|
|
z,
|
|
x,
|
|
y,
|
|
geom_type,
|
|
) = cols
|
|
|
|
feature_id_i = int(feature_id)
|
|
z_i = int(z) if z else 0
|
|
x_i = int(x) if x else 0
|
|
y_i = int(y) if y else 0
|
|
flags = classify(source_tag, canonical_object_type, capability)
|
|
|
|
prev = seen.get(feature_id_i)
|
|
row = (
|
|
feature_id_i,
|
|
fid or None,
|
|
source_tag,
|
|
source_layer,
|
|
canonical_object_type,
|
|
object_family,
|
|
detection_class,
|
|
capability,
|
|
detection_key,
|
|
class_name,
|
|
shape_name,
|
|
layer_name,
|
|
z_i,
|
|
x_i,
|
|
y_i,
|
|
geom_type,
|
|
*flags,
|
|
)
|
|
|
|
if prev is None:
|
|
seen[feature_id_i] = row
|
|
obj_batch.append(row)
|
|
inserted += 1
|
|
else:
|
|
merged = list(prev)
|
|
for idx, flag in enumerate(flags, start=16):
|
|
merged[idx] = int(merged[idx]) or int(flag)
|
|
seen[feature_id_i] = tuple(merged)
|
|
|
|
tile_key = f"{z_i}/{x_i}/{y_i}"
|
|
counts = cells.get(tile_key)
|
|
if counts is None:
|
|
counts = {
|
|
"z": z_i,
|
|
"x": x_i,
|
|
"y": y_i,
|
|
"land_count": 0,
|
|
"hole_count": 0,
|
|
"collision_count": 0,
|
|
"grounding_count": 0,
|
|
"navmark_count": 0,
|
|
"boundary_count": 0,
|
|
"route_count": 0,
|
|
"anchorage_count": 0,
|
|
"clearance_count": 0,
|
|
}
|
|
cells[tile_key] = counts
|
|
counts["land_count"] += flags[0]
|
|
counts["hole_count"] += flags[1]
|
|
counts["collision_count"] += flags[2]
|
|
counts["grounding_count"] += flags[3]
|
|
counts["navmark_count"] += flags[4]
|
|
counts["boundary_count"] += flags[5]
|
|
counts["route_count"] += flags[6]
|
|
counts["anchorage_count"] += flags[7]
|
|
counts["clearance_count"] += flags[8]
|
|
|
|
if len(obj_batch) >= batch_size:
|
|
conn.executemany(
|
|
"""
|
|
INSERT OR REPLACE INTO semantic_object(
|
|
feature_id, fid, source_table, source_layer, canonical_object_type,
|
|
object_family, detection_class, capability, detection_key, class_name,
|
|
shape_name, layer_name, z, x, y, geom_type, is_land, is_hole,
|
|
is_collision, is_grounding, is_navmark, is_boundary, is_route,
|
|
is_anchorage, is_clearance
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
obj_batch,
|
|
)
|
|
conn.commit()
|
|
obj_batch.clear()
|
|
print(f"[{source_tag}] 已写入 {inserted} 条对象")
|
|
|
|
if obj_batch:
|
|
conn.executemany(
|
|
"""
|
|
INSERT OR REPLACE INTO semantic_object(
|
|
feature_id, fid, source_table, source_layer, canonical_object_type,
|
|
object_family, detection_class, capability, detection_key, class_name,
|
|
shape_name, layer_name, z, x, y, geom_type, is_land, is_hole,
|
|
is_collision, is_grounding, is_navmark, is_boundary, is_route,
|
|
is_anchorage, is_clearance
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
obj_batch,
|
|
)
|
|
conn.commit()
|
|
|
|
return inserted
|
|
|
|
|
|
def finalize_grid(conn: sqlite3.Connection, cells: dict[str, dict[str, int]]) -> int:
|
|
rows = []
|
|
for tile_key, c in cells.items():
|
|
blocked = 0
|
|
reason = None
|
|
if c["land_count"] > c["hole_count"] and c["land_count"] > 0:
|
|
blocked = 1
|
|
reason = "land"
|
|
elif c["collision_count"] > 0:
|
|
blocked = 1
|
|
reason = "collision"
|
|
elif c["grounding_count"] > 0:
|
|
blocked = 1
|
|
reason = "grounding"
|
|
elif c["clearance_count"] > 0:
|
|
blocked = 1
|
|
reason = "clearance"
|
|
|
|
rows.append(
|
|
(
|
|
tile_key,
|
|
c["z"],
|
|
c["x"],
|
|
c["y"],
|
|
c["land_count"],
|
|
c["hole_count"],
|
|
c["collision_count"],
|
|
c["grounding_count"],
|
|
c["navmark_count"],
|
|
c["boundary_count"],
|
|
c["route_count"],
|
|
c["anchorage_count"],
|
|
c["clearance_count"],
|
|
blocked,
|
|
reason,
|
|
)
|
|
)
|
|
|
|
conn.executemany(
|
|
"""
|
|
INSERT INTO grid_cell(
|
|
tile_key, z, x, y, land_count, hole_count, collision_count, grounding_count,
|
|
navmark_count, boundary_count, route_count, anchorage_count, clearance_count,
|
|
blocked, blocked_reason
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
rows,
|
|
)
|
|
conn.commit()
|
|
return len(rows)
|
|
|
|
|
|
def main() -> None:
|
|
conn = open_db()
|
|
create_schema(conn)
|
|
|
|
seen: dict[int, tuple] = {}
|
|
cells: dict[str, dict[str, int]] = {}
|
|
total = 0
|
|
|
|
for source_tag, query in QUERIES:
|
|
print(f"开始导出 {source_tag} ...")
|
|
total += ingest_query(conn, source_tag, query, seen, cells)
|
|
print(f"{source_tag} 完成,累计对象 {total}")
|
|
|
|
cell_total = finalize_grid(conn, cells)
|
|
conn.execute("ANALYZE")
|
|
conn.commit()
|
|
|
|
obj_count = conn.execute("SELECT COUNT(*) FROM semantic_object").fetchone()[0]
|
|
cell_count = conn.execute("SELECT COUNT(*) FROM grid_cell").fetchone()[0]
|
|
conn.close()
|
|
|
|
print(f"完成: objects={obj_count}, cells={cell_count}, grid_rows={cell_total}")
|
|
print(f"输出文件: {OUT.resolve()}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|