Initial import of NavSea pbf project
This commit is contained in:
434
navsea_detection_pipeline.py
Normal file
434
navsea_detection_pipeline.py
Normal file
@@ -0,0 +1,434 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
import pymysql
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DbConfig:
|
||||
host: str = os.getenv("NAVSEA_DB_HOST", "localhost")
|
||||
port: int = int(os.getenv("NAVSEA_DB_PORT", "3306"))
|
||||
user: str = os.getenv("NAVSEA_DB_USER", "root")
|
||||
password: str = os.getenv("NAVSEA_DB_PASSWORD", "2chi9ks2")
|
||||
database: str = os.getenv("NAVSEA_DB_NAME", "pbf_analysis")
|
||||
unix_socket: str | None = os.getenv("NAVSEA_DB_SOCKET", "/tmp/mysql.sock")
|
||||
|
||||
|
||||
FAMILY_DEFAULT_CAPABILITIES = {
|
||||
"hazard": {"collision"},
|
||||
"navigation_aid": {"navigation_mark"},
|
||||
"bathymetry": {"depth_reference"},
|
||||
"seabed": {"bottom_reference"},
|
||||
"fishery": {"entangle"},
|
||||
"route": {"route_reference"},
|
||||
"boundary": {"boundary_reference"},
|
||||
"anchorage": {"anchorage_reference"},
|
||||
"restricted_area": {"boundary_reference"},
|
||||
"infrastructure": {"structure_reference"},
|
||||
"utility": {"utility_hazard"},
|
||||
"landmark": {"landmark_reference"},
|
||||
"place": {"place_reference"},
|
||||
"water_area": {"water_reference"},
|
||||
"monitoring": {"traffic_monitoring"},
|
||||
"overlay_support": {"render_support"},
|
||||
"unknown": {"review_required"},
|
||||
}
|
||||
|
||||
|
||||
class DetectionPipeline:
|
||||
def __init__(self, config: DbConfig) -> None:
|
||||
self.config = config
|
||||
|
||||
def connect(self):
|
||||
kwargs = {
|
||||
"host": self.config.host,
|
||||
"port": self.config.port,
|
||||
"user": self.config.user,
|
||||
"password": self.config.password,
|
||||
"database": self.config.database,
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
}
|
||||
if self.config.unix_socket and self.config.host in {"localhost", "127.0.0.1"}:
|
||||
kwargs["unix_socket"] = self.config.unix_socket
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
def run(self) -> None:
|
||||
with self.connect() as conn:
|
||||
with conn.cursor() as cur:
|
||||
object_types = self.fetch_object_types(cur)
|
||||
taxonomy_rows = [self.classify_object_type(name) for name in object_types]
|
||||
capability_rows = self.build_capabilities(taxonomy_rows)
|
||||
self.create_taxonomy_table(cur, taxonomy_rows)
|
||||
self.create_capabilities_table(cur, capability_rows)
|
||||
self.create_detection_view(cur)
|
||||
self.create_capability_views(cur)
|
||||
self.ensure_query_indexes(cur)
|
||||
conn.commit()
|
||||
print(
|
||||
"Built navsea_object_taxonomy, navsea_object_capabilities, "
|
||||
"navsea_detection_objects, capability views and supporting query indexes"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def fetch_object_types(cur) -> list[str]:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT canonical_object_type
|
||||
FROM canonical_object_rules
|
||||
ORDER BY canonical_object_type
|
||||
"""
|
||||
)
|
||||
return [row[0] for row in cur.fetchall()]
|
||||
|
||||
@staticmethod
|
||||
def is_depth_band(name: str) -> bool:
|
||||
compact = name.replace(" ", "")
|
||||
return compact.endswith("m") or "m以深" in compact
|
||||
|
||||
@staticmethod
|
||||
def classify_object_type(name: str) -> tuple[str, str, str, str]:
|
||||
if name.startswith(("P", "L", "p")) and (
|
||||
name.endswith("ククリ")
|
||||
or name in {
|
||||
"P穴",
|
||||
"P陸域",
|
||||
"P危険界ククリ",
|
||||
"P投錨注意障害物ククリ",
|
||||
"P施設・境界線等ククリ",
|
||||
"P航路ククリ",
|
||||
"P錨泊地等ククリ",
|
||||
"P754ククリ",
|
||||
"P721ククリ",
|
||||
"P730ククリ",
|
||||
"L海底地形",
|
||||
"p地名",
|
||||
"p地名陸",
|
||||
"p高さ制限",
|
||||
}
|
||||
):
|
||||
return (
|
||||
name,
|
||||
"overlay_support",
|
||||
"render_support",
|
||||
"保留原始 source_layer 的叠加支持对象,不作为独立语义实体替换原始渲染层。",
|
||||
)
|
||||
|
||||
if DetectionPipeline.is_depth_band(name):
|
||||
return (name, "bathymetry", "depth_zone", "水深分带对象,用于浅滩、深度范围和搁浅风险分析。")
|
||||
|
||||
if any(token in name for token in ("等深線",)):
|
||||
return (name, "bathymetry", "depth_contour", "等深线对象,用于水深变化与航线安全分析。")
|
||||
|
||||
if any(token in name for token in ("底質", "海底地形", "サンドウェーブ", "海底火山")):
|
||||
return (name, "seabed", "seabed_feature", "海底形态或底质对象,用于海底环境识别。")
|
||||
|
||||
if any(
|
||||
token in name
|
||||
for token in (
|
||||
"険悪物",
|
||||
"障害物",
|
||||
"危険物",
|
||||
"危険全沈没船",
|
||||
"沈船",
|
||||
"全沈没船",
|
||||
"沈木",
|
||||
"暗岩",
|
||||
"洗岩",
|
||||
"干出岩",
|
||||
"水上岩",
|
||||
"孤立危険物",
|
||||
"魚礁",
|
||||
"浅所危険界",
|
||||
"撤去跡",
|
||||
"掃海済み",
|
||||
"サンゴ礁",
|
||||
)
|
||||
):
|
||||
return (name, "hazard", "obstacle", "对航行存在碰撞或搁浅风险的危险物对象。")
|
||||
|
||||
if any(
|
||||
token in name
|
||||
for token in (
|
||||
"灯",
|
||||
"灯台",
|
||||
"灯標",
|
||||
"灯浮標",
|
||||
"浮標",
|
||||
"立標",
|
||||
"導灯",
|
||||
"指向灯",
|
||||
"V-AIS",
|
||||
"管制信号所",
|
||||
)
|
||||
):
|
||||
return (name, "navigation_aid", "navigation_mark", "航标、灯标或导助航对象。")
|
||||
|
||||
if any(token in name for token in ("航路", "進路矢印", "分離通航方式", "誘導線", "指導線")):
|
||||
return (name, "route", "route_reference", "航路、导向线或通航组织对象。")
|
||||
|
||||
if any(
|
||||
token in name
|
||||
for token in (
|
||||
"境界",
|
||||
"危険界",
|
||||
"境界線",
|
||||
"制限区域",
|
||||
"専用用途海域",
|
||||
"航泊禁止区域",
|
||||
"錨泊禁止区域",
|
||||
"航空機進入区域",
|
||||
)
|
||||
):
|
||||
return (name, "boundary", "boundary_control", "边界、限制区或规则控制对象。")
|
||||
|
||||
if "錨泊" in name or "錨地" in name:
|
||||
return (name, "anchorage", "anchorage", "锚地或锚泊控制对象。")
|
||||
|
||||
if any(token in name for token in ("漁", "養殖場", "漁網", "海草")):
|
||||
return (name, "fishery", "fishery_area", "渔业、养殖或缠绕风险相关对象。")
|
||||
|
||||
if any(
|
||||
token in name
|
||||
for token in (
|
||||
"防波堤",
|
||||
"潜提",
|
||||
"桟橋",
|
||||
"海上バース",
|
||||
"ドルフィン",
|
||||
"ポンツーン",
|
||||
"橋",
|
||||
"道路",
|
||||
"ビル",
|
||||
"タンク",
|
||||
"煙突",
|
||||
"塔",
|
||||
"やぐら",
|
||||
"ケーソン",
|
||||
"土砂捨て場",
|
||||
"石油開発台",
|
||||
)
|
||||
):
|
||||
return (name, "infrastructure", "structure", "港口、岸线或陆上海工构造物对象。")
|
||||
|
||||
if any(token in name for token in ("海底線", "輸送管", "送電線", "架空線", "放水口", "取水口")):
|
||||
return (name, "utility", "utility_line", "海底线缆、管道或公用设施对象。")
|
||||
|
||||
if any(token in name for token in ("河川域", "湖沼域", "陸上水域", "干潮帯", "未測海域", "平水境界")):
|
||||
return (name, "water_area", "water_area", "水域、潮滩或未测区域对象。")
|
||||
|
||||
if any(
|
||||
token in name
|
||||
for token in (
|
||||
"その他 (記念碑等)",
|
||||
"陸上顕著物",
|
||||
"地名",
|
||||
"山頂",
|
||||
"港湾",
|
||||
"漁港",
|
||||
"マリーナ",
|
||||
"フィッシャリーナ",
|
||||
"海の駅",
|
||||
"漁業協同組合",
|
||||
"海事関係署",
|
||||
"税関",
|
||||
"パイロットステーション",
|
||||
)
|
||||
):
|
||||
return (name, "place", "place_label", "地名、显著地物或港区服务设施类参考对象。")
|
||||
|
||||
if any(token in name for token in ("廃棄物捨て場", "貯木場")):
|
||||
return (name, "infrastructure", "structure", "作业、堆置或弃置区域类设施对象。")
|
||||
|
||||
if any(token in name for token in ("無線局", "レーダー局")):
|
||||
return (name, "monitoring", "monitoring_station", "通信、雷达或监测设施对象。")
|
||||
|
||||
if any(token in name for token in ("急潮", "波紋", "激潮", "渦流", "水源方向")):
|
||||
return (name, "hazard", "current_hazard", "流态、水流或潮流风险相关对象。")
|
||||
|
||||
return (name, "unknown", "review_required", "尚未细化归类的对象,需要人工复核。")
|
||||
|
||||
@staticmethod
|
||||
def build_capabilities(
|
||||
taxonomy_rows: list[tuple[str, str, str, str]]
|
||||
) -> list[tuple[str, str]]:
|
||||
capability_rows: set[tuple[str, str]] = set()
|
||||
|
||||
for canonical_object_type, object_family, detection_class, _ in taxonomy_rows:
|
||||
capabilities = set(FAMILY_DEFAULT_CAPABILITIES.get(object_family, {"review_required"}))
|
||||
|
||||
if detection_class in {"obstacle"}:
|
||||
capabilities.add("collision")
|
||||
if detection_class in {"depth_zone", "depth_contour"}:
|
||||
capabilities.add("grounding")
|
||||
if "魚礁" in canonical_object_type or "浅所" in canonical_object_type or "干潮帯" in canonical_object_type:
|
||||
capabilities.add("grounding")
|
||||
if any(token in canonical_object_type for token in ("漁", "養殖場", "漁網", "海草")):
|
||||
capabilities.add("entangle")
|
||||
if any(token in canonical_object_type for token in ("防波堤",)):
|
||||
capabilities.add("wave_barrier")
|
||||
if any(token in canonical_object_type for token in ("架空線", "送電線", "高さ制限")):
|
||||
capabilities.add("overhead_clearance")
|
||||
if any(token in canonical_object_type for token in ("航路", "誘導線", "進路矢印", "分離通航方式")):
|
||||
capabilities.add("route_reference")
|
||||
if any(token in canonical_object_type for token in ("錨泊", "錨地")):
|
||||
capabilities.add("anchorage_reference")
|
||||
if any(token in canonical_object_type for token in ("境界", "区域", "危険界")):
|
||||
capabilities.add("boundary_reference")
|
||||
if any(token in canonical_object_type for token in ("海底線", "輸送管")):
|
||||
capabilities.add("snag_risk")
|
||||
if any(token in canonical_object_type for token in ("灯", "標", "浮標", "導灯", "V-AIS", "管制信号所")):
|
||||
capabilities.add("navigation_mark")
|
||||
if object_family in {"place", "infrastructure", "monitoring"}:
|
||||
capabilities.add("landmark_reference")
|
||||
|
||||
for capability in sorted(capabilities):
|
||||
capability_rows.add((canonical_object_type, capability))
|
||||
|
||||
return sorted(capability_rows)
|
||||
|
||||
@staticmethod
|
||||
def create_taxonomy_table(cur, rows: list[tuple[str, str, str, str]]) -> None:
|
||||
cur.execute("DROP TABLE IF EXISTS navsea_object_taxonomy")
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE navsea_object_taxonomy (
|
||||
canonical_object_type VARCHAR(191) NOT NULL,
|
||||
object_family VARCHAR(64) NOT NULL,
|
||||
detection_class VARCHAR(64) NOT NULL,
|
||||
description TEXT NULL,
|
||||
PRIMARY KEY (canonical_object_type),
|
||||
KEY idx_family_class (object_family, detection_class)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
"""
|
||||
)
|
||||
cur.executemany(
|
||||
"""
|
||||
INSERT INTO navsea_object_taxonomy (
|
||||
canonical_object_type,
|
||||
object_family,
|
||||
detection_class,
|
||||
description
|
||||
)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
""",
|
||||
rows,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_capabilities_table(cur, rows: list[tuple[str, str]]) -> None:
|
||||
cur.execute("DROP TABLE IF EXISTS navsea_object_capabilities")
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE navsea_object_capabilities (
|
||||
canonical_object_type VARCHAR(191) NOT NULL,
|
||||
capability VARCHAR(64) NOT NULL,
|
||||
PRIMARY KEY (canonical_object_type, capability),
|
||||
KEY idx_capability (capability, canonical_object_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
"""
|
||||
)
|
||||
cur.executemany(
|
||||
"""
|
||||
INSERT INTO navsea_object_capabilities (
|
||||
canonical_object_type,
|
||||
capability
|
||||
)
|
||||
VALUES (%s, %s)
|
||||
""",
|
||||
rows,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_detection_view(cur) -> None:
|
||||
cur.execute("DROP VIEW IF EXISTS navsea_detection_objects")
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE VIEW navsea_detection_objects AS
|
||||
SELECT
|
||||
r.feature_id,
|
||||
CAST(NULL AS CHAR(1)) AS geometry,
|
||||
r.z,
|
||||
r.x,
|
||||
r.y,
|
||||
r.geom_type,
|
||||
r.source_layer,
|
||||
r.class_name,
|
||||
r.shape_name,
|
||||
r.layer_name,
|
||||
t.canonical_object_type,
|
||||
t.object_family,
|
||||
t.detection_class,
|
||||
c.capability,
|
||||
r.detection_key
|
||||
FROM pbf_relayer_candidates r
|
||||
JOIN navsea_object_taxonomy t
|
||||
ON r.canonical_object_type = t.canonical_object_type
|
||||
LEFT JOIN navsea_object_capabilities c
|
||||
ON r.canonical_object_type = c.canonical_object_type
|
||||
"""
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_capability_views(cur) -> None:
|
||||
views = {
|
||||
"navsea_collision_objects": "collision",
|
||||
"navsea_grounding_objects": "grounding",
|
||||
"navsea_entangle_objects": "entangle",
|
||||
"navsea_navigation_mark_objects": "navigation_mark",
|
||||
"navsea_route_reference_objects": "route_reference",
|
||||
"navsea_boundary_reference_objects": "boundary_reference",
|
||||
}
|
||||
for view_name, capability in views.items():
|
||||
cur.execute(f"DROP VIEW IF EXISTS {view_name}")
|
||||
cur.execute(
|
||||
f"""
|
||||
CREATE VIEW {view_name} AS
|
||||
SELECT *
|
||||
FROM navsea_detection_objects
|
||||
WHERE capability = %s
|
||||
""",
|
||||
(capability,),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def add_index_if_missing(cur, table: str, index_name: str, ddl: str) -> None:
|
||||
cur.execute(f"SHOW INDEX FROM {table} WHERE Key_name=%s", (index_name,))
|
||||
if cur.fetchone() is None:
|
||||
cur.execute(ddl)
|
||||
|
||||
def ensure_query_indexes(self, cur) -> None:
|
||||
self.add_index_if_missing(
|
||||
cur,
|
||||
"pbf_relayer_candidates",
|
||||
"idx_navsea_tile",
|
||||
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_tile (z, x, y)",
|
||||
)
|
||||
self.add_index_if_missing(
|
||||
cur,
|
||||
"pbf_relayer_candidates",
|
||||
"idx_navsea_object",
|
||||
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_object (canonical_object_type(100), geom_type)",
|
||||
)
|
||||
self.add_index_if_missing(
|
||||
cur,
|
||||
"pbf_relayer_candidates",
|
||||
"idx_navsea_detection_key",
|
||||
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_detection_key (detection_key(64))",
|
||||
)
|
||||
self.add_index_if_missing(
|
||||
cur,
|
||||
"pbf_relayer_candidates",
|
||||
"idx_navsea_source_layer",
|
||||
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_source_layer (source_layer(100), geom_type)",
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
DetectionPipeline(DbConfig()).run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user