Initial commit — QGIS S-57 Converter
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# PyInstaller — carpeta de build intermedia (no necesaria)
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
build_log.txt
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
Binary file not shown.
+127
@@ -0,0 +1,127 @@
|
|||||||
|
========================================
|
||||||
|
QGIS → S-57 Converter — Installation
|
||||||
|
========================================
|
||||||
|
|
||||||
|
REQUIRED: Python 3.9+
|
||||||
|
REQUIRED: GDAL with S-57 support
|
||||||
|
|
||||||
|
─────────────────────────────────────────
|
||||||
|
OPTION A: Windows — Easiest (OSGeo4W)
|
||||||
|
─────────────────────────────────────────
|
||||||
|
1. Download OSGeo4W: https://trac.osgeo.org/osgeo4w/
|
||||||
|
2. Run installer → Advanced Install → Select:
|
||||||
|
- gdal
|
||||||
|
- python3-gdal
|
||||||
|
- python3-geopandas (or install via pip)
|
||||||
|
3. Use the "OSGeo4W Shell" to run the converter:
|
||||||
|
cd "D:\Proyectos Software\QGISS57Converter"
|
||||||
|
python converter.py myproject.qgs
|
||||||
|
|
||||||
|
─────────────────────────────────────────
|
||||||
|
OPTION B: Conda / Mamba (Recommended)
|
||||||
|
─────────────────────────────────────────
|
||||||
|
conda create -n s57 python=3.11
|
||||||
|
conda activate s57
|
||||||
|
conda install -c conda-forge gdal geopandas
|
||||||
|
pip install lxml
|
||||||
|
cd "D:\Proyectos Software\QGISS57Converter"
|
||||||
|
python converter.py myproject.qgs
|
||||||
|
|
||||||
|
─────────────────────────────────────────
|
||||||
|
OPTION C: QGIS Python environment
|
||||||
|
─────────────────────────────────────────
|
||||||
|
QGIS already includes GDAL. Run from QGIS Python console:
|
||||||
|
import sys
|
||||||
|
sys.path.append(r"D:\Proyectos Software\QGISS57Converter")
|
||||||
|
from converter import convert
|
||||||
|
convert("myproject.qgs", None, None, False, True, True)
|
||||||
|
|
||||||
|
─────────────────────────────────────────
|
||||||
|
INSTALL other dependencies
|
||||||
|
─────────────────────────────────────────
|
||||||
|
pip install geopandas lxml pyproj
|
||||||
|
|
||||||
|
─────────────────────────────────────────
|
||||||
|
VERIFY installation
|
||||||
|
─────────────────────────────────────────
|
||||||
|
python -c "from osgeo import gdal; drv = gdal.GetDriverByName('S57'); print('S-57 driver:', drv.GetDescription() if drv else 'NOT AVAILABLE')"
|
||||||
|
|
||||||
|
========================================
|
||||||
|
USAGE
|
||||||
|
========================================
|
||||||
|
|
||||||
|
# List layers and their S-57 mapping:
|
||||||
|
python converter.py project.qgs --list
|
||||||
|
|
||||||
|
# Convert:
|
||||||
|
python converter.py project.qgs
|
||||||
|
|
||||||
|
# Custom output name:
|
||||||
|
python converter.py project.qgs --output CO1CO01M.000
|
||||||
|
|
||||||
|
# Custom config:
|
||||||
|
python converter.py project.qgs --config my_config.json
|
||||||
|
|
||||||
|
# No prompts (convert all mapped layers):
|
||||||
|
python converter.py project.qgs --force
|
||||||
|
|
||||||
|
========================================
|
||||||
|
LAYER NAMING — IMPORTANT
|
||||||
|
========================================
|
||||||
|
|
||||||
|
You have THREE options for mapping your QGIS layers to S-57:
|
||||||
|
|
||||||
|
OPTION 1 (easiest): Name your layers directly with S-57 acronyms
|
||||||
|
- Layer named "COALNE" → coastline
|
||||||
|
- Layer named "DEPARE" → depth area
|
||||||
|
- Layer named "SOUNDG" → soundings
|
||||||
|
See s57_objects.json for all available object classes.
|
||||||
|
|
||||||
|
OPTION 2: Name layers in Spanish or English (auto-detected)
|
||||||
|
- Layer named "costa" or "coastline" → auto → COALNE
|
||||||
|
- Layer named "fondos" or "batimetria" → auto → DEPARE
|
||||||
|
- Layer named "sondas" → auto → SOUNDG
|
||||||
|
See cell_config.json → "layer_mappings" for full list.
|
||||||
|
|
||||||
|
OPTION 3: Add custom mappings to cell_config.json
|
||||||
|
"layer_mappings": {
|
||||||
|
"mi_capa_costa": "COALNE",
|
||||||
|
"datos_profundidad": "DEPARE"
|
||||||
|
}
|
||||||
|
|
||||||
|
========================================
|
||||||
|
CELL NAMING CONVENTION (IHO S-57)
|
||||||
|
========================================
|
||||||
|
Format: CC1AA##X.000
|
||||||
|
CC = 2-letter country code (CO=Colombia, US=USA, etc.)
|
||||||
|
1 = Navigational Purpose (1=overview...6=berthing)
|
||||||
|
AA = 2-letter area code
|
||||||
|
## = serial number (01, 02...)
|
||||||
|
X = compilation scale indicator (M=medium)
|
||||||
|
|
||||||
|
Example: CO3CA01M.000 = Colombia, scale 1:50000, Caribbean area
|
||||||
|
|
||||||
|
Edit "cell_name" in cell_config.json accordingly.
|
||||||
|
|
||||||
|
========================================
|
||||||
|
COMMON S-57 OBJECT CLASSES
|
||||||
|
========================================
|
||||||
|
COALNE Coastline (lines)
|
||||||
|
LNDARE Land Area (polygons)
|
||||||
|
DEPARE Depth Area (polygons) — needs DRVAL1, DRVAL2 attributes
|
||||||
|
DEPCNT Depth Contour (lines) — needs VALDCO attribute
|
||||||
|
SOUNDG Soundings (points) — needs VALSOU (depth value) attribute
|
||||||
|
LIGHTS Lights (points)
|
||||||
|
BUOYLAT Lateral Buoy (points)
|
||||||
|
BCNLAT Lateral Beacon (points)
|
||||||
|
ACHARE Anchorage Area (polygons)
|
||||||
|
HRBARE Harbour Area (polygons)
|
||||||
|
BERTHS Berth (polygons/lines)
|
||||||
|
OBSTRN Obstruction (any)
|
||||||
|
WRECKS Wreck (polygons/points)
|
||||||
|
FAIRWY Fairway (polygons)
|
||||||
|
RESARE Restricted Area (polygons)
|
||||||
|
RIVERS River (polygons/lines)
|
||||||
|
M_COVR Coverage (polygons) — required in valid ENCs
|
||||||
|
|
||||||
|
See s57_objects.json for complete list.
|
||||||
+695
@@ -0,0 +1,695 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>QGISS57Converter — Manual</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #0d1117; --panel: #161b22; --border: #30363d;
|
||||||
|
--accent: #1e7fc8; --accent2: #388bfd;
|
||||||
|
--green: #2ea043; --yellow: #d29922; --red: #da3633;
|
||||||
|
--text: #e6edf3; --dim: #8b949e; --mono: #f0a500;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { background: var(--bg); color: var(--text); font-family: "Segoe UI", system-ui, sans-serif; font-size: 14px; }
|
||||||
|
|
||||||
|
/* ── layout ── */
|
||||||
|
#sidebar { position: fixed; top: 0; left: 0; width: 240px; height: 100vh; background: var(--panel);
|
||||||
|
border-right: 1px solid var(--border); overflow-y: auto; padding: 16px 0; z-index: 10; }
|
||||||
|
#content { margin-left: 240px; padding: 32px 40px; max-width: 960px; }
|
||||||
|
|
||||||
|
/* ── header ── */
|
||||||
|
.app-title { padding: 0 16px 16px; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
|
||||||
|
.app-title h2 { color: var(--accent2); font-size: 15px; font-weight: 700; }
|
||||||
|
.app-title p { color: var(--dim); font-size: 11px; margin-top: 2px; }
|
||||||
|
|
||||||
|
/* ── search ── */
|
||||||
|
#search-box { margin: 8px 12px 12px; width: calc(100% - 24px);
|
||||||
|
background: var(--bg); border: 1px solid var(--border);
|
||||||
|
color: var(--text); padding: 6px 10px; border-radius: 6px; font-size: 12px; }
|
||||||
|
#search-box:focus { outline: none; border-color: var(--accent); }
|
||||||
|
|
||||||
|
/* ── nav ── */
|
||||||
|
.nav-section { padding: 6px 16px 2px; font-size: 11px; font-weight: 700;
|
||||||
|
color: var(--dim); text-transform: uppercase; letter-spacing: .8px; }
|
||||||
|
.nav-link { display: block; padding: 5px 16px; color: var(--dim); text-decoration: none;
|
||||||
|
font-size: 13px; border-left: 2px solid transparent; transition: all .15s; }
|
||||||
|
.nav-link:hover, .nav-link.active { color: var(--text); border-left-color: var(--accent); background: rgba(56,139,253,.08); }
|
||||||
|
|
||||||
|
/* ── content ── */
|
||||||
|
section { margin-bottom: 48px; scroll-margin-top: 24px; }
|
||||||
|
h1 { font-size: 26px; font-weight: 700; color: var(--text); margin-bottom: 6px; }
|
||||||
|
h2 { font-size: 18px; font-weight: 700; color: var(--accent2); margin: 32px 0 12px;
|
||||||
|
padding-bottom: 8px; border-bottom: 1px solid var(--border); }
|
||||||
|
h3 { font-size: 14px; font-weight: 700; color: var(--text); margin: 20px 0 8px; }
|
||||||
|
p { color: var(--dim); line-height: 1.7; margin-bottom: 10px; }
|
||||||
|
code { background: var(--panel); border: 1px solid var(--border); border-radius: 4px;
|
||||||
|
padding: 1px 6px; font-family: "Consolas", monospace; font-size: 12px; color: var(--mono); }
|
||||||
|
.subtitle { color: var(--dim); font-size: 14px; margin-bottom: 28px; }
|
||||||
|
|
||||||
|
/* ── tables ── */
|
||||||
|
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; font-size: 13px; }
|
||||||
|
th { background: #1c2230; color: var(--accent2); font-weight: 600; text-align: left;
|
||||||
|
padding: 9px 12px; border-bottom: 2px solid var(--accent); }
|
||||||
|
td { padding: 7px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
||||||
|
tr:hover td { background: rgba(56,139,253,.05); }
|
||||||
|
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 700; }
|
||||||
|
.tag-point { background: #1a3a5c; color: #58a6ff; }
|
||||||
|
.tag-line { background: #1a3d2b; color: #3fb950; }
|
||||||
|
.tag-area { background: #3d2a1a; color: #f0883e; }
|
||||||
|
.tag-meta { background: #2d1f3d; color: #bc8cff; }
|
||||||
|
.badge { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 11px; }
|
||||||
|
.b-green { background: #1a3a2a; color: #3fb950; border: 1px solid #2ea043; }
|
||||||
|
.b-blue { background: #1a2d4a; color: #58a6ff; border: 1px solid #1e7fc8; }
|
||||||
|
.b-yellow { background: #3a2e10; color: #e3b341; border: 1px solid #9e6a03; }
|
||||||
|
.b-red { background: #3a1a1a; color: #f85149; border: 1px solid #da3633; }
|
||||||
|
|
||||||
|
/* ── callouts ── */
|
||||||
|
.tip, .warn, .info { border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; border-left: 3px solid; }
|
||||||
|
.tip { background: #1a3a2a; border-color: var(--green); }
|
||||||
|
.warn { background: #3a2e10; border-color: var(--yellow); }
|
||||||
|
.info { background: #1a2d4a; border-color: var(--accent); }
|
||||||
|
.tip strong, .warn strong, .info strong { display: block; margin-bottom: 4px; }
|
||||||
|
.tip strong { color: #3fb950; }
|
||||||
|
.warn strong { color: #e3b341; }
|
||||||
|
.info strong { color: #58a6ff; }
|
||||||
|
|
||||||
|
/* ── steps ── */
|
||||||
|
.steps { counter-reset: step; list-style: none; padding: 0; }
|
||||||
|
.steps li { counter-increment: step; display: flex; gap: 16px; margin-bottom: 16px; }
|
||||||
|
.steps li::before { content: counter(step); min-width: 28px; height: 28px; border-radius: 50%;
|
||||||
|
background: var(--accent); color: #fff; font-weight: 700; font-size: 13px;
|
||||||
|
display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||||
|
.steps li .step-body { padding-top: 4px; }
|
||||||
|
.steps li .step-body strong { color: var(--text); display: block; margin-bottom: 4px; }
|
||||||
|
.steps li .step-body p { margin: 0; }
|
||||||
|
|
||||||
|
/* ── colour swatches ── */
|
||||||
|
.swatch { display: inline-block; width: 14px; height: 14px; border-radius: 3px; vertical-align: middle; margin-right: 6px; border: 1px solid rgba(255,255,255,.2); }
|
||||||
|
|
||||||
|
/* ── search highlight ── */
|
||||||
|
.hl { background: rgba(210,153,34,.35); border-radius: 2px; }
|
||||||
|
.hidden-row { display: none; }
|
||||||
|
|
||||||
|
/* ── responsive ── */
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
#sidebar { display: none; }
|
||||||
|
#content { margin-left: 0; padding: 20px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- ── SIDEBAR ──────────────────────────────────────────────────────────── -->
|
||||||
|
<nav id="sidebar">
|
||||||
|
<div class="app-title">
|
||||||
|
<h2>QGISS57Converter</h2>
|
||||||
|
<p>Manual de referencia v1.0</p>
|
||||||
|
</div>
|
||||||
|
<input id="search-box" type="search" placeholder="Buscar...">
|
||||||
|
|
||||||
|
<div class="nav-section">Inicio</div>
|
||||||
|
<a class="nav-link" href="#inicio">Introduccion</a>
|
||||||
|
<a class="nav-link" href="#flujo">Flujo de trabajo</a>
|
||||||
|
|
||||||
|
<div class="nav-section">Capas</div>
|
||||||
|
<a class="nav-link" href="#puntuales">Objetos puntuales</a>
|
||||||
|
<a class="nav-link" href="#lineales">Objetos lineales</a>
|
||||||
|
<a class="nav-link" href="#areas">Objetos de area</a>
|
||||||
|
|
||||||
|
<div class="nav-section">Atributos por tipo</div>
|
||||||
|
<a class="nav-link" href="#attr-generales">Atributos generales</a>
|
||||||
|
<a class="nav-link" href="#attr-boylat">BOYLAT — Boyas laterales</a>
|
||||||
|
<a class="nav-link" href="#attr-bcnlat">BCNLAT — Balizas/faros orilla</a>
|
||||||
|
<a class="nav-link" href="#attr-boycar">BOYCAR — Boyas cardinales</a>
|
||||||
|
<a class="nav-link" href="#attr-boyisd">BOYISD — Peligro aislado</a>
|
||||||
|
<a class="nav-link" href="#attr-boyspp">BOYSPP — Marcas especiales</a>
|
||||||
|
<a class="nav-link" href="#attr-lights">LIGHTS — Luces independientes</a>
|
||||||
|
<a class="nav-link" href="#codigos">Tablas de codigos S-57</a>
|
||||||
|
|
||||||
|
<div class="nav-section">Referencia</div>
|
||||||
|
<a class="nav-link" href="#csv-directo">Formato CSV directo</a>
|
||||||
|
<a class="nav-link" href="#config">cell_config.json</a>
|
||||||
|
<a class="nav-link" href="#ejemplos">Ejemplos de SHP</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- ── CONTENT ──────────────────────────────────────────────────────────── -->
|
||||||
|
<main id="content">
|
||||||
|
|
||||||
|
<!-- INICIO -->
|
||||||
|
<section id="inicio">
|
||||||
|
<h1>Manual QGISS57Converter</h1>
|
||||||
|
<p class="subtitle">Convierte proyectos QGIS (.qgz/.qgs) a cartas naúticas S-57 (.000) válidas para cualquier ECDIS.</p>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<strong>¿Qué hace esta app?</strong>
|
||||||
|
Lee tus capas QGIS con SHP y las convierte a formato S-57 ISO 8211 — el estándar IHO para cartas electrónicas de navegación (ENC). El archivo .000 resultante puede cargarse en cualquier ECDIS que soporte GDAL, incluyendo el AR ECDIS.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="warn">
|
||||||
|
<strong>Requisito del sistema</strong>
|
||||||
|
El entorno conda <code>s57</code> debe estar instalado en <code>D:\Miniconda\envs\s57</code>. La app lo llama automáticamente en el backend.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FLUJO -->
|
||||||
|
<section id="flujo">
|
||||||
|
<h2>Flujo de trabajo</h2>
|
||||||
|
<ol class="steps">
|
||||||
|
<li>
|
||||||
|
<div class="step-body">
|
||||||
|
<strong>Crear proyecto QGIS</strong>
|
||||||
|
<p>En QGIS, crea capas SHP con los nombres de la sección "Objetos puntuales / lineales / de área". Cada capa debe estar en coordenadas WGS84 (EPSG:4326) o el converter la reproyecta automáticamente.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="step-body">
|
||||||
|
<strong>Agregar atributos al SHP</strong>
|
||||||
|
<p>Agrega columnas a tu SHP con los nombres de la sección "Atributos". Por ejemplo, para boyas: columnas <code>nombre</code>, <code>catlam</code>, <code>colour</code>, <code>litchr</code>, <code>sigper</code>, <code>alcance</code>.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="step-body">
|
||||||
|
<strong>Guardar como .qgz</strong>
|
||||||
|
<p>Proyecto → Guardar como → formato .qgz (archivo comprimido que incluye el .qgs y los SHP embebidos).</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="step-body">
|
||||||
|
<strong>Abrir QGISS57Converter</strong>
|
||||||
|
<p>Haz clic en <strong>Examinar…</strong>, selecciona tu .qgz, elige la carpeta de salida, y presiona <strong>▶ Convertir</strong>.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="step-body">
|
||||||
|
<strong>Cargar en el ECDIS</strong>
|
||||||
|
<p>El archivo .000 generado se puede instalar directamente en AR ECDIS desde el menú Charts → Instalar carta.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- PUNTUALES -->
|
||||||
|
<section id="puntuales">
|
||||||
|
<h2>Objetos puntuales</h2>
|
||||||
|
<p>Nombra tu capa QGIS con cualquier texto de la columna "Nombres reconocidos" (sin importar mayúsculas). También puedes usar el acrónimo S-57 directamente.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Acrónimo S-57</th><th>Descripción</th><th>Nombres reconocidos en QGIS</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>BOYLAT</code> <span class="tag tag-point">Punto</span></td><td>Boya lateral (babor/estribor)</td><td>boyas, buoys</td></tr>
|
||||||
|
<tr><td><code>BOYCAR</code> <span class="tag tag-point">Punto</span></td><td>Boya cardinal (N/S/E/W)</td><td>boycar</td></tr>
|
||||||
|
<tr><td><code>BOYISD</code> <span class="tag tag-point">Punto</span></td><td>Boya de peligro aislado</td><td>boyisd</td></tr>
|
||||||
|
<tr><td><code>BOYSAW</code> <span class="tag tag-point">Punto</span></td><td>Boya de aguas seguras</td><td>boysaw</td></tr>
|
||||||
|
<tr><td><code>BCNLAT</code> <span class="tag tag-point">Punto</span></td><td>Baliza lateral</td><td>balizas, beacons</td></tr>
|
||||||
|
<tr><td><code>BCNSPP</code> <span class="tag tag-point">Punto</span></td><td>Baliza especial</td><td>bcnspp</td></tr>
|
||||||
|
<tr><td><code>LIGHTS</code> <span class="tag tag-point">Punto</span></td><td>Luz / faro</td><td>luces, lights, faroles</td></tr>
|
||||||
|
<tr><td><code>LNDMRK</code> <span class="tag tag-point">Punto</span></td><td>Hito en tierra (torre, tanque…)</td><td>Puntos del Terreno, landmark</td></tr>
|
||||||
|
<tr><td><code>SOUNDG</code> <span class="tag tag-point">Punto</span></td><td>Sonda batimétrica</td><td>sondas, soundings, profundidades</td></tr>
|
||||||
|
<tr><td><code>UWTROC</code> <span class="tag tag-point">Punto</span></td><td>Roca sumergida / a flor de agua</td><td>rocas, rocks</td></tr>
|
||||||
|
<tr><td><code>WRECKS</code> <span class="tag tag-point">Punto</span></td><td>Naufragio</td><td>naufragio, wreck</td></tr>
|
||||||
|
<tr><td><code>OBSTRN</code> <span class="tag tag-point">Punto</span></td><td>Obstrucción</td><td>obstruccion, obstruction</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- LINEALES -->
|
||||||
|
<section id="lineales">
|
||||||
|
<h2>Objetos lineales</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Acrónimo S-57</th><th>Descripción</th><th>Nombres reconocidos en QGIS</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>COALNE</code> <span class="tag tag-line">Línea</span></td><td>Línea de costa</td><td>Linderos, coastline, costa, linea_de_costa</td></tr>
|
||||||
|
<tr><td><code>DEPCNT</code> <span class="tag tag-line">Línea</span></td><td>Curva batimétrica (isobata)</td><td>isobata, curvas_nivel, depth_contour</td></tr>
|
||||||
|
<tr><td><code>CBLSUB</code> <span class="tag tag-line">Línea</span></td><td>Cable submarino</td><td>cable</td></tr>
|
||||||
|
<tr><td><code>PIPSOL</code> <span class="tag tag-line">Línea</span></td><td>Tubería submarina / en tierra</td><td>tuberia</td></tr>
|
||||||
|
<tr><td><code>RIVERS</code> <span class="tag tag-line">Línea</span></td><td>Río / canal</td><td>rio, river</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- AREAS -->
|
||||||
|
<section id="areas">
|
||||||
|
<h2>Objetos de área</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Acrónimo S-57</th><th>Descripción</th><th>Nombres reconocidos en QGIS</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>LNDARE</code> <span class="tag tag-area">Área</span></td><td>Área terrestre</td><td>Área Terreno, tierra, land</td></tr>
|
||||||
|
<tr><td><code>DEPARE</code> <span class="tag tag-area">Área</span></td><td>Área de profundidad</td><td>fondos, batimetria, depth_area</td></tr>
|
||||||
|
<tr><td><code>FAIRWY</code> <span class="tag tag-area">Área</span></td><td>Canal de navegación</td><td>canal_navegacion, fairway</td></tr>
|
||||||
|
<tr><td><code>RESARE</code> <span class="tag tag-area">Área</span></td><td>Área restringida</td><td>zona_restringida, restricted</td></tr>
|
||||||
|
<tr><td><code>ACHARE</code> <span class="tag tag-area">Área</span></td><td>Área de fondeo</td><td>fondeadero, anchorage</td></tr>
|
||||||
|
<tr><td><code>HRBARE</code> <span class="tag tag-area">Área</span></td><td>Área portuaria</td><td>puerto, harbor</td></tr>
|
||||||
|
<tr><td><code>BERTHS</code> <span class="tag tag-area">Área</span></td><td>Atraque / muelle</td><td>atraque, berth</td></tr>
|
||||||
|
<tr><td><code>SBDARE</code> <span class="tag tag-area">Área</span></td><td>Área de fondo marino</td><td>fondo_marino, seabed</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ATRIBUTOS GENERALES -->
|
||||||
|
<section id="attr-generales">
|
||||||
|
<h2>Atributos generales</h2>
|
||||||
|
<p>Agrega estas columnas en tu SHP. El nombre de columna es flexible — el converter usa las palabras clave de la tabla.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna en el SHP</th><th>Atributo S-57</th><th>Descripción</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nombre</code> / <code>name</code></td><td><code>OBJNAM</code></td><td>Nombre del objeto — aparece en tooltip del ECDIS</td></tr>
|
||||||
|
<tr><td><code>altura</code> / <code>height</code></td><td><code>HEIGHT</code></td><td>Altura sobre el nivel del mar (metros)</td></tr>
|
||||||
|
<tr><td><code>colour</code> / <code>color</code></td><td><code>COLOUR</code></td><td>Color — ver tabla de códigos abajo</td></tr>
|
||||||
|
<tr><td><code>profundidad</code> / <code>depth</code></td><td><code>DRVAL1</code></td><td>Profundidad mínima (metros)</td></tr>
|
||||||
|
<tr><td><code>depth_max</code></td><td><code>DRVAL2</code></td><td>Profundidad máxima (metros)</td></tr>
|
||||||
|
<tr><td><code>sonda</code> / <code>sounding</code></td><td><code>VALSOU</code></td><td>Valor de sonda (metros)</td></tr>
|
||||||
|
<tr><td><code>contour</code> / <code>valor</code></td><td><code>VALDCO</code></td><td>Valor de curva batimétrica (metros)</td></tr>
|
||||||
|
<tr><td><code>estado</code> / <code>status</code></td><td><code>STATUS</code></td><td>Estado: 1=permanente, 2=ocasional, 7=privado</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- BOYLAT -->
|
||||||
|
<section id="attr-boylat">
|
||||||
|
<h2>BOYLAT — Boyas laterales</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Capa QGIS:</strong> nombrar <code>boyas</code> / <code>boylat</code> / <code>lateral</code> — geometria Punto — CRS EPSG:4326
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna SHP / CSV</th><th>Atributo S-57</th><th>Descripcion y valores</th><th>Requerido</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nombre</code> / <code>OBJNAM</code></td><td><code>OBJNAM</code></td><td>Nombre. Ej: <em>"Boya No.1"</em></td><td></td></tr>
|
||||||
|
<tr><td><code>catlam</code> / <code>CATLAM</code></td><td><code>CATLAM</code></td><td><span class="badge b-green">1 = Babor (VERDE en IALA-B)</span> <span class="badge b-red">2 = Estribor (ROJO en IALA-B)</span></td><td>Si</td></tr>
|
||||||
|
<tr><td><code>colour</code> / <code>COLOUR</code></td><td><code>COLOUR</code></td><td>Codigo de color — ver tabla. IALA-B: <code>4</code>=verde (babor), <code>3</code>=rojo (estribor)</td><td>Si</td></tr>
|
||||||
|
<tr><td><code>boyshp</code> / <code>BOYSHP</code></td><td><code>BOYSHP</code></td><td>Forma: <code>1</code>=conica, <code>2</code>=cilindrica, <code>4</code>=pilar, <code>5</code>=barril, <code>6</code>=esfera</td><td></td></tr>
|
||||||
|
<tr><td><code>litchr</code> / <code>LITCHR</code></td><td><code>LITCHR</code></td><td>Destello — ver tabla LITCHR. Ej: <code>2</code>=Fl, <code>4</code>=Q</td><td></td></tr>
|
||||||
|
<tr><td><code>sigper</code> / <code>SIGPER</code></td><td><code>SIGPER</code></td><td>Periodo en segundos. Ej: <code>4.0</code></td><td></td></tr>
|
||||||
|
<tr><td><code>siggrp</code> / <code>SIGGRP</code></td><td><code>SIGGRP</code></td><td>Grupo de destellos. Ej: <code>(2)</code>, <code>(2+1)</code></td><td></td></tr>
|
||||||
|
<tr><td><code>alcance</code> / <code>VALNMR</code></td><td><code>VALNMR</code></td><td>Alcance nominal en millas nauticas. Ej: <code>5.0</code></td><td></td></tr>
|
||||||
|
<tr><td><code>altura</code> / <code>HEIGHT</code></td><td><code>HEIGHT</code></td><td>Altura del plano focal sobre MLLW (metros)</td><td></td></tr>
|
||||||
|
<tr><td><code>colpat</code> / <code>COLPAT</code></td><td><code>COLPAT</code></td><td>Patron de color: <code>1</code>=horizontal, <code>2</code>=vertical, <code>3</code>=diagonal</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Ejemplo de fila CSV (IALA-B — Americas)</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>OBJNAM</th><th>CATLAM</th><th>COLOUR</th><th>BOYSHP</th><th>LITCHR</th><th>SIGPER</th><th>SIGGRP</th><th>VALNMR</th><th>HEIGHT</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Boya Verde No.1 (Babor)</td><td>1</td><td>4</td><td>2</td><td>2</td><td>3.0</td><td>(1)</td><td>3.0</td><td>2.5</td></tr>
|
||||||
|
<tr><td>Boya Roja No.2 (Estribor)</td><td>2</td><td>3</td><td>1</td><td>2</td><td>4.0</td><td>(1)</td><td>3.0</td><td>2.5</td></tr>
|
||||||
|
<tr><td>Boya Verde No.4 (Babor)</td><td>1</td><td>4</td><td>4</td><td>4</td><td></td><td>(4)</td><td>4.0</td><td>3.0</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- BCNLAT -->
|
||||||
|
<section id="attr-bcnlat">
|
||||||
|
<h2>BCNLAT — Balizas / Faros de orilla</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Capa QGIS:</strong> nombrar <code>balizas</code> / <code>bcnlat</code> / <code>faros</code> — geometria Punto — CRS EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Estructura fija anclada en tierra o sobre el agua. Mismo esquema de atributos que BOYLAT pero sin <code>BOYSHP</code>. Usa <code>BCNSHP</code> para la forma de la baliza y <code>TOPSHP</code> para la marca de tope.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna SHP / CSV</th><th>Atributo S-57</th><th>Descripcion y valores</th><th>Requerido</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nombre</code> / <code>OBJNAM</code></td><td><code>OBJNAM</code></td><td>Nombre. Ej: <em>"Faro X1"</em></td><td></td></tr>
|
||||||
|
<tr><td><code>catlam</code> / <code>CATLAM</code></td><td><code>CATLAM</code></td><td><span class="badge b-green">1 = Babor (VERDE IALA-B)</span> <span class="badge b-red">2 = Estribor (ROJO IALA-B)</span></td><td>Si</td></tr>
|
||||||
|
<tr><td><code>colour</code> / <code>COLOUR</code></td><td><code>COLOUR</code></td><td>IALA-B: <code>4</code>=verde (babor), <code>3</code>=rojo (estribor)</td><td>Si</td></tr>
|
||||||
|
<tr><td><code>bcnshp</code> / <code>BCNSHP</code></td><td><code>BCNSHP</code></td><td>Forma: <code>1</code>=poste, <code>2</code>=tripode, <code>3</code>=torre, <code>4</code>=pilao, <code>8</code>=faro</td><td></td></tr>
|
||||||
|
<tr><td><code>topshp</code> / <code>TOPSHP</code></td><td><code>TOPSHP</code></td><td>Marca de tope: <code>2</code>=cono, <code>5</code>=cilindro, <code>6</code>=esfera, <code>11</code>=cuadro</td><td></td></tr>
|
||||||
|
<tr><td><code>litchr</code> / <code>LITCHR</code></td><td><code>LITCHR</code></td><td>Destello — ver tabla LITCHR</td><td></td></tr>
|
||||||
|
<tr><td><code>sigper</code> / <code>SIGPER</code></td><td><code>SIGPER</code></td><td>Periodo en segundos</td><td></td></tr>
|
||||||
|
<tr><td><code>siggrp</code> / <code>SIGGRP</code></td><td><code>SIGGRP</code></td><td>Grupo de destellos. Ej: <code>(4)</code></td><td></td></tr>
|
||||||
|
<tr><td><code>alcance</code> / <code>VALNMR</code></td><td><code>VALNMR</code></td><td>Alcance en millas nauticas</td><td></td></tr>
|
||||||
|
<tr><td><code>altura</code> / <code>HEIGHT</code></td><td><code>HEIGHT</code></td><td>Altura del plano focal (metros)</td><td></td></tr>
|
||||||
|
<tr><td><code>ORIENT</code></td><td><code>ORIENT</code></td><td>Rumbo verdadero de enfilacion (grados). Si se llena, genera linea de enfilacion en carta</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Ejemplo de fila CSV — faros de orilla Barranquilla</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>OBJNAM</th><th>CATLAM</th><th>COLOUR</th><th>LITCHR</th><th>SIGPER</th><th>SIGGRP</th><th>VALNMR</th><th>HEIGHT</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Faro X1</td><td>1</td><td>4</td><td>4</td><td>11.0</td><td>(4)</td><td>6.0</td><td>6.0</td></tr>
|
||||||
|
<tr><td>Faro X4</td><td>2</td><td>3</td><td>2</td><td>4.0</td><td>(1)</td><td>5.0</td><td>8.0</td></tr>
|
||||||
|
<tr><td>Faro X10</td><td>1</td><td>4</td><td>2</td><td>5.0</td><td>(3)</td><td>7.0</td><td>10.0</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- BOYCAR -->
|
||||||
|
<section id="attr-boycar">
|
||||||
|
<h2>BOYCAR — Boyas cardinales</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Capa QGIS:</strong> nombrar <code>cardinales</code> / <code>boycar</code> / <code>cardinal</code> — geometria Punto — CRS EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Boyas cardinales IALA: Norte, Sur, Este, Oeste. Se identifican por la combinacion <code>CATCAM</code> + patron de color amarillo/negro.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna SHP / CSV</th><th>Atributo S-57</th><th>Descripcion y valores</th><th>Requerido</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nombre</code> / <code>OBJNAM</code></td><td><code>OBJNAM</code></td><td>Nombre. Ej: <em>"Boyarin N Canal"</em></td><td></td></tr>
|
||||||
|
<tr><td><code>catcam</code> / <code>CATCAM</code></td><td><code>CATCAM</code></td><td><code>1</code>=Norte, <code>2</code>=Este, <code>3</code>=Sur, <code>4</code>=Oeste</td><td>Si</td></tr>
|
||||||
|
<tr><td><code>colour</code> / <code>COLOUR</code></td><td><code>COLOUR</code></td><td>Normalmente <code>6,2</code> (amarillo+negro). El converter asigna automaticamente segun CATCAM si se omite</td><td></td></tr>
|
||||||
|
<tr><td><code>litchr</code> / <code>LITCHR</code></td><td><code>LITCHR</code></td><td><code>4</code>=Q (Norte/Sur), <code>5</code>=VQ (rapida)</td><td></td></tr>
|
||||||
|
<tr><td><code>sigper</code> / <code>SIGPER</code></td><td><code>SIGPER</code></td><td>Periodo</td><td></td></tr>
|
||||||
|
<tr><td><code>siggrp</code> / <code>SIGGRP</code></td><td><code>SIGGRP</code></td><td>Grupo: Norte=continuo, Este=<code>(3)</code>, Sur=<code>(6)+LFl</code>, Oeste=<code>(9)</code></td><td></td></tr>
|
||||||
|
<tr><td><code>alcance</code> / <code>VALNMR</code></td><td><code>VALNMR</code></td><td>Alcance en millas nauticas</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Valores CATCAM</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Codigo</th><th>Cardinal</th><th>Color tipico</th><th>Destello tipico</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>1</code></td><td>Norte (N)</td><td>Negro arriba / Amarillo abajo</td><td>Q o VQ (rapida continua)</td></tr>
|
||||||
|
<tr><td><code>2</code></td><td>Este (E)</td><td>Negro-Amarillo-Negro</td><td>Q(3) o VQ(3) cada 5/10s</td></tr>
|
||||||
|
<tr><td><code>3</code></td><td>Sur (S)</td><td>Amarillo arriba / Negro abajo</td><td>Q(6)+LFl o VQ(6)+LFl cada 15s</td></tr>
|
||||||
|
<tr><td><code>4</code></td><td>Oeste (W)</td><td>Amarillo-Negro-Amarillo</td><td>Q(9) o VQ(9) cada 10/15s</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- BOYISD -->
|
||||||
|
<section id="attr-boyisd">
|
||||||
|
<h2>BOYISD — Boya de peligro aislado</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Capa QGIS:</strong> nombrar <code>peligro</code> / <code>boyisd</code> / <code>isolated</code> — geometria Punto — CRS EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Marca un obstaculo rodeado de agua navegable por todos lados. Color: negro con banda(s) roja(s). Marcas de tope: dos esferas negras.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna SHP / CSV</th><th>Atributo S-57</th><th>Descripcion y valores</th><th>Requerido</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nombre</code> / <code>OBJNAM</code></td><td><code>OBJNAM</code></td><td>Nombre del peligro</td><td></td></tr>
|
||||||
|
<tr><td><code>colour</code> / <code>COLOUR</code></td><td><code>COLOUR</code></td><td>Normalmente <code>2,3</code> (negro+rojo). Si se omite el converter asigna automaticamente</td><td></td></tr>
|
||||||
|
<tr><td><code>litchr</code> / <code>LITCHR</code></td><td><code>LITCHR</code></td><td>Tipicamente <code>13</code>=FFl (Fija y destellante)</td><td></td></tr>
|
||||||
|
<tr><td><code>sigper</code> / <code>SIGPER</code></td><td><code>SIGPER</code></td><td>Periodo</td><td></td></tr>
|
||||||
|
<tr><td><code>siggrp</code> / <code>SIGGRP</code></td><td><code>SIGGRP</code></td><td>Grupo: tipicamente <code>(2)</code></td><td></td></tr>
|
||||||
|
<tr><td><code>alcance</code> / <code>VALNMR</code></td><td><code>VALNMR</code></td><td>Alcance en millas nauticas</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- BOYSPP -->
|
||||||
|
<section id="attr-boyspp">
|
||||||
|
<h2>BOYSPP — Marcas especiales</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Capa QGIS:</strong> nombrar <code>especiales</code> / <code>boyspp</code> / <code>special</code> — geometria Punto — CRS EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Boyas de usos especiales: zonas de pesca, cabos de fondeo, areas restringidas, tuberias, etc. Color: amarillo. Marca de tope: aspa (X) amarilla.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna SHP / CSV</th><th>Atributo S-57</th><th>Descripcion y valores</th><th>Requerido</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nombre</code> / <code>OBJNAM</code></td><td><code>OBJNAM</code></td><td>Descripcion de uso</td><td></td></tr>
|
||||||
|
<tr><td><code>colour</code> / <code>COLOUR</code></td><td><code>COLOUR</code></td><td><code>6</code>=Amarillo (estandar para marcas especiales)</td><td>Si</td></tr>
|
||||||
|
<tr><td><code>boyshp</code> / <code>BOYSHP</code></td><td><code>BOYSHP</code></td><td>Forma de la boya</td><td></td></tr>
|
||||||
|
<tr><td><code>litchr</code> / <code>LITCHR</code></td><td><code>LITCHR</code></td><td>Destello — ver tabla LITCHR</td><td></td></tr>
|
||||||
|
<tr><td><code>sigper</code> / <code>SIGPER</code></td><td><code>SIGPER</code></td><td>Periodo</td><td></td></tr>
|
||||||
|
<tr><td><code>alcance</code> / <code>VALNMR</code></td><td><code>VALNMR</code></td><td>Alcance</td><td></td></tr>
|
||||||
|
<tr><td><code>inform</code> / <code>INFORM</code></td><td><code>INFORM</code></td><td>Texto libre: descripcion de la zona restringida</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- LIGHTS -->
|
||||||
|
<section id="attr-lights">
|
||||||
|
<h2>LIGHTS — Luces independientes (faros, luces de sector)</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Capa QGIS:</strong> nombrar <code>luces</code> / <code>lights</code> / <code>faros</code> — geometria Punto — CRS EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Luces independientes como faros de tierra, luces de sector o luces de recalada que no estan asociadas a una boya. Para boyas con luz, usa BOYLAT/BCNLAT con los campos LITCHR/SIGPER — el converter genera automaticamente el objeto LIGHTS co-ubicado.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna SHP / CSV</th><th>Atributo S-57</th><th>Descripcion y valores</th><th>Requerido</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nombre</code> / <code>OBJNAM</code></td><td><code>OBJNAM</code></td><td>Nombre del faro</td><td></td></tr>
|
||||||
|
<tr><td><code>colour</code> / <code>COLOUR</code></td><td><code>COLOUR</code></td><td>Color de la luz: <code>1</code>=blanco, <code>3</code>=rojo, <code>4</code>=verde</td><td>Si</td></tr>
|
||||||
|
<tr><td><code>litchr</code> / <code>LITCHR</code></td><td><code>LITCHR</code></td><td>Caracteristica de destello — ver tabla LITCHR</td><td>Si</td></tr>
|
||||||
|
<tr><td><code>sigper</code> / <code>SIGPER</code></td><td><code>SIGPER</code></td><td>Periodo en segundos. Ej: <code>2.0</code></td><td>Si</td></tr>
|
||||||
|
<tr><td><code>siggrp</code> / <code>SIGGRP</code></td><td><code>SIGGRP</code></td><td>Grupo: si aplica. Ej: <code>(3)</code></td><td></td></tr>
|
||||||
|
<tr><td><code>alcance</code> / <code>VALNMR</code></td><td><code>VALNMR</code></td><td>Alcance nominal en millas nauticas</td><td></td></tr>
|
||||||
|
<tr><td><code>altura</code> / <code>HEIGHT</code></td><td><code>HEIGHT</code></td><td>Altura del plano focal sobre MLLW (metros)</td><td></td></tr>
|
||||||
|
<tr><td><code>ORIENT</code></td><td><code>ORIENT</code></td><td>Rumbo verdadero de la enfilacion (grados). Genera linea de enfilacion en la carta</td><td></td></tr>
|
||||||
|
<tr><td><code>inform</code> / <code>INFORM</code></td><td><code>INFORM</code></td><td>Notas: color de la torre, caracteristicas especiales</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Ejemplo — Faros de recalada Barranquilla</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>OBJNAM</th><th>COLOUR</th><th>LITCHR</th><th>SIGPER</th><th>VALNMR</th><th>HEIGHT</th><th>ORIENT</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Faro F1 Recalada</td><td>4</td><td>7</td><td>2.0</td><td>9.0</td><td>20.0</td><td>270.0</td></tr>
|
||||||
|
<tr><td>Faro F2 Recalada</td><td>3</td><td>7</td><td>2.0</td><td>13.4</td><td>23.0</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CÓDIGOS S-57 -->
|
||||||
|
<section id="codigos">
|
||||||
|
<h2>Tablas de codigos S-57</h2>
|
||||||
|
|
||||||
|
<h3>LITCHR — Caracteristica de luz (IHO S-57 Ed. 3.1)</h3>
|
||||||
|
<div class="warn">
|
||||||
|
<strong>Atencion:</strong> Usar exactamente estos codigos numericos en la columna <code>LITCHR</code>. El texto (Fl, Q, Iso…) es solo referencia.
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Codigo</th><th>Abrev.</th><th>Descripcion</th><th>Ejemplo en carta</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>1</code></td><td><strong>F</strong></td><td>Fija — luz continua sin interrupciones</td><td>F G</td></tr>
|
||||||
|
<tr><td><code>2</code></td><td><strong>Fl</strong></td><td>Destellante — destello mas corto que ocultacion</td><td>Fl G 4s</td></tr>
|
||||||
|
<tr><td><code>3</code></td><td><strong>LFl</strong></td><td>Gran destello — destello de duracion ≥ 2s</td><td>LFl W 10s</td></tr>
|
||||||
|
<tr><td><code>4</code></td><td><strong>Q</strong></td><td>Centelleante — 50 a 60 destellos por minuto</td><td>Q(4) G 11s</td></tr>
|
||||||
|
<tr><td><code>5</code></td><td><strong>VQ</strong></td><td>Rapida — 100 a 120 destellos por minuto</td><td>VQ(3) W</td></tr>
|
||||||
|
<tr><td><code>6</code></td><td><strong>UQ</strong></td><td>Ultra rapida — mas de 160 destellos por minuto</td><td>UQ</td></tr>
|
||||||
|
<tr><td><code>7</code></td><td><strong>Iso</strong></td><td>Isofasica — periodo de luz igual al de oscuridad</td><td>Iso G 2s</td></tr>
|
||||||
|
<tr><td><code>8</code></td><td><strong>Oc</strong></td><td>Ocultante — periodo de luz mayor que el de oscuridad</td><td>Oc R 4s</td></tr>
|
||||||
|
<tr><td><code>9</code></td><td><strong>IQ</strong></td><td>Centelleante interrumpida</td><td>IQ</td></tr>
|
||||||
|
<tr><td><code>10</code></td><td><strong>IVQ</strong></td><td>Rapida interrumpida</td><td>IVQ(3)</td></tr>
|
||||||
|
<tr><td><code>12</code></td><td><strong>Mo</strong></td><td>Codigo Morse</td><td>Mo(A) W</td></tr>
|
||||||
|
<tr><td><code>13</code></td><td><strong>FFl</strong></td><td>Fija y destellante</td><td>FFl(2) W</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>COLOUR — Color</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Código</th><th>Color</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>1</code></td><td><span class="swatch" style="background:#fff"></span>Blanco (White)</td></tr>
|
||||||
|
<tr><td><code>2</code></td><td><span class="swatch" style="background:#111"></span>Negro (Black)</td></tr>
|
||||||
|
<tr><td><code>3</code></td><td><span class="swatch" style="background:#cc2222"></span>Rojo (Red)</td></tr>
|
||||||
|
<tr><td><code>4</code></td><td><span class="swatch" style="background:#228822"></span>Verde (Green)</td></tr>
|
||||||
|
<tr><td><code>5</code></td><td><span class="swatch" style="background:#3355cc"></span>Azul (Blue)</td></tr>
|
||||||
|
<tr><td><code>6</code></td><td><span class="swatch" style="background:#cccc22"></span>Amarillo (Yellow)</td></tr>
|
||||||
|
<tr><td><code>9</code></td><td><span class="swatch" style="background:#cc8822"></span>Naranja (Orange)</td></tr>
|
||||||
|
<tr><td><code>11</code></td><td><span class="swatch" style="background:#cc22cc"></span>Violeta (Violet)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>CATLAM — Categoria lateral</h3>
|
||||||
|
<div class="warn">
|
||||||
|
<strong>IALA-B (Americas — Colombia, USA, Canada, Brasil…)</strong>
|
||||||
|
Babor = VERDE | Estribor = ROJO. Al entrar al puerto: verde a la izquierda, rojo a la derecha.
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Codigo</th><th>IALA-B (Americas)</th><th>IALA-A (Europa/Asia/Africa/Australia)</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>1</code></td><td><span class="badge b-green">Babor — VERDE (colour=4)</span> — mano izquierda entrando</td><td><span class="badge b-red">Babor — ROJO (colour=3)</span></td></tr>
|
||||||
|
<tr><td><code>2</code></td><td><span class="badge b-red">Estribor — ROJO (colour=3)</span> — mano derecha entrando</td><td><span class="badge b-green">Estribor — VERDE (colour=4)</span></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>BOYSHP — Forma de boya</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Código</th><th>Forma</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>1</code></td><td>Cónica (estribor en IALA-A)</td></tr>
|
||||||
|
<tr><td><code>2</code></td><td>Cilíndrica (babor en IALA-A)</td></tr>
|
||||||
|
<tr><td><code>3</code></td><td>Esférica</td></tr>
|
||||||
|
<tr><td><code>4</code></td><td>Barril</td></tr>
|
||||||
|
<tr><td><code>5</code></td><td>Super-boya</td></tr>
|
||||||
|
<tr><td><code>6</code></td><td>Pilón (spar)</td></tr>
|
||||||
|
<tr><td><code>7</code></td><td>Boyarín</td></tr>
|
||||||
|
<tr><td><code>8</code></td><td>Ícaro (ice buoy)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CSV DIRECTO -->
|
||||||
|
<section id="csv-directo">
|
||||||
|
<h2>Formato CSV directo (sin QGIS)</h2>
|
||||||
|
<p>Si prefieres no usar QGIS, puedes preparar un CSV maestro y correr <code>build_barranquilla.py</code> (o script equivalente) directamente. El CSV usa los nombres de atributos S-57 como columnas.</p>
|
||||||
|
<div class="info">
|
||||||
|
<strong>Columna clave: <code>feat_type</code></strong>
|
||||||
|
Define el tipo S-57 de cada fila. Valores: <code>BOYLAT</code>, <code>BCNLAT</code>, <code>BOYCAR</code>, <code>BOYISD</code>, <code>BOYSPP</code>, <code>LIGHTS</code>
|
||||||
|
</div>
|
||||||
|
<h3>Cabecera del CSV maestro</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Columna</th><th>Atributo S-57</th><th>Descripcion</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>no_dimar</code></td><td>—</td><td>Numero de referencia interno (opcional)</td></tr>
|
||||||
|
<tr><td><code>OBJNAM</code></td><td><code>OBJNAM</code></td><td>Nombre del objeto</td></tr>
|
||||||
|
<tr><td><code>lon</code></td><td>—</td><td>Longitud decimal WGS-84 (negativa al W)</td></tr>
|
||||||
|
<tr><td><code>lat</code></td><td>—</td><td>Latitud decimal WGS-84</td></tr>
|
||||||
|
<tr><td><code>feat_type</code></td><td>Clase S-57</td><td>BOYLAT / BCNLAT / BOYCAR / BOYISD / BOYSPP / LIGHTS</td></tr>
|
||||||
|
<tr><td><code>LITCHR</code></td><td><code>LITCHR</code></td><td>Codigo de destello — ver tabla</td></tr>
|
||||||
|
<tr><td><code>LITCHR_TXT</code></td><td>—</td><td>Texto del destello (solo referencia, no se escribe en S-57)</td></tr>
|
||||||
|
<tr><td><code>SIGGRP</code></td><td><code>SIGGRP</code></td><td>Grupo de destellos. Ej: <code>(4)</code></td></tr>
|
||||||
|
<tr><td><code>SIGPER</code></td><td><code>SIGPER</code></td><td>Periodo en segundos</td></tr>
|
||||||
|
<tr><td><code>COLOUR</code></td><td><code>COLOUR</code></td><td>Codigo de color (ver tabla COLOUR)</td></tr>
|
||||||
|
<tr><td><code>COLOUR_TXT</code></td><td>—</td><td>Texto del color (solo referencia)</td></tr>
|
||||||
|
<tr><td><code>COLPAT</code></td><td><code>COLPAT</code></td><td>Patron de color: 1=horizontal, 2=vertical</td></tr>
|
||||||
|
<tr><td><code>VALNMR</code></td><td><code>VALNMR</code></td><td>Alcance nominal (millas nauticas)</td></tr>
|
||||||
|
<tr><td><code>HEIGHT</code></td><td><code>HEIGHT</code></td><td>Altura del plano focal (metros)</td></tr>
|
||||||
|
<tr><td><code>ORIENT</code></td><td><code>ORIENT</code></td><td>Rumbo de enfilacion en grados (opcional)</td></tr>
|
||||||
|
<tr><td><code>CATLAM</code></td><td><code>CATLAM</code></td><td>Categoria lateral: 1=babor, 2=estribor</td></tr>
|
||||||
|
<tr><td><code>CATCAM</code></td><td><code>CATCAM</code></td><td>Cardinal: 1=N, 2=E, 3=S, 4=W</td></tr>
|
||||||
|
<tr><td><code>BOYSHP</code></td><td><code>BOYSHP</code></td><td>Forma de boya</td></tr>
|
||||||
|
<tr><td><code>BCNSHP</code></td><td><code>BCNSHP</code></td><td>Forma de baliza</td></tr>
|
||||||
|
<tr><td><code>TOPSHP</code></td><td><code>TOPSHP</code></td><td>Marca de tope</td></tr>
|
||||||
|
<tr><td><code>INFORM</code></td><td><code>INFORM</code></td><td>Notas libres (aparecen en tooltip)</td></tr>
|
||||||
|
<tr><td><code>_dimar_char_raw</code></td><td>—</td><td>Caracter de luz original DIMAR (solo referencia)</td></tr>
|
||||||
|
<tr><td><code>_source</code></td><td>—</td><td>Fuente del dato (solo referencia)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Ejemplo de filas CSV maestro</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>OBJNAM</th><th>lon</th><th>lat</th><th>feat_type</th><th>LITCHR</th><th>SIGPER</th><th>COLOUR</th><th>CATLAM</th><th>CATCAM</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Boya No. 1</td><td>-74.810</td><td>11.102</td><td>BOYLAT</td><td>2</td><td>3.0</td><td>4</td><td>1</td><td></td></tr>
|
||||||
|
<tr><td>Faro X1</td><td>-74.806</td><td>11.095</td><td>BCNLAT</td><td>4</td><td>11.0</td><td>4</td><td>1</td><td></td></tr>
|
||||||
|
<tr><td>Cardinal N</td><td>-74.820</td><td>11.110</td><td>BOYCAR</td><td>4</td><td></td><td>2</td><td></td><td>1</td></tr>
|
||||||
|
<tr><td>Faro Recalada</td><td>-74.849</td><td>11.106</td><td>LIGHTS</td><td>7</td><td>2.0</td><td>4</td><td></td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Para agregar tierra (LNDARE) al .000</strong>
|
||||||
|
El CSV directo solo soporta objetos puntuales. Para incluir poligonos de tierra (LNDARE) y linea de costa (COALNE), usar el flujo QGIS con capas SHP de area/linea, o crear una capa <code>Tierra</code> (geometria Poligono) en QGIS con los limites costeros y exportar con el converter normal.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CONFIG -->
|
||||||
|
<section id="config">
|
||||||
|
<h2>cell_config.json</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Este archivo ya viene incluido con el converter — NO hay que crearlo.</strong>
|
||||||
|
Solo edita los campos que necesites cambiar (cell_name, scale, issue_date, producer_name).
|
||||||
|
El archivo esta en: <code>D:\Proyectos Software\QGISS57Converter\cell_config.json</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Campos que debes editar para cada carta</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Campo</th><th>Descripcion</th><th>Ejemplo</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>cell_name</code></td><td>Nombre del archivo de salida (sin .000). Convenio IHO: 2 letras pais + 1 digito escala + codigo area</td><td><code>CO1CO01M</code></td></tr>
|
||||||
|
<tr><td><code>scale</code></td><td>Escala denominador de la carta</td><td><code>50000</code></td></tr>
|
||||||
|
<tr><td><code>issue_date</code></td><td>Fecha de emision formato YYYYMMDD</td><td><code>20260430</code></td></tr>
|
||||||
|
<tr><td><code>producer_name</code></td><td>Nombre del productor hidrográfico</td><td><code>DIMAR</code></td></tr>
|
||||||
|
<tr><td><code>producer_code</code></td><td>Codigo ISO2 del pais productor</td><td><code>CO</code></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Estructura completa del archivo</h3>
|
||||||
|
<pre style="background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:14px;font-family:Consolas,monospace;font-size:12px;color:var(--mono);overflow-x:auto;margin-bottom:16px">{
|
||||||
|
"cell_name": "CO1CO01M",
|
||||||
|
"cell_edition": 1,
|
||||||
|
"update_number": 1,
|
||||||
|
"issue_date": "20260430",
|
||||||
|
"producer_code": "CO",
|
||||||
|
"producer_name": "DIMAR",
|
||||||
|
"data_set_name": "Barranquilla ENC",
|
||||||
|
"scale": 50000,
|
||||||
|
"horizontal_datum": "WGS84",
|
||||||
|
"vertical_datum": "MLLW",
|
||||||
|
"sounding_datum": "MLLW",
|
||||||
|
"compilation_scale": 50000,
|
||||||
|
|
||||||
|
"layer_mappings": {
|
||||||
|
"boyas": "BOYLAT",
|
||||||
|
"balizas": "BCNLAT",
|
||||||
|
"luces": "LIGHTS",
|
||||||
|
"tierra": "LNDARE",
|
||||||
|
"Área Terreno": "LNDARE",
|
||||||
|
"Linderos": "COALNE",
|
||||||
|
"sondas": "SOUNDG",
|
||||||
|
"fondeadero": "ACHARE"
|
||||||
|
},
|
||||||
|
|
||||||
|
"attribute_mappings": {
|
||||||
|
"nombre": "OBJNAM",
|
||||||
|
"colour": "COLOUR",
|
||||||
|
"catlam": "CATLAM",
|
||||||
|
"boyshp": "BOYSHP",
|
||||||
|
"litchr": "LITCHR",
|
||||||
|
"sigper": "SIGPER",
|
||||||
|
"siggrp": "SIGGRP",
|
||||||
|
"alcance": "VALNMR",
|
||||||
|
"altura": "HEIGHT"
|
||||||
|
}
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<strong>layer_mappings</strong> — traduce el nombre de tu capa QGIS al acronimo S-57. El converter ya incluye los nombres mas comunes (ver archivo completo). Si tu capa tiene un nombre especial, agrega una linea aqui.
|
||||||
|
<br><br>
|
||||||
|
<strong>attribute_mappings</strong> — traduce el nombre de tu columna SHP al atributo S-57. Igualmente, los nombres comunes ya estan mapeados.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- EJEMPLOS -->
|
||||||
|
<section id="ejemplos">
|
||||||
|
<h2>Ejemplos de SHP</h2>
|
||||||
|
|
||||||
|
<h3>Capa de boyas (BOYLAT)</h3>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Nombre de la capa:</strong> <code>boyas</code> — geometría Punto — CRS: EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Columnas recomendadas:</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>nombre</th><th>catlam</th><th>colour</th><th>litchr</th><th>sigper</th><th>siggrp</th><th>alcance</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Boya R-4</td><td>1</td><td>3</td><td>2</td><td>4.0</td><td>(1)</td><td>3.0</td></tr>
|
||||||
|
<tr><td>Boya V-3</td><td>2</td><td>4</td><td>2</td><td>4.0</td><td>(1)</td><td>3.0</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Capa de línea de costa (COALNE)</h3>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Nombre de la capa:</strong> <code>Linderos</code> o <code>coastline</code> — geometría Línea — CRS: EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>No requiere atributos mínimos. Opcionales: <code>nombre</code> para identificar el tramo.</p>
|
||||||
|
|
||||||
|
<h3>Capa de área terrestre (LNDARE)</h3>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Nombre de la capa:</strong> <code>Área Terreno</code> o <code>tierra</code> — geometría Polígono — CRS: EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Opcional: <code>nombre</code> para identificar la isla o terreno.</p>
|
||||||
|
|
||||||
|
<h3>Capa de sondas (SOUNDG)</h3>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>Nombre de la capa:</strong> <code>sondas</code> — geometría Punto — CRS: EPSG:4326
|
||||||
|
</div>
|
||||||
|
<p>Agrega columna <code>sonda</code> con el valor en metros. Si el punto tiene coordenada Z, se usa automáticamente.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>sonda</th><th>Coordenadas</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>3.5</td><td>-80.456, 27.752</td></tr>
|
||||||
|
<tr><td>12.0</td><td>-80.451, 27.758</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- ── JS: search + active nav ───────────────────────────────────────────── -->
|
||||||
|
<script>
|
||||||
|
// Active nav on scroll
|
||||||
|
const sections = document.querySelectorAll('section[id]');
|
||||||
|
const navLinks = document.querySelectorAll('.nav-link');
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
let cur = '';
|
||||||
|
sections.forEach(s => { if (window.scrollY >= s.offsetTop - 60) cur = s.id; });
|
||||||
|
navLinks.forEach(l => {
|
||||||
|
l.classList.toggle('active', l.getAttribute('href') === '#' + cur);
|
||||||
|
});
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Search
|
||||||
|
const box = document.getElementById('search-box');
|
||||||
|
box.addEventListener('input', () => {
|
||||||
|
const q = box.value.trim().toLowerCase();
|
||||||
|
document.querySelectorAll('table tbody tr').forEach(tr => {
|
||||||
|
tr.classList.toggle('hidden-row', q.length > 1 && !tr.textContent.toLowerCase().includes(q));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
import pyproj, os
|
||||||
|
_proj_data = pyproj.datadir.get_data_dir()
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['gui.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[
|
||||||
|
('cell_config.json', '.'),
|
||||||
|
('noaa_ddr_template.bin', '.'),
|
||||||
|
('CAPAS_REFERENCIA.pdf', '.'),
|
||||||
|
('s57_objects.json', '.'),
|
||||||
|
('MANUAL.html', '.'),
|
||||||
|
(_proj_data, 'proj_data'), # PROJ grid files for pyproj
|
||||||
|
],
|
||||||
|
hiddenimports=['converter', 's57_writer', 'pyproj', 'pyproj.datadir'],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='QGISS57Converter',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=False,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# SCHEMA CSV — DIMAR → S-57 ENC
|
||||||
|
## Referencia para extracción de datos de PDFs DIMAR
|
||||||
|
|
||||||
|
Cuando Claude recibe un PDF de DIMAR (Lista de Luces, AAN, carta), lee este archivo
|
||||||
|
y genera el CSV con exactamente estas columnas y códigos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## COLUMNAS DEL CSV (en este orden)
|
||||||
|
|
||||||
|
```
|
||||||
|
no_dimar, OBJNAM, lon, lat, feat_type, LITCHR, LITCHR_TXT, SIGGRP, COLOUR, COLOUR_TXT, SIGPER, VALNMR, HEIGHT, ORIENT, CATCAM, INFORM, _dimar_char_raw, _source
|
||||||
|
```
|
||||||
|
|
||||||
|
| Columna | Descripción | Ejemplo |
|
||||||
|
|----------------|--------------------------------------------------|----------------------|
|
||||||
|
| no_dimar | Número en Lista de Luces DIMAR | 257 |
|
||||||
|
| OBJNAM | Nombre oficial de la ayuda | Faro Castillogrande |
|
||||||
|
| lon | Longitud decimal WGS84 (negativo = oeste) | -75.545000 |
|
||||||
|
| lat | Latitud decimal WGS84 | 10.391000 |
|
||||||
|
| feat_type | Tipo S-57 (ver tabla abajo) | BOYCAR |
|
||||||
|
| LITCHR | Código numérico de característica de luz | 2 |
|
||||||
|
| LITCHR_TXT | Texto legible de la característica | Fl |
|
||||||
|
| SIGGRP | Grupo de destellos entre paréntesis | 3 o (6)+ |
|
||||||
|
| COLOUR | Código numérico de color (ver tabla) | 3 |
|
||||||
|
| COLOUR_TXT | Texto del color | red |
|
||||||
|
| SIGPER | Período en segundos | 10 |
|
||||||
|
| VALNMR | Alcance nominal en millas náuticas | 12 |
|
||||||
|
| HEIGHT | Altura de la luz sobre MLLW en metros | 24 |
|
||||||
|
| ORIENT | Rumbo de enfilación en grados (solo enfilaciones)| 135.7 |
|
||||||
|
| CATCAM | Dirección cardinal (solo boyas cardinales) | 1 |
|
||||||
|
| INFORM | Descripción física de la estructura | Torre concreto beige |
|
||||||
|
| _dimar_char_raw| Característica de luz tal como aparece en DIMAR | Fl. W 10 s |
|
||||||
|
| _source | Fuente del dato | DIMAR Lista de Luces 2015 |
|
||||||
|
|
||||||
|
**Regla**: columnas vacías se dejan en blanco (no NULL, no 0, solo vacío).
|
||||||
|
**Regla**: columnas que empiezan con `_` son privadas, el converter las ignora.
|
||||||
|
**Regla**: coordenadas siempre en decimal WGS84. Convertir grados-minutos así:
|
||||||
|
`DD°MM.mmm' = DD + MM.mmm/60`
|
||||||
|
Ejemplo: 10°23.45'N = 10 + 23.45/60 = 10.390833
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TABLA feat_type — TIPO DE AYUDA
|
||||||
|
|
||||||
|
| feat_type | Descripción | Cuándo usarlo |
|
||||||
|
|-----------|------------------------------------|--------------------------------------------------|
|
||||||
|
| BOYCAR | Boya cardinal | Boya con topmark en forma de cono/cono invertido que indica N/S/E/W |
|
||||||
|
| BCNCAR | Baliza cardinal (fija) | Estructura fija cardinal |
|
||||||
|
| BOYLAT | Boya lateral | Boya roja o verde que marca bordes de canal |
|
||||||
|
| BCNLAT | Baliza lateral (fija) | Estructura fija lateral, también enfilaciones |
|
||||||
|
| BOYISD | Boya de peligro aislado | Boya negra-roja sobre peligro aislado |
|
||||||
|
| BOYSAW | Boya de aguas seguras | Boya roja-blanca, marca agua navegable |
|
||||||
|
| BOYSPP | Boya especial | Boya amarilla, uso especial |
|
||||||
|
| LIGHTS | Luz / Faro / Enfilación | Faros, luces de puerto, enfilaciones |
|
||||||
|
| LNDMRK | Punto de referencia terrestre | Torres, edificios notables, chimeneas |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TABLA LITCHR — CARACTERÍSTICA DE LUZ
|
||||||
|
|
||||||
|
| Código | LITCHR_TXT | DIMAR escribe | Descripción |
|
||||||
|
|--------|------------|------------------------|--------------------------|
|
||||||
|
| 1 | F | F. | Fija (Fixed) |
|
||||||
|
| 2 | Fl | Fl. | Destello (Flashing) |
|
||||||
|
| 3 | LFl | LFl. | Destello largo |
|
||||||
|
| 4 | Q | Q. | Rápida (Quick) |
|
||||||
|
| 5 | VQ | VQ. | Muy rápida |
|
||||||
|
| 6 | UQ | UQ. | Ultra rápida |
|
||||||
|
| 7 | Iso | Iso. | Isofase |
|
||||||
|
| 8 | Oc | Oc. | Ocultante |
|
||||||
|
| 9 | IQ | IQ. | Interrumpida rápida |
|
||||||
|
| 12 | Mo | Mo. | Morse |
|
||||||
|
| 13 | FFl | FFl. | Fija y destellante |
|
||||||
|
|
||||||
|
**Grupos**: el número entre paréntesis va en SIGGRP. Ejemplos:
|
||||||
|
- `Fl.(3) W 10 s` → LITCHR=2, LITCHR_TXT=Fl, SIGGRP=3, COLOUR=1, SIGPER=10
|
||||||
|
- `Q.(6)+LFl.W 15s` → LITCHR=4, LITCHR_TXT=Q, SIGGRP=(6)+, COLOUR=1, SIGPER=15
|
||||||
|
- `Iso. Bu 4 s` → LITCHR=7, LITCHR_TXT=Iso, SIGGRP=, COLOUR=5, SIGPER=4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TABLA COLOUR — COLOR DE LA LUZ
|
||||||
|
|
||||||
|
| Código | COLOUR_TXT | DIMAR escribe |
|
||||||
|
|--------|------------|---------------|
|
||||||
|
| 1 | white | W |
|
||||||
|
| 2 | black | — |
|
||||||
|
| 3 | red | R |
|
||||||
|
| 4 | green | G |
|
||||||
|
| 5 | blue | Bu |
|
||||||
|
| 6 | yellow | Y |
|
||||||
|
| 11 | orange | Or |
|
||||||
|
|
||||||
|
**Colores múltiples** (sectores): separar con coma. Ejemplo `"1,3,4"` = white/red/green.
|
||||||
|
COLOUR_TXT correspondiente: `"white/red/green"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TABLA CATCAM — DIRECCIÓN CARDINAL (solo BOYCAR / BCNCAR)
|
||||||
|
|
||||||
|
| Código | Dirección | Cuándo |
|
||||||
|
|--------|-----------|-------------------------------------------------|
|
||||||
|
| 1 | N | Boya Norte — pasa al norte de la boya |
|
||||||
|
| 2 | E | Boya Este — pasa al este |
|
||||||
|
| 3 | S | Boya Sur — pasa al sur |
|
||||||
|
| 4 | W | Boya Oeste — pasa al oeste |
|
||||||
|
|
||||||
|
Inferencia por nombre cuando CATCAM no está explícito:
|
||||||
|
- Contiene " SN", " VN", "Norte", " NN" → 1 (N)
|
||||||
|
- Contiene " SE", "Este" → 2 (E)
|
||||||
|
- Contiene " SS", " VS", "Sur" → 3 (S)
|
||||||
|
- Contiene " SO", " BB", "Oeste", " SW" → 4 (W)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TABLA ORIENT — RUMBO DE ENFILACIÓN (solo LIGHTS con enfilación)
|
||||||
|
|
||||||
|
- Solo se rellena cuando la ayuda es una enfilación (leading light / range mark)
|
||||||
|
- Valor en grados verdaderos (0–360), con un decimal
|
||||||
|
- Es el rumbo que sigue el buque cuando está alineado con las luces
|
||||||
|
- Si el PDF no especifica rumbo → dejar ORIENT vacío
|
||||||
|
- Ejemplos: `135.7`, `167.3`, `347.5`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EJEMPLOS COMPLETOS POR TIPO DE AYUDA
|
||||||
|
|
||||||
|
### Faro (LIGHTS)
|
||||||
|
```
|
||||||
|
35,Faro Castillogrande,-75.545000,10.391000,LIGHTS,2,Fl,,1,white,15,12,24,,,Torre en concreto color beige,Fl. W 15 s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Faro con grupos (LIGHTS)
|
||||||
|
```
|
||||||
|
34,Faro Punta Canoas,-75.499167,10.573000,LIGHTS,2,Fl,2,1,white,20,12,96,,,Torre roja bandas blancas. Giratorio,Fl.(2) W 20 s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enfilación (LIGHTS con ORIENT)
|
||||||
|
```
|
||||||
|
196,Enfilacion E1,-74.848333,11.103667,LIGHTS,6,Iso,,6,white,5,13,10,135.7,,Baliza enrejado naranja y blanco,Iso Bu 5s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boya cardinal Norte (BOYCAR)
|
||||||
|
```
|
||||||
|
250,Boya SN,-75.521000,10.366000,BOYCAR,4,Q,,1,white,15,4,4,,1,Castillete cardinal N negro,Q.W 15s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boya cardinal Sur (BOYCAR)
|
||||||
|
```
|
||||||
|
256,Boya SS,-75.527500,10.331833,BOYCAR,3,4,Q,(6)+,1,white,15,4,4,,3,Castillete cardinal S negros,Q.(6)+LFl.W 15s,AAN-DIMAR-2024-770
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boya cardinal Este (BOYCAR)
|
||||||
|
```
|
||||||
|
253,Boya SE,-75.512000,10.365000,BOYCAR,4,Q,3,1,white,10,4,4,,2,Castillete cardinal E negros,Q.(3)W 10s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boya cardinal Oeste (BOYCAR)
|
||||||
|
```
|
||||||
|
252,Boya SO,-75.534000,10.372000,BOYCAR,2,Fl,9,1,white,15,4,4,,4,Castillete cardinal W negros,Fl.(9)W 15s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boya lateral verde (BOYLAT)
|
||||||
|
```
|
||||||
|
240,Boya No. 1,-75.563000,10.345000,BOYLAT,2,Fl,,4,green,3,3,4,,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boya lateral roja (BOYLAT)
|
||||||
|
```
|
||||||
|
241,Boya No. 2,-75.560000,10.347000,BOYLAT,2,Fl,,3,red,3,3,4,,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boya de peligro aislado (BOYISD)
|
||||||
|
```
|
||||||
|
258,Boya Peligro Aislado Polvorines,-75.536000,10.351167,BOYISD,2,Fl,2,1,white,5,4,2.5,,,Castillete roja bandas negras,Fl.(2) W 5 s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Baliza lateral con enfilación (BCNLAT)
|
||||||
|
```
|
||||||
|
257,Enfilacion de Bocachica B,-75.508833,10.320833,BCNLAT,7,Iso,,5,blue,4,12,33,,,Torre enrejada rojo bandas blancas,Iso. Bu 4 s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
### Luz de aproximación (LIGHTS color especial)
|
||||||
|
```
|
||||||
|
296,Luz de Aproximacion,-75.549500,10.409000,LIGHTS,2,Fl,,5,blue,2.5,11,37,,,Torre metalica roja y blanca,Fl. Bu 2.5 s,DIMAR Lista de Luces 2015
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CONVERSIÓN DE COORDENADAS DIMAR
|
||||||
|
|
||||||
|
DIMAR publica en grados y minutos decimales: `10°23.45'N 75°32.67'W`
|
||||||
|
|
||||||
|
Fórmula: `grados + minutos/60`
|
||||||
|
- Latitud: 10 + 23.45/60 = **10.390833** (positivo = Norte)
|
||||||
|
- Longitud: 75 + 32.67/60 = **75.544500** → con signo negativo = **-75.544500** (Oeste)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## INSTRUCCIÓN PARA CLAUDE
|
||||||
|
|
||||||
|
Cuando el usuario pase un PDF de DIMAR:
|
||||||
|
1. Lee este archivo SCHEMA_REFERENCIA.md primero
|
||||||
|
2. Extrae cada ayuda a la navegación del PDF
|
||||||
|
3. Convierte coordenadas a decimal WGS84
|
||||||
|
4. Mapea característica de luz a LITCHR + COLOUR + SIGPER + SIGGRP
|
||||||
|
5. Asigna feat_type según el tipo de estructura
|
||||||
|
6. Asigna CATCAM si es cardinal, ORIENT si es enfilación
|
||||||
|
7. Genera el CSV con exactamente las columnas de este schema, en el mismo orden
|
||||||
|
8. Nombra el archivo: `dimar_ayudas_<puerto>.csv`
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Build CO1CO01M.000 directly from dimar_ayudas_barranquilla.csv
|
||||||
|
without needing a QGIS project file.
|
||||||
|
"""
|
||||||
|
import sys, json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Import S57CellWriter from converter (it's defined there)
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
from converter import S57CellWriter
|
||||||
|
|
||||||
|
# ── paths ─────────────────────────────────────────────────────────────────────
|
||||||
|
HERE = Path(__file__).parent
|
||||||
|
CSV = HERE / "dimar_ayudas_barranquilla.csv"
|
||||||
|
CONFIG = HERE / "cell_config.json"
|
||||||
|
OUTPUT = HERE / "dist" / "CO1CO01M" / "CO1CO01M.000"
|
||||||
|
|
||||||
|
# ── load config ───────────────────────────────────────────────────────────────
|
||||||
|
with open(CONFIG, encoding="utf-8") as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
|
||||||
|
# Update issue date and ensure correct cell name
|
||||||
|
cfg["cell_name"] = "CO1CO01M"
|
||||||
|
cfg["issue_date"] = "20260430"
|
||||||
|
cfg["update_number"] = 1
|
||||||
|
|
||||||
|
# ── build ─────────────────────────────────────────────────────────────────────
|
||||||
|
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
attr_map = cfg.get("attribute_mappings", {})
|
||||||
|
|
||||||
|
print(f"Input: {CSV}")
|
||||||
|
print(f"Output: {OUTPUT}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
writer = S57CellWriter(str(OUTPUT), cfg)
|
||||||
|
writer.open()
|
||||||
|
|
||||||
|
count = writer.add_features_from_csv(
|
||||||
|
CSV,
|
||||||
|
"BOYLAT", # default class (overridden per-row by feat_type column)
|
||||||
|
attr_map,
|
||||||
|
x_field="lon",
|
||||||
|
y_field="lat",
|
||||||
|
)
|
||||||
|
|
||||||
|
writer.close()
|
||||||
|
writer.summary()
|
||||||
|
|
||||||
|
print(f"\n✓ {count} feature(s) written")
|
||||||
|
print(f" {OUTPUT} ({OUTPUT.stat().st_size // 1024} KB)")
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
Build CO4CTG01M.000 — Carta ENC S-57 de la Bahía de Cartagena.
|
||||||
|
Lee todas las capas CSV de capas_ctg/ y genera un archivo S-57 válido
|
||||||
|
para OpenCPN, AR ECDIS y cualquier software compatible IHO S-57.
|
||||||
|
|
||||||
|
Uso:
|
||||||
|
python build_cartagena.py
|
||||||
|
python build_cartagena.py --out dist/MI_CARTA/MI_CARTA.000
|
||||||
|
|
||||||
|
Columnas especiales en los CSV:
|
||||||
|
feat_type — acrónimo S-57 de la fila (BOYCAR, LIGHTS, etc.)
|
||||||
|
CATCAM — dirección cardinal (1=N 2=E 3=S 4=W) → se escribe directo al S-57
|
||||||
|
ORIENT — rumbo de enfilación en grados → se escribe directo al S-57
|
||||||
|
_* — columnas privadas, se ignoran
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
from converter import S57CellWriter
|
||||||
|
|
||||||
|
# ── rutas ─────────────────────────────────────────────────────────────────────
|
||||||
|
HERE = Path(__file__).parent
|
||||||
|
CAPAS_DIR = HERE / "capas_ctg"
|
||||||
|
OUTPUT = HERE / "dist" / "CO4CTG01M" / "CO4CTG01M.000"
|
||||||
|
|
||||||
|
# ── orden de carga de capas (primero las estructuras, luego las luces) ────────
|
||||||
|
# El orden importa: si una fila de LIGHTS.csv tiene feat_type=LIGHTS su companion
|
||||||
|
# light ya no se emite dos veces porque solo las clases _STRUCT_CLASSES generan
|
||||||
|
# companion. Pero al cargar BOYCAR/BCNLAT antes que LIGHTS evitamos duplicados.
|
||||||
|
LAYERS = [
|
||||||
|
# archivo CSV clase S-57 por defecto (se sobreescribe por feat_type)
|
||||||
|
("BOYCAR.csv", "BOYCAR"),
|
||||||
|
("BCNLAT.csv", "BCNLAT"),
|
||||||
|
("BOYISD.csv", "BOYISD"),
|
||||||
|
("BOYLAT.csv", "BOYLAT"),
|
||||||
|
("BOYSPEC.csv", "BOYSPP"),
|
||||||
|
("LIGHTS.csv", "LIGHTS"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--out", default=str(OUTPUT), help="Ruta del archivo .000 de salida")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
out_path = Path(args.out)
|
||||||
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
cfg = {
|
||||||
|
"cell_name": "CO4CTG01M",
|
||||||
|
"cell_edition": 1,
|
||||||
|
"update_number": 0,
|
||||||
|
"issue_date": date.today().strftime("%Y%m%d"),
|
||||||
|
"producer_code": "CO",
|
||||||
|
"producer_name": "DIMAR / AR ECDIS",
|
||||||
|
"data_set_name": "Bahia de Cartagena ENC",
|
||||||
|
"scale": 12000,
|
||||||
|
"compilation_scale":12000,
|
||||||
|
"comment": "Generated by QGIS S-57 Converter from capas_ctg",
|
||||||
|
"horizontal_datum": "WGS84",
|
||||||
|
"vertical_datum": "MLLW",
|
||||||
|
"sounding_datum": "MLLW",
|
||||||
|
"attribute_mappings": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Input dir : {CAPAS_DIR}")
|
||||||
|
print(f"Output : {out_path}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
writer = S57CellWriter(str(out_path), cfg)
|
||||||
|
writer.open()
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
for csv_name, default_class in LAYERS:
|
||||||
|
csv_path = CAPAS_DIR / csv_name
|
||||||
|
if not csv_path.exists():
|
||||||
|
print(f" [SKIP] {csv_name} — no encontrado")
|
||||||
|
continue
|
||||||
|
n = writer.add_features_from_csv(
|
||||||
|
csv_path,
|
||||||
|
default_class,
|
||||||
|
attr_map={},
|
||||||
|
x_field="lon",
|
||||||
|
y_field="lat",
|
||||||
|
)
|
||||||
|
print(f" {csv_name:20s} → {n:3d} feature(s)")
|
||||||
|
total += n
|
||||||
|
|
||||||
|
writer.close()
|
||||||
|
writer.summary()
|
||||||
|
|
||||||
|
size_kb = out_path.stat().st_size // 1024
|
||||||
|
print(f"\n✓ {total} feature(s) escritos")
|
||||||
|
print(f" {out_path} ({size_kb} KB)")
|
||||||
|
print()
|
||||||
|
print("Para OpenCPN: Herramientas → Opciones → Cartas → Agregar directorio")
|
||||||
|
print(f" → {out_path.parent}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
build_ecdis_manual.py — Reconstruye los GeoJSONs del ECDIS manual chart
|
||||||
|
desde los CSVs fuente. Funciona igual para CUALQUIER puerto.
|
||||||
|
|
||||||
|
Uso:
|
||||||
|
python build_ecdis_manual.py capas_ctg BAHÍA_DE_CARTAGENA
|
||||||
|
python build_ecdis_manual.py capas_baq BARRANQUILLA
|
||||||
|
python build_ecdis_manual.py capas_ptco BUENAVENTURA
|
||||||
|
|
||||||
|
El script:
|
||||||
|
1. Lee todos los *.csv del directorio de capas
|
||||||
|
2. Genera un GeoJSON por feat_type con atributos S-57 limpios
|
||||||
|
3. Escribe los GeoJSONs en el directorio manual del ECDIS
|
||||||
|
|
||||||
|
Reglas de datos:
|
||||||
|
- feat_type determina el archivo de salida (LIGHTS.geojson, BOYCAR.geojson...)
|
||||||
|
- SIGGRP "**" → null (limpia basura de GDAL)
|
||||||
|
- LITCHR_TXT se convierte a código S-57 si LITCHR está vacío
|
||||||
|
- COLOUR_TXT se convierte a código S-57 si COLOUR está vacío
|
||||||
|
- CATCAM y ORIENT se pasan directo al GeoJSON
|
||||||
|
- INFORM se preserva completo
|
||||||
|
"""
|
||||||
|
import csv, json, sys, argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ECDIS_DATA = Path(__file__).parent.parent / "AR ECDIS" / "webecdis" / "data" / "charts" / "manual"
|
||||||
|
|
||||||
|
LITCHR_TXT = {
|
||||||
|
"f":"1","fl":"2","lfl":"3","q":"4","vq":"5","uq":"6",
|
||||||
|
"iso":"7","oc":"8","iq":"9","mo":"12","ffl":"13",
|
||||||
|
}
|
||||||
|
COLOUR_TXT = {
|
||||||
|
"white":"1","black":"2","red":"3","green":"4","blue":"5",
|
||||||
|
"yellow":"6","grey":"7","brown":"8","amber":"9","violet":"10",
|
||||||
|
"orange":"11","magenta":"12",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _fval(s):
|
||||||
|
s = (s or "").strip()
|
||||||
|
if not s or all(c in "* " for c in s): return None
|
||||||
|
try: return float(s)
|
||||||
|
except: return None
|
||||||
|
|
||||||
|
def _ival(s):
|
||||||
|
s = (s or "").strip()
|
||||||
|
if not s or all(c in "* " for c in s): return None
|
||||||
|
try: return int(float(s))
|
||||||
|
except: return None
|
||||||
|
|
||||||
|
def _sval(s):
|
||||||
|
s = (s or "").strip()
|
||||||
|
return s if s and not all(c in "* " for c in s) else None
|
||||||
|
|
||||||
|
def _parse_litchr(row):
|
||||||
|
v = _ival(row.get("LITCHR", ""))
|
||||||
|
if v is not None: return v
|
||||||
|
txt = (row.get("LITCHR_TXT") or "").lower().split("(")[0].strip()
|
||||||
|
c = LITCHR_TXT.get(txt)
|
||||||
|
return int(c) if c else None
|
||||||
|
|
||||||
|
def _parse_colour(row):
|
||||||
|
v = (row.get("COLOUR") or "").strip()
|
||||||
|
if v and not all(c in "* " for c in v):
|
||||||
|
parts = [p.strip() for p in v.split(",") if p.strip().isdigit()]
|
||||||
|
if parts: return [int(p) for p in parts]
|
||||||
|
txt = (row.get("COLOUR_TXT") or "").lower().strip()
|
||||||
|
c = COLOUR_TXT.get(txt)
|
||||||
|
return [int(c)] if c else None
|
||||||
|
|
||||||
|
def build(capas_dir: Path, chart_name: str, ecdis_data: Path):
|
||||||
|
out_dir = ecdis_data / chart_name
|
||||||
|
if not out_dir.exists():
|
||||||
|
print(f"[WARN] Directorio ECDIS no existe: {out_dir}")
|
||||||
|
print(f" Creando...")
|
||||||
|
out_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
layers: dict[str, list] = {}
|
||||||
|
rcid = 1
|
||||||
|
|
||||||
|
for csv_file in sorted(capas_dir.glob("*.csv")):
|
||||||
|
default_layer = csv_file.stem.upper()
|
||||||
|
with open(csv_file, newline="", encoding="utf-8-sig") as f:
|
||||||
|
for row in csv.DictReader(f):
|
||||||
|
try:
|
||||||
|
lon = float(row.get("lon", "").strip())
|
||||||
|
lat = float(row.get("lat", "").strip())
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
feat_type = (_sval(row.get("feat_type", "")) or default_layer).upper()
|
||||||
|
|
||||||
|
props = {
|
||||||
|
"RCID": rcid,
|
||||||
|
"PRIM": 1,
|
||||||
|
"GRUP": 1,
|
||||||
|
"OBJL": 75,
|
||||||
|
"RVER": 1,
|
||||||
|
"AGEN": 999,
|
||||||
|
"FIDN": rcid,
|
||||||
|
"FIDS": 1,
|
||||||
|
"LNAM": f"03E7{rcid:08X}0001",
|
||||||
|
"OBJNAM": _sval(row.get("OBJNAM", "")),
|
||||||
|
"LITCHR": _parse_litchr(row),
|
||||||
|
"COLOUR": _parse_colour(row),
|
||||||
|
"SIGGRP": _sval(row.get("SIGGRP", "")),
|
||||||
|
"SIGPER": _fval(row.get("SIGPER", "")),
|
||||||
|
"VALNMR": _fval(row.get("VALNMR", "")),
|
||||||
|
"HEIGHT": _fval(row.get("HEIGHT", "")),
|
||||||
|
"ORIENT": _fval(row.get("ORIENT", "")),
|
||||||
|
"CATCAM": _ival(row.get("CATCAM", "")),
|
||||||
|
"INFORM": _sval(row.get("INFORM", "")),
|
||||||
|
"NOBJNM": _sval(row.get("NOBJNM", "")),
|
||||||
|
}
|
||||||
|
|
||||||
|
feat = {
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"coordinates": [lon, lat],
|
||||||
|
"type": "Point",
|
||||||
|
"geometries": None
|
||||||
|
},
|
||||||
|
"properties": props
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.setdefault(feat_type, []).append(feat)
|
||||||
|
rcid += 1
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
for layer, feats in sorted(layers.items()):
|
||||||
|
fc = {"type": "FeatureCollection", "features": feats}
|
||||||
|
out_file = out_dir / f"{layer}.geojson"
|
||||||
|
out_file.write_text(json.dumps(fc, ensure_ascii=False), encoding="utf-8")
|
||||||
|
print(f" {layer}.geojson : {len(feats)} features")
|
||||||
|
total += feats.__len__()
|
||||||
|
|
||||||
|
print(f"\nOK {total} features en {out_dir}")
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description="Rebuild ECDIS manual GeoJSONs from CSV layers")
|
||||||
|
ap.add_argument("capas_dir", help="Directorio con los CSVs (capas_ctg, capas_baq...)")
|
||||||
|
ap.add_argument("chart_name", help="Nombre del chart en ECDIS (BAHÍA_DE_CARTAGENA, BARRANQUILLA...)")
|
||||||
|
ap.add_argument("--ecdis", default=str(ECDIS_DATA),
|
||||||
|
help=f"Ruta base de charts/manual del ECDIS [default: {ECDIS_DATA}]")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
capas = Path(args.capas_dir)
|
||||||
|
if not capas.exists():
|
||||||
|
print(f"ERROR: No existe {capas}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
build(capas, args.chart_name, Path(args.ecdis))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
235,Boya Cardinal Norte,-74.753500,10.959167,BOYCAR,3,Fl,,6,white,1,6,4,,Castillete cardinal N negros,Fl W 1s,DIMAR Lista de Luces 2015
|
||||||
|
236,Boya Cardinal Sur,-74.753500,10.959167,BOYCAR,3,Fl,,6,white,15,6,4,,Castillete cardinal S negros,Fl W 15s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
240,Boya Peligro Aislado,-74.757333,10.954500,BOYISD,3,Fl(2),2,6,white,4,3,3.3,,Castillete roja bandas negras. Bajo rocoso,Fl(2) W 4s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
199,Boya No. 1,-74.833500,11.084500,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
200,Boya No. 3,-74.844833,11.075833,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
202,Boya No. 5,-74.841000,11.065667,BOYLAT,1,Q,,3,green,1,6,4,,Castillete verde,Q G 1s,DIMAR Lista de Luces 2015
|
||||||
|
208,Boya No. 7,-74.837500,11.060000,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
209,Boya No. 9,-74.824000,11.046833,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
210,Boya No. 11,-74.812167,11.039667,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
211,Boya No. 12,-74.813500,11.037167,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
212,Boya No. 13,-74.802000,11.034333,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
213,Boya No. 14,-74.788333,11.021833,BOYLAT,3,Fl,,1,red,3,6,3,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
214,Boya No. 15,-74.793833,11.028167,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
215,Boya No. 16,-74.797500,11.027333,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
218,Boya No. 18,-74.788333,11.021833,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
219,Boya No. 19,-74.776500,11.017500,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
220,Boya No. 20,-74.777333,11.015500,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
221,Boya No. 21,-74.772000,11.014000,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
222,Boya No. 22,-74.773333,11.012333,BOYLAT,1,Q,,1,red,1,6,4,,Castillete roja,Q R 1s,DIMAR Lista de Luces 2015
|
||||||
|
223,Boya No. 23,-74.755000,10.975000,BOYLAT,3,Fl,,3,green,1.3,6,3,,Castillete verde,Fl G 1.3s,DIMAR Lista de Luces 2015
|
||||||
|
224,Boya No. 24,-74.770500,11.009167,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
225,Boya No. 25,-74.766333,11.006667,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
226,Boya No. 26,-74.768333,11.005833,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
227,Boya No. 27,-74.762000,10.998500,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
228,Boya No. 28,-74.765333,10.999833,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
229,Boya No. 29,-74.758000,10.987333,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
230,Boya No. 30,-74.760667,10.987333,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
231,Boya No. 31,-74.754833,10.975000,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
232,Boya No. 33,-74.755667,10.959333,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
233,Boya No. 35,-74.754167,10.942667,BOYLAT,3,Fl,,3,green,3,6,4,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
234,Boya No. 36,-74.756667,10.941500,BOYLAT,3,Fl,,1,red,3,6,4,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
239,Boya de Oleaje,-74.758000,11.134000,BOYSPEC,3,Fl,,11,yellow,20,4.5,0.5,,Esferica amarilla. Recolectora datos oceanograficos,Fl Y 20s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
13,Faro F1 Recalada,-74.849500,11.106167,LIGHTS,6,Iso,,3,green,2,9,20,,Torre naranja bandas blancas. Faro de Recalada,Iso G 2s,DIMAR Lista de Luces 2015
|
||||||
|
14,Faro F2 Recalada,-74.854667,11.106000,LIGHTS,6,Iso,,1,red,2,13.4,23,,Torre naranja bandas blancas. Racon B,Iso R 2s,DIMAR Lista de Luces 2015
|
||||||
|
32,Faro Morro Hermoso,-75.017500,10.963333,LIGHTS,3,Fl,,6,white,4,28,134,,Torre blanca bandas rojas. Giratorio,Fl W 4s,DIMAR Lista de Luces 2015
|
||||||
|
33,Faro Galerazamba,-75.266000,10.785333,LIGHTS,3,Fl,,6,white,4,11,14,,Torre fibra vidrio blanca bandas rojas. Giratorio,Fl W 4s,DIMAR Lista de Luces 2015
|
||||||
|
15,Faro X1,-74.849500,11.102167,LIGHTS,1,Q(4)G,4,3,green,11,6,6,,Torre verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
16,Faro X2,-74.853333,11.100000,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
18,Faro X3,-74.847167,11.091333,LIGHTS,1,Q(4)G,4,3,green,11,6,6,,Torre verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
17,Faro X4,-74.851667,11.093000,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
19,Faro X5,-74.846667,11.089167,LIGHTS,1,Q(4)G,4,3,green,11,6,6,,Torre verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
20,Faro X6,-74.850500,11.087667,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
21,Faro X7,-74.814333,11.041500,LIGHTS,1,Q(4)G,4,3,green,11,6,8,,Baliza enrejado rojo bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
22,Faro X8,-74.849167,11.081667,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
23,Faro X9,-74.804833,11.035833,LIGHTS,1,Q(4)G,4,3,green,11,6,8,,Baliza enrejado rojo bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
24,Faro X10,-74.848000,11.076000,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
25,Faro X11,-74.795500,11.029833,LIGHTS,1,Q(4)G,4,3,green,11,6,8,,Baliza enrejado rojo bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
26,Faro X12,-74.844167,11.065000,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Baliza enrejado roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
27,Faro X13,-74.789667,11.025833,LIGHTS,1,Q(4)G,4,3,green,11,6,8,,Baliza enrejado rojo bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
28,Faro X14,-74.839500,11.057833,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
30,Faro X15,-74.785500,11.022833,LIGHTS,1,Q(4)G,4,3,green,11,6,6,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
29,Faro X16,-74.833000,11.050000,LIGHTS,1,Q(4)R,4,1,red,11,6,6,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
31,Faro X17,-74.778333,11.018667,LIGHTS,1,Q(4)G,4,3,green,11,6,6,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
196,Enfilacion E1,-74.848333,11.103667,LIGHTS,6,Iso,,6,white,5,13,10,135.7,Baliza enrejado naranja y blanco. Rumbo 135.7,Iso Bu 5s,DIMAR Lista de Luces 2015
|
||||||
|
197,Enfilacion E3,-74.846333,11.101667,LIGHTS,6,Iso,,6,white,5,9,22,139.3,Torre enrejada naranja y blanco. Rumbo 139.3,Iso Bu 5s,DIMAR Lista de Luces 2015
|
||||||
|
198,Enfilacion E3A,-74.845000,11.100333,LIGHTS,6,Iso,,6,white,5,12.3,20,135.7,Torre naranja y blanco. Rumbo 135.7,Iso W 5s,DIMAR Lista de Luces 2015
|
||||||
|
201,Enfilacion E4,-74.846833,11.070167,LIGHTS,6,Iso,,1,red,4,4.5,11,142.3,Baliza enrejado naranja bandas blancas. Rumbo 142.3,Iso R 4s,DIMAR Lista de Luces 2015
|
||||||
|
203,Enfilacion E6,-74.843667,11.063000,LIGHTS,6,Iso,,1,red,4,8,12,167.7,Baliza enrejado roja bandas blancas. Rumbo 167.7,Iso Bu 4s,DIMAR Lista de Luces 2015
|
||||||
|
204,Enfilacion E8,-74.841833,11.058500,LIGHTS,6,Iso,,6,white,4,14.5,25,167.7,Baliza enrejado naranja bandas blancas. Rumbo 167.7,Iso Bu 4s,DIMAR Lista de Luces 2015
|
||||||
|
205,Enfilacion E10,-74.841667,11.059833,LIGHTS,6,Iso,,3,green,5,10,11,167.3,Torre naranja bandas blancas. Rumbo 167.3,Iso G 5s,DIMAR Lista de Luces 2015
|
||||||
|
206,Enfilacion E12,-74.840667,11.056167,LIGHTS,6,Iso,,3,green,5,8,22,167.3,Baliza tablero blanco franja roja. Rumbo 167.3,Iso G 5s,DIMAR Lista de Luces 2015
|
||||||
|
207,Enfilacion E14,-74.840667,11.056167,LIGHTS,6,Iso,,6,white,6,8,22,122,Tablero blanco con franja roja. Rumbo 122,Iso Bu 6s,DIMAR Lista de Luces 2015
|
||||||
|
237,Enfilacion E16,-74.836667,11.053667,LIGHTS,6,Iso,,6,white,6,9,12,122,Baliza enrejado naranja bandas blancas. Rumbo 122,Iso Bu 6s,DIMAR Lista de Luces 2015
|
||||||
|
238,Enfilacion E18,-74.825500,11.043000,LIGHTS,3,Fl,,"1,3,6",white/red/green,4,6,18,142,Torre roja bandas blancas. Sector 9 grados. Rumbo 142,Fl WRG 4s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
257,Enfilacion de Bocachica B,-75.508833,10.320833,BCNLAT,7,Iso,,5,blue,4,12,33,,Torre enrejada rojo bandas blancas,Iso. Bu 4 s,DIMAR Lista de Luces 2015
|
||||||
|
304,Baliza No. 01,-75.5245,10.304,BCNLAT,2,Fl,,4,green,2,3,4.5,,Poste cilindrico verde,Fl. G 2 s,DIMAR Lista de Luces 2015
|
||||||
|
305,Baliza No. 02,-75.5245,10.304,BCNLAT,2,Fl,,3,red,2,3,4.5,,Poste cilindrico rojo,Fl. R 2 s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,CATCAM,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
256,Boya SS,-75.527500,10.331833,BOYCAR,3,4,Q,(6)+,1,white,15,4,4,,Castillete Cardinal S negros - Senala Bajo Santa Cruz,Q.(6)+LFl.W 15s,AAN-DIMAR-2024-770
|
||||||
|
266,Boya SN,-75.526667,10.3445,BOYCAR,1,4,Q,,1,white,1,3,4,,Castillete Cardinal N negros - Senala Bajo Santa Cruz,Q. W 1 s,DIMAR Lista de Luces 2015
|
||||||
|
289,Boya VN,-75.5425,10.399833,BOYCAR,1,4,Q,,1,white,1,3,4,,Castillete Cardinal N negros - Senalizacion Bajo la Virgen,Q. W 1 s,DIMAR Lista de Luces 2015
|
||||||
|
290,Boya VS,-75.5415,10.393667,BOYCAR,3,4,Q,6,1,white,15,3,4,,Castillete Cardinal S negros - Senalizacion Bajo la Virgen,Q.(6)+Fl. W 15 s,DIMAR Lista de Luces 2015
|
||||||
|
297,Boya BB1,-75.516167,10.326167,BOYCAR,4,4,Q,9,1,white,15,3,4,,Castillete Cardinal W negros - Senala Bajo Brujas,Q.(9) W 15 s,DIMAR Lista de Luces 2015
|
||||||
|
298,Boya BB2,-75.515167,10.322500,BOYCAR,4,4,Q,9,1,white,15,3,4,,Castillete Cardinal W negros - Senala Bajo Brujas,Q.(9) W 15 s,AAN-DIMAR-2025-180
|
||||||
|
299,Boya SN (Salmedina),-75.648022,10.384530,BOYCAR,1,4,Q,,1,white,1,3,4,,Castillete Cardinal N negros - Senala Bancos de Salmedina,Q. W 1 s,AAN-DIMAR-2025-261
|
||||||
|
300,Boya SS (Salmedina),-75.651000,10.364833,BOYCAR,3,4,Q,6,1,white,15,3,4,,Castillete Cardinal S negros - Senala Bancos de Salmedina,Q.(6)+Fl. W 15 s,AAN-DIMAR-2024-246
|
||||||
|
301,Boya SE (Salmedina),-75.635850,10.380173,BOYCAR,2,4,Q,3,1,white,10,3,4,,Castillete Cardinal E negros - Senala Bancos de Salmedina,Q.(3) W 10 s,AAN-DIMAR-2025-154
|
||||||
|
302,Boya SO (Salmedina),-75.689133,10.381967,BOYCAR,4,4,Q,9,1,white,10,3,4,,Castillete Cardinal W negros - Senala Bancos de Salmedina,Q.(9) W 10 s,AAN-DIMAR-2024-229
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
258,Boya Peligro Aislado Polvorines,-75.536,10.351167,BOYISD,2,Fl,2,1,white,5,4,2.5,,Castillete roja bandas negras,Fl.(2) W 5 s,DIMAR Lista de Luces 2015
|
||||||
|
259,Boya TT,-75.521,10.366667,BOYISD,2,Fl,2,1,white,5,3,4,,Castillete roja bandas negras - Boya de Peligro Aislado La Tata,Fl.(2) W 5 s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,65 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
242,Boya No. 1,-75.588833,10.318,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
243,Boya No. 2,-75.589167,10.314833,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
244,Boya No. 3,-75.584833,10.316667,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
245,Boya No. 4,-75.585333,10.315167,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
246,Boya No. 5,-75.580667,10.316833,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2023-268
|
||||||
|
247,Boya No. 6,-75.580667,10.3155,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
248,Boya No. 7,-75.576083,10.317598,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2022-067
|
||||||
|
249,Boya No. 8,-75.576167,10.315833,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2025-315
|
||||||
|
250,Boya No. 9,-75.572333,10.317667,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
251,Boya No. 10,-75.572167,10.316,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
252,Boya No. 11,-75.565517,10.318358,BOYLAT,4,Q,,4,green,1,3,4,,Castillete verde - Boya de viraje,Q. G 1 s,AAN-DIMAR-2022-193
|
||||||
|
253,Boya No. 12,-75.563,10.314167,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
254,Boya No. 13,-75.556333,10.3235,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
255,Boya No. 15,-75.551500,10.327333,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2025-403
|
||||||
|
260,Boya No. 17,-75.545833,10.331333,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2021-325
|
||||||
|
261,Boya No. 18,-75.540750,10.320367,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2024-328
|
||||||
|
262,Boya No. 19,-75.540667,10.319333,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
263,Boya No. 20,-75.518,10.3305,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
264,Boya No. 21,-75.524000,10.320000,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2021-237
|
||||||
|
265,Boya No. 22,-75.517350,10.335050,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2025-038
|
||||||
|
267,Boya No. 23,-75.533080,10.341723,BOYLAT,4,Q,,4,green,1,3,4,,Castillete verde - Boya de viraje,Q. G 1 s,AAN-DIMAR-2023-108
|
||||||
|
268,Boya No. 24,-75.529667,10.340333,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
269,Boya No. 25,-75.532500,10.348333,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2023-183
|
||||||
|
270,Boya No. 26,-75.518215,10.347442,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2024-273
|
||||||
|
271,Boya No. 27,-75.532287,10.355342,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2022-626
|
||||||
|
272,Boya No. 28,-75.520167,10.358833,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2025-452
|
||||||
|
273,Boya No. 29,-75.5365,10.364167,BOYLAT,4,Q,,4,green,1,3,4,,Castillete verde - Boya de viraje,Q. G 1 s,DIMAR Lista de Luces 2015
|
||||||
|
274,Boya No. 30,-75.522667,10.364833,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
275,Boya No. 31,-75.5445,10.368167,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
276,Boya No. 32,-75.533200,10.378533,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2025-625
|
||||||
|
277,Boya No. 33,-75.543833,10.391333,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2024-263
|
||||||
|
278,Boya No. 34,-75.539833,10.393167,BOYLAT,4,Q,,3,red,1,3,4,,Castillete roja - Boya de viraje,Q. R 1 s,DIMAR Lista de Luces 2015
|
||||||
|
279,Boya No. 35,-75.544500,10.394000,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2022-243
|
||||||
|
280,Boya No. 36,-75.544167,10.396167,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2023-159
|
||||||
|
281,Boya No. 37,-75.540017,10.396500,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2024-171
|
||||||
|
282,Boya No. 38,-75.538667,10.395167,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
283,Boya No. 39,-75.539668,10.398237,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2021-150
|
||||||
|
284,Boya No. 40,-75.536833,10.397333,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
285,Boya No. 41,-75.545833,10.395147,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2023-016
|
||||||
|
286,Boya No. 42,-75.544672,10.397017,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2022-154
|
||||||
|
287,Boya No. 43,-75.550150,10.400018,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2023-371
|
||||||
|
288,Boya No. 45,-75.55,10.4065,BOYLAT,2,Fl,,4,green,3,3,4,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
291,Boya No. 48,-75.545167,10.412000,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2025-089
|
||||||
|
294,Boya E2,-75.571,10.389667,BOYLAT,2,Fl,,3,red,3,4,3,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
295,Boya E1,-75.570667,10.39,BOYLAT,2,Fl,,4,green,3,3,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
307,Boya No. 50,-75.547167,10.415167,BOYLAT,2,Fl,,3,red,3,3,4,,Castillete roja rojo,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
308,Boya No. 51,-75.547500,10.415167,BOYLAT,2,Fl,,4,green,3,3,3,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2024-296
|
||||||
|
309,Boya No. 52,-75.549305,10.417628,BOYLAT,2,Fl,,3,red,3,3,3,,Castillete roja,Fl. R 3 s,AAN-DIMAR-2023-032
|
||||||
|
310,Boya No. 53,-75.549918,10.419850,BOYLAT,2,Fl,,4,green,3,3,3,,Castillete verde,Fl. G 3 s,AAN-DIMAR-2022-262
|
||||||
|
311,Boya No. 54,-75.5495,10.419,BOYLAT,2,Fl,,3,red,3,3,3,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
312,Boya No. 55,-75.55,10.419333,BOYLAT,2,Fl,,4,green,3,3,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
314,Boya No. 56,-75.55,10.420333,BOYLAT,2,Fl,,3,red,3,3,3,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
317,Boya Bifurcacion,-75.529333,10.402667,BOYLAT,2,Fl,,4,green,3,4,3,,Castillete verde bandas rojas,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
318,Boya Verde,-75.529167,10.4035,BOYLAT,2,Fl,,4,green,3,4,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
325,Boya No. 1 (Sector Compas),-75.529833,10.402167,BOYLAT,2,Fl,,4,green,3,,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
326,Boya No. 2 (Sector Compas),-75.5305,10.401167,BOYLAT,2,Fl,,3,red,3,,3,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
327,Boya No. 3 (Sector Compas),-75.528167,10.4015,BOYLAT,2,Fl,,4,green,3,,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
328,Boya No. 4 (Sector Compas),-75.528667,10.400667,BOYLAT,2,Fl,,3,red,3,,3,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
329,Boya No. 5 (Sector Compas),-75.526167,10.4005,BOYLAT,2,Fl,,4,green,3,,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
330,Boya No. 6 (Sector Compas),-75.527833,10.4,BOYLAT,2,Fl,,3,red,3,,3,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
331,Boya No. 7 (Sector Compas),-75.525667,10.399833,BOYLAT,2,Fl,,4,green,3,,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
332,Boya No. 8 (Sector Compas),-75.532167,10.397667,BOYLAT,2,Fl,,3,red,3,,3,,Castillete roja,Fl. R 3 s,DIMAR Lista de Luces 2015
|
||||||
|
333,Boya No. 9 (Sector Compas),-75.525333,10.399333,BOYLAT,2,Fl,,4,green,3,,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
334,Boya No. 13 (Sector Compas),-75.531167,10.398833,BOYLAT,2,Fl,,4,green,3,,3,,Castillete verde,Fl. G 3 s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
303,Boya Metocean,-75.609667,10.3265,BOYSPEC,2,Fl,,6,yellow,20,7,3.5,,Castillete Amarillo. Sistema datos oceanograficos y meteorologicos.,Fl. Y 20 s,DIMAR Lista de Luces 2015
|
||||||
|
306,Boya Especial,-75.520333,10.3035,BOYSPEC,2,Fl,,6,yellow,10,1.5,3.5,,Cilindrica amarilla,Fl. Y 10 s,DIMAR Lista de Luces 2015
|
||||||
|
313,Boya BA7,-75.549667,10.420833,BOYSPEC,2,Fl,,6,yellow,3,3,2.6,,Castillete amarilla,Fl. Y 3 s,DIMAR Lista de Luces 2015
|
||||||
|
335,Boya Especial No. 3 Isla Manzanillo,-75.530833,10.3955,BOYSPEC,2,Fl,2,6,yellow,10,3,4,,Castillete amarilla,Fl.(2) Y 10 s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,VALNMR,HEIGHT,ORIENT,INFORM,_dimar_char_raw,_source
|
||||||
|
32,Faro Punta Morro Hermoso,-75.0175,10.963333,LIGHTS,2,Fl,,1,white,4,28,134,,Torre blanca bandas rojas. Giratorio.,Fl. W 4 s,DIMAR Lista de Luces 2015
|
||||||
|
33,Faro Galerazamba,-75.266,10.785333,LIGHTS,2,Fl,,1,white,4,11,14,,Torre fibra de vidrio blanca bandas rojas. Giratorio.,Fl. W 4 s,DIMAR Lista de Luces 2015
|
||||||
|
34,Faro Punta Canoas,-75.499167,10.573,LIGHTS,2,Fl,2,1,white,20,12,96,,Torre roja bandas blancas. Giratorio.,Fl.(2) W 20 s,DIMAR Lista de Luces 2015
|
||||||
|
35,Faro Castillogrande,-75.545,10.391,LIGHTS,2,Fl,,1,white,15,12,24,,Torre en concreto color beige.,Fl. W 15 s,DIMAR Lista de Luces 2015
|
||||||
|
36,Faro Salmedina,-75.651333,10.378333,LIGHTS,2,Fl,,1,white,10,8,5,,Torre roja bandas blancas.,Fl. W 10 s,DIMAR Lista de Luces 2015
|
||||||
|
37,Faro Tierrabomba,-75.581,10.34,LIGHTS,2,Fl,,1,white,12,26,112,,Torre roja bandas blancas. Giratorio.,Fl. W 12 s,DIMAR Lista de Luces 2015
|
||||||
|
38,Faro Isla Tesoro,-75.740667,10.235333,LIGHTS,2,Fl,,1,white,6.6,13,20,,Torre metalica roja y blanca.,Fl. W 6.6 s,DIMAR Lista de Luces 2015
|
||||||
|
39,Faro Isla de Rosario,-75.8005,10.168,LIGHTS,2,Fl,3,1,white,10,12,14,,Torre metalica roja y blanca.,Fl.(3) W 10 s,DIMAR Lista de Luces 2015
|
||||||
|
40,Faro Isla Mucura,-75.87,9.7835,LIGHTS,2,Fl,,1,white,6.7,11,20,,Torre roja bandas blancas. Giratorio.,Fl. W 6.7 s,DIMAR Lista de Luces 2015
|
||||||
|
41,Faro Isla Arena,-75.726833,10.144833,LIGHTS,2,Fl,,1,white,12,13,20,,Torre metalica roja y blanca. Giratorio.,Fl. W 12 s,DIMAR Lista de Luces 2015
|
||||||
|
42,Faro Ceycen,-75.855833,9.692833,LIGHTS,2,Fl,,1,white,12,17,20,,Torre roja bandas blancas. Giratorio.,Fl. W 12 s,DIMAR Lista de Luces 2015
|
||||||
|
43,Faro Roca Morrosquillo,-75.992167,9.591333,LIGHTS,2,Fl,,1,white,3,10,6,,Torre roja bandas blancas.,Fl. W 3 s,DIMAR Lista de Luces 2015
|
||||||
|
296,Luz de Aproximacion,-75.5495,10.409,LIGHTS,2,Fl,,5,blue,2.5,11,37,,Torre metalica Roja y blanca,Fl. Bu 2.5 s,DIMAR Lista de Luces 2015
|
||||||
|
257,Enfilacion de Bocachica B,-75.508833,10.320833,LIGHTS,7,Iso,,5,blue,4,12,33,,Torre enrejada rojo bandas blancas,,DIMAR Lista de Luces 2015
|
||||||
|
292,Enfilacion No. 1,-75.530500,10.389333,LIGHTS,4,Q,,6,yellow,1,10,22,0,,Torre enrejada roja bandas blancas. Calibracion compases.,Q. Am 1 s,DIMAR Lista de Luces 2015
|
||||||
|
293,Enfilacion No. 2,-75.530500,10.388500,LIGHTS,4,Q,,6,yellow,1,10,18,0,,Torre enrejada roja bandas blancas. Calibracion compases.,Q. Am 1 s,DIMAR Lista de Luces 2015
|
||||||
|
322,Baliza de Enfilacion No. 1,-75.524667,10.405000,LIGHTS,2,Fl,,6,yellow,3,5,4,,,Torre amarilla. Marca de dia: amarillo-negro-amarillo.,Fl. Am 3 s,DIMAR Lista de Luces 2015
|
||||||
|
323,Baliza de Enfilacion No. 2,-75.524167,10.405167,LIGHTS,2,Fl,,6,yellow,3,5,6,,,Torre amarilla. Marca de dia: amarillo-negro-amarillo.,Fl. Am 3 s,DIMAR Lista de Luces 2015
|
||||||
|
@@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
"_comment": "S-57 cell metadata. Edit before converting.",
|
||||||
|
"cell_name": "CO1CO01M",
|
||||||
|
"cell_edition": 1,
|
||||||
|
"update_number": 0,
|
||||||
|
"issue_date": "20260427",
|
||||||
|
"producer_code": "CO",
|
||||||
|
"producer_name": "Custom Chart",
|
||||||
|
"data_set_name": "My ENC Chart",
|
||||||
|
"scale": 50000,
|
||||||
|
"comment": "Generated by QGIS S-57 Converter",
|
||||||
|
"horizontal_datum": "WGS84",
|
||||||
|
"vertical_datum": "MLLW",
|
||||||
|
"sounding_datum": "MLLW",
|
||||||
|
"compilation_scale": 50000,
|
||||||
|
"layer_mappings": {
|
||||||
|
"_comment": "Map your QGIS layer names to S-57 object class acronyms. Case-insensitive.",
|
||||||
|
"coastline": "COALNE",
|
||||||
|
"coast_line": "COALNE",
|
||||||
|
"linea_de_costa": "COALNE",
|
||||||
|
"costa": "COALNE",
|
||||||
|
"land": "LNDARE",
|
||||||
|
"tierra": "LNDARE",
|
||||||
|
"tierra_firme": "LNDARE",
|
||||||
|
"depth_area": "DEPARE",
|
||||||
|
"area_profundidad": "DEPARE",
|
||||||
|
"fondos": "DEPARE",
|
||||||
|
"batimetria": "DEPARE",
|
||||||
|
"depth_contour": "DEPCNT",
|
||||||
|
"isobata": "DEPCNT",
|
||||||
|
"curvas_nivel": "DEPCNT",
|
||||||
|
"soundings": "SOUNDG",
|
||||||
|
"sondas": "SOUNDG",
|
||||||
|
"profundidades": "SOUNDG",
|
||||||
|
"lights": "LIGHTS",
|
||||||
|
"luces": "LIGHTS",
|
||||||
|
"faroles": "LIGHTS",
|
||||||
|
"boyas": "BOYLAT",
|
||||||
|
"buoys": "BOYLAT",
|
||||||
|
"balizas": "BCNLAT",
|
||||||
|
"beacons": "BCNLAT",
|
||||||
|
"anchorage": "ACHARE",
|
||||||
|
"fondeadero": "ACHARE",
|
||||||
|
"harbor": "HRBARE",
|
||||||
|
"puerto": "HRBARE",
|
||||||
|
"berth": "BERTHS",
|
||||||
|
"atraque": "BERTHS",
|
||||||
|
"wreck": "WRECKS",
|
||||||
|
"naufragio": "WRECKS",
|
||||||
|
"obstruction": "OBSTRN",
|
||||||
|
"obstruccion": "OBSTRN",
|
||||||
|
"rocks": "UWTROC",
|
||||||
|
"rocas": "UWTROC",
|
||||||
|
"fairway": "FAIRWY",
|
||||||
|
"canal_navegacion": "FAIRWY",
|
||||||
|
"restricted": "RESARE",
|
||||||
|
"zona_restringida": "RESARE",
|
||||||
|
"cable": "CBLSUB",
|
||||||
|
"tuberia": "PIPSOL",
|
||||||
|
"bridge": "BRIDGE",
|
||||||
|
"puente": "BRIDGE",
|
||||||
|
"river": "RIVERS",
|
||||||
|
"rio": "RIVERS",
|
||||||
|
"seabed": "SBDARE",
|
||||||
|
"Linderos": "COALNE",
|
||||||
|
"Puntos del Terreno": "LNDMRK",
|
||||||
|
"Área Terreno": "LNDARE",
|
||||||
|
"Área Terreno taxable": "LNDARE",
|
||||||
|
"fondo_marino": "SBDARE"
|
||||||
|
},
|
||||||
|
"attribute_mappings": {
|
||||||
|
"_comment": "Map your SHP field names to S-57 attribute names.",
|
||||||
|
"name": "OBJNAM",
|
||||||
|
"nombre": "OBJNAM",
|
||||||
|
"height": "HEIGHT",
|
||||||
|
"altura": "HEIGHT",
|
||||||
|
"colour": "COLOUR",
|
||||||
|
"color": "COLOUR",
|
||||||
|
"colpat": "COLPAT",
|
||||||
|
"patron": "COLPAT",
|
||||||
|
"catlam": "CATLAM",
|
||||||
|
"lateral": "CATLAM",
|
||||||
|
"boyshp": "BOYSHP",
|
||||||
|
"forma": "BOYSHP",
|
||||||
|
"litchr": "LITCHR",
|
||||||
|
"destello": "LITCHR",
|
||||||
|
"sigper": "SIGPER",
|
||||||
|
"periodo": "SIGPER",
|
||||||
|
"siggrp": "SIGGRP",
|
||||||
|
"grupo": "SIGGRP",
|
||||||
|
"alcance": "VALNMR",
|
||||||
|
"range": "VALNMR",
|
||||||
|
"valnmr": "VALNMR",
|
||||||
|
"status": "STATUS",
|
||||||
|
"estado": "STATUS",
|
||||||
|
"depth": "DRVAL1",
|
||||||
|
"profundidad":"DRVAL1",
|
||||||
|
"depth_min": "DRVAL1",
|
||||||
|
"depth_max": "DRVAL2",
|
||||||
|
"contour": "VALDCO",
|
||||||
|
"valor": "VALDCO",
|
||||||
|
"sounding": "VALSOU",
|
||||||
|
"sonda": "VALSOU"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import geopandas as gpd
|
||||||
|
|
||||||
|
path = r"C:\Users\aerom\CO1CO01M.000"
|
||||||
|
for layer in ["COALNE", "LNDARE", "LNDMRK", "M_COVR"]:
|
||||||
|
try:
|
||||||
|
gdf = gpd.read_file(path, layer=layer)
|
||||||
|
nulls = gdf.geometry.isna().sum()
|
||||||
|
print(f"{layer}: {len(gdf)} features, {nulls} sin geometria")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{layer}: ERROR - {e}")
|
||||||
+881
@@ -0,0 +1,881 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
QGIS -> S-57 ENC Converter
|
||||||
|
Zero external dependencies for geometry: reads SHP/DBF using struct only.
|
||||||
|
pyproj is used for reprojection (optional — falls back to passthrough if missing).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python converter.py myproject.qgs
|
||||||
|
python converter.py myproject.qgz --output my_chart.000
|
||||||
|
python converter.py myproject.qgs --list
|
||||||
|
python converter.py myproject.qgs --config cell_config.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
SCRIPT_DIR = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(SCRIPT_DIR))
|
||||||
|
|
||||||
|
from s57_writer import (
|
||||||
|
S57Cell,
|
||||||
|
OBJL_BY_ACRONYM, ATTR_CODE,
|
||||||
|
OBJL_LIGHTS,
|
||||||
|
ATTL_CATCOV, ATTL_VALSOU, ATTL_LITCHR, ATTL_COLOUR,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pyproj import CRS, Transformer
|
||||||
|
PYPROJ_AVAILABLE = True
|
||||||
|
except Exception:
|
||||||
|
PYPROJ_AVAILABLE = False
|
||||||
|
|
||||||
|
# ── SHP shape-type sets ───────────────────────────────────────────────────────
|
||||||
|
_PT = {1, 11, 21}
|
||||||
|
_MPT = {8, 18, 28}
|
||||||
|
_LINE = {3, 13, 23}
|
||||||
|
_POLY = {5, 15, 25}
|
||||||
|
|
||||||
|
# ── minimal DBF reader (stdlib only) ─────────────────────────────────────────
|
||||||
|
def _read_dbf(dbf_path: Path):
|
||||||
|
"""Return (field_names, list_of_dicts) from a dBASE III .dbf file."""
|
||||||
|
for enc in ("utf-8", "latin-1", "cp1252"):
|
||||||
|
try:
|
||||||
|
with open(dbf_path, "rb") as f:
|
||||||
|
f.read(4) # version + date
|
||||||
|
nrec = struct.unpack("<I", f.read(4))[0]
|
||||||
|
hdrsz = struct.unpack("<H", f.read(2))[0]
|
||||||
|
recsz = struct.unpack("<H", f.read(2))[0]
|
||||||
|
f.read(20) # reserved
|
||||||
|
|
||||||
|
fields = []
|
||||||
|
while True:
|
||||||
|
raw = f.read(32)
|
||||||
|
if not raw or raw[0] == 0x0D:
|
||||||
|
break
|
||||||
|
name = raw[:11].rstrip(b"\x00").decode("ascii", errors="replace")
|
||||||
|
flen = raw[16]
|
||||||
|
fields.append((name, flen))
|
||||||
|
|
||||||
|
f.seek(hdrsz)
|
||||||
|
rows = []
|
||||||
|
for _ in range(nrec):
|
||||||
|
rec = f.read(recsz)
|
||||||
|
if not rec:
|
||||||
|
break
|
||||||
|
if rec[0] == 0x2A: # deleted
|
||||||
|
continue
|
||||||
|
row = {}
|
||||||
|
off = 1
|
||||||
|
for fname, flen in fields:
|
||||||
|
raw_val = rec[off:off+flen]
|
||||||
|
try:
|
||||||
|
val = raw_val.decode(enc).strip()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
val = raw_val.decode("latin-1").strip()
|
||||||
|
if val:
|
||||||
|
row[fname.lower()] = val
|
||||||
|
off += flen
|
||||||
|
rows.append(row)
|
||||||
|
return [f[0].lower() for f in fields], rows
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
continue
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
# ── minimal SHP reader (stdlib only) ─────────────────────────────────────────
|
||||||
|
def _read_shp(shp_path: Path):
|
||||||
|
"""Yield (shape_type, points, parts) from a .shp file.
|
||||||
|
|
||||||
|
points: list of (x, y) tuples
|
||||||
|
parts: list of part start indices (for Polyline/Polygon)
|
||||||
|
"""
|
||||||
|
with open(shp_path, "rb") as f:
|
||||||
|
f.read(100) # skip file header
|
||||||
|
while True:
|
||||||
|
hdr = f.read(8)
|
||||||
|
if len(hdr) < 8:
|
||||||
|
break
|
||||||
|
content_len = struct.unpack(">ii", hdr)[1] * 2
|
||||||
|
content = f.read(content_len)
|
||||||
|
if len(content) < 4:
|
||||||
|
break
|
||||||
|
stype = struct.unpack_from("<i", content, 0)[0]
|
||||||
|
|
||||||
|
if stype == 0:
|
||||||
|
yield stype, [], []
|
||||||
|
continue
|
||||||
|
|
||||||
|
if stype in _PT:
|
||||||
|
x, y = struct.unpack_from("<dd", content, 4)
|
||||||
|
yield stype, [(x, y)], []
|
||||||
|
|
||||||
|
elif stype in _MPT:
|
||||||
|
npts = struct.unpack_from("<i", content, 36)[0]
|
||||||
|
pts = [struct.unpack_from("<dd", content, 40 + i*16)
|
||||||
|
for i in range(npts)]
|
||||||
|
yield stype, pts, []
|
||||||
|
|
||||||
|
elif stype in _LINE | _POLY:
|
||||||
|
nparts = struct.unpack_from("<i", content, 36)[0]
|
||||||
|
npts = struct.unpack_from("<i", content, 40)[0]
|
||||||
|
parts = [struct.unpack_from("<i", content, 44 + i*4)[0]
|
||||||
|
for i in range(nparts)]
|
||||||
|
off = 44 + nparts * 4
|
||||||
|
pts = [struct.unpack_from("<dd", content, off + i*16)
|
||||||
|
for i in range(npts)]
|
||||||
|
yield stype, pts, parts
|
||||||
|
|
||||||
|
# ── signed area (Shoelace) — ESRI outer rings are CW → negative ──────────────
|
||||||
|
def _signed_area(pts):
|
||||||
|
n = len(pts)
|
||||||
|
return sum(
|
||||||
|
pts[i][0] * pts[(i+1)%n][1] - pts[(i+1)%n][0] * pts[i][1]
|
||||||
|
for i in range(n)
|
||||||
|
) / 2.0
|
||||||
|
|
||||||
|
# ── Geometry simplification ───────────────────────────────────────────────────
|
||||||
|
# ISO 8211 leader field = 5 digits → DR max 99,999 bytes.
|
||||||
|
# SG2D: each coord pair = 8 bytes (2 × int32). Max ~12,000 pairs per record.
|
||||||
|
# For safety, keep max 8,000 vertices per ring/line with RDP simplification.
|
||||||
|
|
||||||
|
_MAX_VERTICES = 8000 # hard limit per ring or line feature
|
||||||
|
_RDP_TOL_DEG = 1e-5 # ~1 m at equator — safe for 1:50 000 charts
|
||||||
|
|
||||||
|
def _perp_dist_sq(p, a, b):
|
||||||
|
"""Squared perpendicular distance from p to segment a→b (2-D, degrees)."""
|
||||||
|
ax, ay = a; bx, by = b; px, py = p
|
||||||
|
dx, dy = bx - ax, by - ay
|
||||||
|
if dx == 0 and dy == 0:
|
||||||
|
return (px - ax) ** 2 + (py - ay) ** 2
|
||||||
|
t = ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)
|
||||||
|
t = max(0.0, min(1.0, t))
|
||||||
|
return (px - ax - t * dx) ** 2 + (py - ay - t * dy) ** 2
|
||||||
|
|
||||||
|
def _rdp(pts, tol_sq):
|
||||||
|
"""Ramer-Douglas-Peucker (iterative stack version, no recursion limit)."""
|
||||||
|
if len(pts) <= 2:
|
||||||
|
return list(pts)
|
||||||
|
keep = [False] * len(pts)
|
||||||
|
keep[0] = keep[-1] = True
|
||||||
|
stack = [(0, len(pts) - 1)]
|
||||||
|
while stack:
|
||||||
|
s, e = stack.pop()
|
||||||
|
if e - s < 2:
|
||||||
|
continue
|
||||||
|
max_d, max_i = 0.0, s
|
||||||
|
for i in range(s + 1, e):
|
||||||
|
d = _perp_dist_sq(pts[i], pts[s], pts[e])
|
||||||
|
if d > max_d:
|
||||||
|
max_d, max_i = d, i
|
||||||
|
if max_d > tol_sq:
|
||||||
|
keep[max_i] = True
|
||||||
|
stack.append((s, max_i))
|
||||||
|
stack.append((max_i, e))
|
||||||
|
return [p for p, k in zip(pts, keep) if k]
|
||||||
|
|
||||||
|
def _simplify_coords(coords, max_verts=_MAX_VERTICES, tol_deg=_RDP_TOL_DEG):
|
||||||
|
"""
|
||||||
|
Simplify a coordinate list so it fits within the ISO 8211 record limit.
|
||||||
|
1) Apply RDP at tol_deg.
|
||||||
|
2) If still > max_verts, apply RDP at escalating tolerance until fits.
|
||||||
|
"""
|
||||||
|
if len(coords) <= max_verts:
|
||||||
|
return coords
|
||||||
|
tol_sq = tol_deg ** 2
|
||||||
|
result = _rdp(coords, tol_sq)
|
||||||
|
# Escalate tolerance if still too many
|
||||||
|
factor = 10.0
|
||||||
|
while len(result) > max_verts and factor < 1e6:
|
||||||
|
result = _rdp(coords, (tol_deg * factor) ** 2)
|
||||||
|
factor *= 10.0
|
||||||
|
# Last resort: uniform decimation
|
||||||
|
if len(result) > max_verts:
|
||||||
|
step = math.ceil(len(result) / max_verts)
|
||||||
|
result = result[::step]
|
||||||
|
if result[-1] != coords[-1]:
|
||||||
|
result.append(coords[-1]) # keep last point
|
||||||
|
n_in, n_out = len(coords), len(result)
|
||||||
|
if n_out < n_in:
|
||||||
|
print(f" [simplify] {n_in} -> {n_out} vertices (tol ~{tol_deg*factor:.6f}°)")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# ── S-57 object catalog ───────────────────────────────────────────────────────
|
||||||
|
def load_s57_objects():
|
||||||
|
path = SCRIPT_DIR / "s57_objects.json"
|
||||||
|
if path.exists():
|
||||||
|
with open(path, encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return {k: v for k, v in data.items() if not k.startswith("_")}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
S57_OBJECTS = load_s57_objects()
|
||||||
|
|
||||||
|
# ── config ────────────────────────────────────────────────────────────────────
|
||||||
|
def load_config(path=None):
|
||||||
|
cfg_path = Path(path) if path else SCRIPT_DIR / "cell_config.json"
|
||||||
|
if not cfg_path.exists():
|
||||||
|
print(f"[WARN] Config not found: {cfg_path}. Using defaults.")
|
||||||
|
return _default_config()
|
||||||
|
with open(cfg_path, encoding="utf-8") as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
cfg = {k: v for k, v in cfg.items() if not k.startswith("_")}
|
||||||
|
cfg.setdefault("layer_mappings", {})
|
||||||
|
cfg.setdefault("attribute_mappings", {})
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
def _default_config():
|
||||||
|
return {
|
||||||
|
"cell_name": "XX1XX01M", "cell_edition": 1, "update_number": 0,
|
||||||
|
"issue_date": datetime.now().strftime("%Y%m%d"),
|
||||||
|
"producer_code": "XX", "producer_name": "Custom",
|
||||||
|
"data_set_name": "ENC Chart", "scale": 50000, "comment": "",
|
||||||
|
"horizontal_datum": "WGS84", "vertical_datum": "MLLW",
|
||||||
|
"sounding_datum": "MLLW", "compilation_scale": 50000,
|
||||||
|
"layer_mappings": {}, "attribute_mappings": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── QGIS project parser ───────────────────────────────────────────────────────
|
||||||
|
class QGISProject:
|
||||||
|
def __init__(self, project_path):
|
||||||
|
self.project_path = Path(project_path)
|
||||||
|
self.base_dir = self.project_path.parent
|
||||||
|
self.layers = []
|
||||||
|
self._parse()
|
||||||
|
|
||||||
|
def _parse(self):
|
||||||
|
if self.project_path.suffix.lower() == ".qgz":
|
||||||
|
self._parse_qgz()
|
||||||
|
else:
|
||||||
|
self._parse_qgs(self.project_path)
|
||||||
|
|
||||||
|
def _parse_qgz(self):
|
||||||
|
with zipfile.ZipFile(self.project_path, "r") as z:
|
||||||
|
qgs_files = [f for f in z.namelist() if f.endswith(".qgs")]
|
||||||
|
if not qgs_files:
|
||||||
|
raise ValueError("No .qgs file inside .qgz")
|
||||||
|
with z.open(qgs_files[0]) as f:
|
||||||
|
content = f.read().decode("utf-8")
|
||||||
|
tmp = self.project_path.parent / "_tmp_project.qgs"
|
||||||
|
tmp.write_text(content, encoding="utf-8")
|
||||||
|
self._parse_qgs(tmp)
|
||||||
|
tmp.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
def _parse_qgs(self, qgs_path):
|
||||||
|
tree = ET.parse(qgs_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
for ltl in root.iter("layer-tree-layer"):
|
||||||
|
lid = ltl.get("id", "")
|
||||||
|
name = ltl.get("name", "unnamed")
|
||||||
|
vis = ltl.get("checked", "Qt::Checked") != "Qt::Unchecked"
|
||||||
|
ml = self._find_maplayer(root, lid)
|
||||||
|
if ml is None or ml.get("type", "") != "vector":
|
||||||
|
continue
|
||||||
|
ds = ml.find("datasource")
|
||||||
|
if ds is None:
|
||||||
|
continue
|
||||||
|
ds_text = (ds.text or "").strip()
|
||||||
|
crs_el = ml.find(".//srs/spatialrefsys/authid")
|
||||||
|
crs = crs_el.text if crs_el is not None else "EPSG:4326"
|
||||||
|
|
||||||
|
# ── Capa SHP ──────────────────────────────────────────────────
|
||||||
|
shp = self._resolve_path(ds_text.split("|")[0].strip())
|
||||||
|
if shp is not None and str(shp).lower().endswith(".shp"):
|
||||||
|
self.layers.append({
|
||||||
|
"id": lid, "name": name, "path": shp,
|
||||||
|
"crs": crs, "visible": vis, "layer_type": "shp",
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ── Capa CSV / texto delimitado ───────────────────────────────
|
||||||
|
# QGIS guarda: file:///ruta/al/archivo.csv?delimiter=,&xField=lon&yField=lat&...
|
||||||
|
csv_path = self._resolve_csv_path(ds_text)
|
||||||
|
if csv_path is not None:
|
||||||
|
# Leer xField / yField del URI
|
||||||
|
x_field = "lon"
|
||||||
|
y_field = "lat"
|
||||||
|
for part in ds_text.split("?")[-1].split("&"):
|
||||||
|
if part.startswith("xField="):
|
||||||
|
x_field = part.split("=", 1)[1]
|
||||||
|
elif part.startswith("yField="):
|
||||||
|
y_field = part.split("=", 1)[1]
|
||||||
|
self.layers.append({
|
||||||
|
"id": lid, "name": name, "path": csv_path,
|
||||||
|
"crs": crs, "visible": vis, "layer_type": "csv",
|
||||||
|
"x_field": x_field, "y_field": y_field,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _resolve_csv_path(self, ds_text):
|
||||||
|
"""Extrae y resuelve la ruta de una datasource CSV de QGIS."""
|
||||||
|
import urllib.parse
|
||||||
|
# Formatos: file:///C:/ruta/file.csv?... o /ruta/file.csv
|
||||||
|
raw = ds_text.split("?")[0]
|
||||||
|
if raw.startswith("file:///"):
|
||||||
|
raw = raw[8:] # quitar file:///
|
||||||
|
elif raw.startswith("file://"):
|
||||||
|
raw = raw[7:]
|
||||||
|
raw = urllib.parse.unquote(raw)
|
||||||
|
p = Path(raw)
|
||||||
|
if p.exists() and p.suffix.lower() == ".csv":
|
||||||
|
return p
|
||||||
|
# Intentar resolver relativo al proyecto
|
||||||
|
rel = self.base_dir / raw
|
||||||
|
if rel.exists() and rel.suffix.lower() == ".csv":
|
||||||
|
return rel.resolve()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _find_maplayer(self, root, lid):
|
||||||
|
for ml in root.iter("maplayer"):
|
||||||
|
el = ml.find("id")
|
||||||
|
if el is not None and el.text == lid:
|
||||||
|
return ml
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _resolve_path(self, path_str):
|
||||||
|
p = Path(path_str)
|
||||||
|
if p.is_absolute() and p.exists():
|
||||||
|
return p
|
||||||
|
rel = self.base_dir / path_str
|
||||||
|
if rel.exists():
|
||||||
|
return rel.resolve()
|
||||||
|
for c in self.base_dir.rglob(p.name):
|
||||||
|
return c
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ── layer -> S-57 class resolver ─────────────────────────────────────────────
|
||||||
|
def resolve_s57_class(layer_name, layer_mappings):
|
||||||
|
nl = layer_name.lower().strip()
|
||||||
|
if nl in layer_mappings:
|
||||||
|
return layer_mappings[nl].upper()
|
||||||
|
for key, val in layer_mappings.items():
|
||||||
|
if key in nl or nl in key:
|
||||||
|
return val.upper()
|
||||||
|
nu = layer_name.upper().strip()
|
||||||
|
if nu in S57_OBJECTS:
|
||||||
|
return nu
|
||||||
|
for acro, info in S57_OBJECTS.items():
|
||||||
|
if any(w in nl for w in info["desc"].lower().split() if len(w) > 3):
|
||||||
|
return acro
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ── SHP feature iterator (stdlib only) ───────────────────────────────────────
|
||||||
|
def iter_shapes(shp_path: Path, crs_str: str, attr_map: dict):
|
||||||
|
"""Yield (geom_type, coords_wgs84, mapped_attrs) using only stdlib."""
|
||||||
|
|
||||||
|
# Reprojection
|
||||||
|
transformer = None
|
||||||
|
if PYPROJ_AVAILABLE and crs_str:
|
||||||
|
try:
|
||||||
|
src = CRS.from_user_input(crs_str)
|
||||||
|
wgs84 = CRS.from_epsg(4326)
|
||||||
|
if not src.equals(wgs84):
|
||||||
|
transformer = Transformer.from_crs(src, wgs84, always_xy=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" [WARN] Reprojection unavailable ({e}); assuming WGS84")
|
||||||
|
|
||||||
|
def tr(x, y):
|
||||||
|
return transformer.transform(x, y) if transformer else (x, y)
|
||||||
|
|
||||||
|
def tr_pts(pts):
|
||||||
|
return [tr(p[0], p[1]) for p in pts]
|
||||||
|
|
||||||
|
# Read attributes from .dbf
|
||||||
|
dbf_path = shp_path.with_suffix(".dbf")
|
||||||
|
_, dbf_rows = _read_dbf(dbf_path) if dbf_path.exists() else ([], [])
|
||||||
|
|
||||||
|
# Iterate geometry
|
||||||
|
for idx, (stype, pts, parts) in enumerate(_read_shp(shp_path)):
|
||||||
|
if stype == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw = dbf_rows[idx] if idx < len(dbf_rows) else {}
|
||||||
|
mapped = []
|
||||||
|
for shp_col, s57_acro in attr_map.items():
|
||||||
|
if shp_col in raw:
|
||||||
|
attl = ATTR_CODE.get(s57_acro.upper())
|
||||||
|
if attl is not None:
|
||||||
|
mapped.append((attl, raw[shp_col]))
|
||||||
|
|
||||||
|
# ── Auto-detect: SHP column name == S-57 attribute acronym ──────────
|
||||||
|
already_attls = {a for a, _ in mapped}
|
||||||
|
raw_upper = {k.upper(): v for k, v in raw.items()}
|
||||||
|
for s57_acro, attl in ATTR_CODE.items():
|
||||||
|
if attl in already_attls:
|
||||||
|
continue
|
||||||
|
if s57_acro in raw_upper:
|
||||||
|
val = raw_upper[s57_acro]
|
||||||
|
val_str = str(val).strip() if val is not None else ""
|
||||||
|
# Skip DBF nulls: empty, all-asterisks, or all-zeros (numeric null)
|
||||||
|
if val_str and not all(c in "*0 " for c in val_str):
|
||||||
|
mapped.append((attl, val_str))
|
||||||
|
already_attls.add(attl)
|
||||||
|
|
||||||
|
# ── COLOUR_TXT override: text name → correct S-57 colour code ───────
|
||||||
|
# SHP/QGIS may store COLOUR as 0-indexed or wrong-offset numeric;
|
||||||
|
# COLOUR_TXT (the human name) is the ground truth.
|
||||||
|
_CNAME = {
|
||||||
|
"white": "1", "black": "2", "red": "3", "green": "4",
|
||||||
|
"blue": "5", "yellow": "6", "grey": "7", "gray": "7",
|
||||||
|
"brown": "8", "amber": "9", "orange": "11",
|
||||||
|
"magenta": "12", "violet": "13",
|
||||||
|
}
|
||||||
|
attl_colour = ATTR_CODE.get("COLOUR")
|
||||||
|
if attl_colour is not None and "COLOUR_TXT" in raw_upper:
|
||||||
|
cname = raw_upper["COLOUR_TXT"].lower().strip()
|
||||||
|
s57c = _CNAME.get(cname)
|
||||||
|
if s57c:
|
||||||
|
mapped = [(a, v) for a, v in mapped if a != attl_colour]
|
||||||
|
mapped.append((attl_colour, s57c))
|
||||||
|
already_attls.discard(attl_colour)
|
||||||
|
already_attls.add(attl_colour)
|
||||||
|
|
||||||
|
# ── Infer CATLAM from colour when absent (IALA B: green=port, red=stbd)
|
||||||
|
attl_catlam = ATTR_CODE.get("CATLAM")
|
||||||
|
if attl_catlam is not None and attl_catlam not in already_attls and attl_colour is not None:
|
||||||
|
colour_val = next((v for a, v in mapped if a == attl_colour), None)
|
||||||
|
if colour_val == "4": # green
|
||||||
|
mapped.append((attl_catlam, "1")) # port-hand
|
||||||
|
elif colour_val == "3": # red
|
||||||
|
mapped.append((attl_catlam, "2")) # starboard-hand
|
||||||
|
|
||||||
|
if stype in _PT and pts:
|
||||||
|
yield "point", [tr(*pts[0])], mapped
|
||||||
|
|
||||||
|
elif stype in _MPT:
|
||||||
|
for pt in pts:
|
||||||
|
yield "point", [tr(*pt)], mapped
|
||||||
|
|
||||||
|
elif stype in _LINE:
|
||||||
|
bounds = list(parts) + [len(pts)]
|
||||||
|
for i in range(len(bounds) - 1):
|
||||||
|
seg = pts[bounds[i]:bounds[i+1]]
|
||||||
|
if len(seg) >= 2:
|
||||||
|
yield "line", tr_pts(seg), mapped
|
||||||
|
|
||||||
|
elif stype in _POLY:
|
||||||
|
bounds = list(parts) + [len(pts)]
|
||||||
|
for i in range(len(bounds) - 1):
|
||||||
|
ring = pts[bounds[i]:bounds[i+1]]
|
||||||
|
if len(ring) < 3:
|
||||||
|
continue
|
||||||
|
# ESRI outer rings are CW (negative shoelace area); skip CCW holes
|
||||||
|
if _signed_area([(p[0], p[1]) for p in ring]) > 0:
|
||||||
|
continue
|
||||||
|
yield "polygon", tr_pts(ring), mapped
|
||||||
|
|
||||||
|
# ── S-57 cell writer ──────────────────────────────────────────────────────────
|
||||||
|
class S57CellWriter:
|
||||||
|
def __init__(self, output_path, config):
|
||||||
|
self.output_path = Path(output_path)
|
||||||
|
self.cfg = config
|
||||||
|
self._cell = None
|
||||||
|
self._bbox = None
|
||||||
|
self._feature_counter = {}
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
cfg = self.cfg
|
||||||
|
issue = cfg.get("issue_date") or datetime.now().strftime("%Y%m%d")
|
||||||
|
self._cell = S57Cell(
|
||||||
|
dsnm = cfg.get("cell_name", "CHART01") + ".000",
|
||||||
|
edition = int(cfg.get("cell_edition", 1)),
|
||||||
|
intu = 5,
|
||||||
|
scale = int(cfg.get("scale", 50000)),
|
||||||
|
agen = 999,
|
||||||
|
comt = cfg.get("comment", "Generated by QGISS57Converter"),
|
||||||
|
issue_date = issue,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _update_bbox(self, coords):
|
||||||
|
for x, y in coords:
|
||||||
|
if self._bbox is None:
|
||||||
|
self._bbox = [x, y, x, y]
|
||||||
|
else:
|
||||||
|
if x < self._bbox[0]: self._bbox[0] = x
|
||||||
|
if y < self._bbox[1]: self._bbox[1] = y
|
||||||
|
if x > self._bbox[2]: self._bbox[2] = x
|
||||||
|
if y > self._bbox[3]: self._bbox[3] = y
|
||||||
|
|
||||||
|
def add_features_from_csv(self, csv_path: Path, s57_class: str,
|
||||||
|
attr_map: dict, x_field: str = "lon",
|
||||||
|
y_field: str = "lat") -> int:
|
||||||
|
"""Lee una capa CSV de QGIS (puntos) y la convierte a features S-57.
|
||||||
|
|
||||||
|
Estándar IHO S-57: usa los nombres de atributo S-57 como cabeceras de
|
||||||
|
columna (LITCHR, COLOUR, VALNMR, BOYSHP, CATLAM, …). El converter los
|
||||||
|
recoge automáticamente sin necesidad de ningún mapeo adicional.
|
||||||
|
|
||||||
|
Columna especial feat_type:
|
||||||
|
Si una fila tiene la columna 'feat_type' con un acrónimo S-57
|
||||||
|
válido (BCNLAT, BOYLAT, LIGHTS, …), esa fila usa ese objeto en
|
||||||
|
lugar del s57_class del nivel de capa. Esto permite mezclar tipos
|
||||||
|
en un solo CSV (p.ej. la carta de Barranquilla que incluye BCNLAT,
|
||||||
|
BOYLAT, LIGHTS y BOYCAR en el mismo archivo).
|
||||||
|
|
||||||
|
Luces compañeras (companion LIGHTS):
|
||||||
|
Cuando una estructura física (BCNLAT, BOYLAT, BCNCAR, BOYCAR,
|
||||||
|
BOYISD, BOYSAW, BOYSPP, LNDMRK) tiene LITCHR definido, el
|
||||||
|
converter emite además un objeto LIGHTS co-ubicado con solo los
|
||||||
|
atributos de luz. Así, el ECDIS puede mostrar tanto el símbolo 3D
|
||||||
|
de la estructura como la descripción de luz en el tooltip, igual
|
||||||
|
que en las cartas NOAA.
|
||||||
|
|
||||||
|
Columnas privadas (prefijo _):
|
||||||
|
Las columnas que empiezan por _ (p.ej. _source, _dimar_char_raw)
|
||||||
|
se ignoran y nunca se escriben al S-57.
|
||||||
|
"""
|
||||||
|
import csv as _csv
|
||||||
|
|
||||||
|
# LITCHR_TXT → código S-57 oficial (para CSVs con texto legible)
|
||||||
|
_LITCHR_TXT = {
|
||||||
|
"f": "1", "fl": "2", "lfl": "3", "q": "4",
|
||||||
|
"vq": "5", "uq": "6", "iso": "7", "oc": "8",
|
||||||
|
"iq": "9", "ivq": "10", "iuq": "11", "mo": "12",
|
||||||
|
"ffl": "13", "fl+lfl":"14", "oc+fl": "15",
|
||||||
|
"al.oc": "25", "al.lfl":"26", "al.fl": "27", "al.grp":"28",
|
||||||
|
}
|
||||||
|
# COLOUR_TXT → código S-57 oficial
|
||||||
|
_COLOUR_TXT = {
|
||||||
|
"white":"1", "black":"2", "red":"3", "green":"4",
|
||||||
|
"blue":"5", "yellow":"6", "grey":"7", "gray":"7",
|
||||||
|
"brown":"8", "amber":"9", "violet":"10", "orange":"11", "magenta":"12",
|
||||||
|
}
|
||||||
|
# S-57 classes that represent physical structures and may carry light attrs
|
||||||
|
_STRUCT_CLASSES = {
|
||||||
|
"BCNLAT","BCNCAR","BCNISD","BCNSAW","BCNSPP","BCNWTW",
|
||||||
|
"BOYLAT","BOYCAR","BOYISD","BOYSAW","BOYSPP",
|
||||||
|
"LNDMRK","LITFLT","LITVES",
|
||||||
|
}
|
||||||
|
# Attribute codes for companion LIGHTS
|
||||||
|
_LIGHT_ATTL = {ATTR_CODE[a] for a in
|
||||||
|
("LITCHR","SIGGRP","SIGPER","COLOUR","VALNMR","HEIGHT",
|
||||||
|
"SECTR1","SECTR2","ORIENT","MLTYLT","CATLIT","OBJNAM")
|
||||||
|
if a in ATTR_CODE}
|
||||||
|
|
||||||
|
if self._cell is None:
|
||||||
|
raise RuntimeError("call open() first")
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
with open(csv_path, newline="", encoding="utf-8-sig") as f:
|
||||||
|
reader = _csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
try:
|
||||||
|
lon = float(row[x_field])
|
||||||
|
lat = float(row[y_field])
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Determine S-57 object class for this row
|
||||||
|
row_class = (row.get("feat_type") or "").strip().upper()
|
||||||
|
if not row_class:
|
||||||
|
row_class = s57_class.upper()
|
||||||
|
objl = OBJL_BY_ACRONYM.get(row_class)
|
||||||
|
if objl is None:
|
||||||
|
print(f" [WARN] Unknown S-57 class '{row_class}' in row, skipping")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Build attribute list using S-57 column name auto-detection
|
||||||
|
already_attls: set[int] = set()
|
||||||
|
mapped: list[tuple[int, str]] = []
|
||||||
|
|
||||||
|
raw_upper = {k.upper(): v for k, v in row.items()
|
||||||
|
if not k.startswith("_") and k not in (x_field, y_field, "feat_type")}
|
||||||
|
|
||||||
|
for s57_acro, attl in ATTR_CODE.items():
|
||||||
|
if attl in already_attls:
|
||||||
|
continue
|
||||||
|
if s57_acro in raw_upper:
|
||||||
|
val = raw_upper[s57_acro].strip()
|
||||||
|
if val and not all(c in "*0 " for c in val):
|
||||||
|
mapped.append((attl, val))
|
||||||
|
already_attls.add(attl)
|
||||||
|
|
||||||
|
# Also apply attribute_mappings from config
|
||||||
|
for shp_col, s57_acro in attr_map.items():
|
||||||
|
attl = ATTR_CODE.get(s57_acro.upper())
|
||||||
|
if attl and attl not in already_attls and shp_col.upper() in raw_upper:
|
||||||
|
val = raw_upper[shp_col.upper()].strip()
|
||||||
|
if val:
|
||||||
|
mapped.append((attl, val))
|
||||||
|
already_attls.add(attl)
|
||||||
|
|
||||||
|
# LITCHR_TXT override — parse readable chars like "Q(4)G" → 4
|
||||||
|
attl_litchr = ATTR_CODE.get("LITCHR")
|
||||||
|
if attl_litchr and "LITCHR_TXT" in raw_upper and attl_litchr not in already_attls:
|
||||||
|
txt = raw_upper["LITCHR_TXT"].lower().split("(")[0].strip()
|
||||||
|
code = _LITCHR_TXT.get(txt)
|
||||||
|
if code:
|
||||||
|
mapped.append((attl_litchr, code))
|
||||||
|
already_attls.add(attl_litchr)
|
||||||
|
elif attl_litchr and "LITCHR_TXT" in raw_upper and attl_litchr in already_attls:
|
||||||
|
# Correct an already-set LITCHR if TXT provides a better value
|
||||||
|
txt = raw_upper["LITCHR_TXT"].lower().split("(")[0].strip()
|
||||||
|
code = _LITCHR_TXT.get(txt)
|
||||||
|
if code:
|
||||||
|
mapped = [(a, v) for a, v in mapped if a != attl_litchr]
|
||||||
|
mapped.append((attl_litchr, code))
|
||||||
|
|
||||||
|
# COLOUR_TXT override
|
||||||
|
attl_colour = ATTR_CODE.get("COLOUR")
|
||||||
|
if attl_colour and "COLOUR_TXT" in raw_upper:
|
||||||
|
cname = raw_upper["COLOUR_TXT"].lower().strip()
|
||||||
|
s57c = _COLOUR_TXT.get(cname)
|
||||||
|
if s57c:
|
||||||
|
mapped = [(a, v) for a, v in mapped if a != attl_colour]
|
||||||
|
mapped.append((attl_colour, s57c))
|
||||||
|
|
||||||
|
# Write the main object
|
||||||
|
self._cell.add_point_feature(objl=objl, lon=lon, lat=lat,
|
||||||
|
attrs=mapped if mapped else None)
|
||||||
|
self._update_bbox([(lon, lat)])
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
# ── Companion LIGHTS ─────────────────────────────────────────
|
||||||
|
# When a physical structure carries light data, emit a co-located
|
||||||
|
# LIGHTS object so ECDIS proximity-merge picks up the light desc.
|
||||||
|
if row_class in _STRUCT_CLASSES and attl_litchr in already_attls:
|
||||||
|
light_attrs = [(a, v) for a, v in mapped if a in _LIGHT_ATTL]
|
||||||
|
if light_attrs:
|
||||||
|
self._cell.add_point_feature(
|
||||||
|
objl=OBJL_LIGHTS, lon=lon, lat=lat,
|
||||||
|
attrs=light_attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._feature_counter[s57_class] = (
|
||||||
|
self._feature_counter.get(s57_class, 0) + count
|
||||||
|
)
|
||||||
|
return count
|
||||||
|
|
||||||
|
def add_features_from_shp(self, shp_path: Path, crs_str: str,
|
||||||
|
s57_class: str, attr_map: dict) -> int:
|
||||||
|
if self._cell is None:
|
||||||
|
raise RuntimeError("call open() first")
|
||||||
|
objl = OBJL_BY_ACRONYM.get(s57_class.upper())
|
||||||
|
if objl is None:
|
||||||
|
print(f" [WARN] Unknown S-57 class '{s57_class}', skipping")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
try:
|
||||||
|
for geom_type, coords, attrs in iter_shapes(shp_path, crs_str, attr_map):
|
||||||
|
if not coords:
|
||||||
|
continue
|
||||||
|
self._update_bbox(coords)
|
||||||
|
a = attrs or None
|
||||||
|
if geom_type == "point":
|
||||||
|
self._cell.add_point_feature(objl=objl, lon=coords[0][0],
|
||||||
|
lat=coords[0][1], attrs=a)
|
||||||
|
count += 1
|
||||||
|
elif geom_type == "line" and len(coords) >= 2:
|
||||||
|
coords = _simplify_coords(list(coords))
|
||||||
|
if len(coords) >= 2:
|
||||||
|
self._cell.add_line_feature(objl=objl, coords=coords, attrs=a)
|
||||||
|
count += 1
|
||||||
|
elif geom_type == "polygon" and len(coords) >= 3:
|
||||||
|
coords = _simplify_coords(list(coords))
|
||||||
|
if len(coords) >= 3:
|
||||||
|
self._cell.add_area_feature(objl=objl, ring=coords, attrs=a)
|
||||||
|
count += 1
|
||||||
|
except AssertionError as e:
|
||||||
|
print(f" [ERR] ISO 8211 record too large in {shp_path.name}: {e}")
|
||||||
|
print(f" [ERR] Reduce geometry complexity or increase _MAX_VERTICES in converter.py")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" [ERR] {shp_path.name}: {e}")
|
||||||
|
|
||||||
|
self._feature_counter[s57_class] = (
|
||||||
|
self._feature_counter.get(s57_class, 0) + count
|
||||||
|
)
|
||||||
|
return count
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._cell is None:
|
||||||
|
return
|
||||||
|
if self._bbox:
|
||||||
|
w, s, e, n = self._bbox
|
||||||
|
buf = 0.001
|
||||||
|
ring = [(w-buf, s-buf), (e+buf, s-buf), (e+buf, n+buf),
|
||||||
|
(w-buf, n+buf), (w-buf, s-buf)]
|
||||||
|
self._cell.add_area_feature(
|
||||||
|
objl = OBJL_BY_ACRONYM["M_COVR"],
|
||||||
|
ring = ring,
|
||||||
|
attrs = [(ATTL_CATCOV, "1")],
|
||||||
|
)
|
||||||
|
print(" M_COVR 1 Coverage (auto-generated)")
|
||||||
|
self._cell.write(self.output_path)
|
||||||
|
self._cell = None
|
||||||
|
|
||||||
|
def summary(self):
|
||||||
|
print("\nFeatures written:")
|
||||||
|
total = 0
|
||||||
|
for cls, cnt in self._feature_counter.items():
|
||||||
|
desc = S57_OBJECTS.get(cls, {}).get("desc", "")
|
||||||
|
# Replace non-cp1252 chars (e.g. →) to avoid UnicodeEncodeError on Windows console
|
||||||
|
desc = desc.encode("cp1252", errors="replace").decode("cp1252")
|
||||||
|
print(f" {cls:<12} {cnt:>5} {desc}")
|
||||||
|
total += cnt
|
||||||
|
print(f" {'TOTAL':<12} {total:>5}")
|
||||||
|
|
||||||
|
|
||||||
|
S57Writer = S57CellWriter # backward-compat alias
|
||||||
|
|
||||||
|
|
||||||
|
# ── main converter ────────────────────────────────────────────────────────────
|
||||||
|
def convert(project_path, output_path, config_path, list_only, force, verbose,
|
||||||
|
extra_csv_dir=None):
|
||||||
|
print(f"\nQGIS -> S-57 Converter")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
print(f"Project : {project_path}")
|
||||||
|
|
||||||
|
cfg = load_config(config_path)
|
||||||
|
attr_map = {k.lower(): v.upper() for k, v in cfg.get("attribute_mappings", {}).items()}
|
||||||
|
layer_map = {k.lower(): v.upper() for k, v in cfg.get("layer_mappings", {}).items()}
|
||||||
|
|
||||||
|
print(f"\nParsing QGIS project...")
|
||||||
|
try:
|
||||||
|
project = QGISProject(project_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Could not parse project: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# ── Inyectar CSVs de directorio extra ──────────────────────────────────
|
||||||
|
extra_layers = []
|
||||||
|
if extra_csv_dir:
|
||||||
|
csv_dir = Path(extra_csv_dir)
|
||||||
|
if csv_dir.is_dir():
|
||||||
|
for csv_file in sorted(csv_dir.glob("*.csv")):
|
||||||
|
# Evitar duplicados: si la capa ya está en el proyecto, ignorar
|
||||||
|
already = any(
|
||||||
|
Path(l["path"]).resolve() == csv_file.resolve()
|
||||||
|
for l in project.layers if l.get("layer_type") == "csv"
|
||||||
|
)
|
||||||
|
if not already:
|
||||||
|
extra_layers.append({
|
||||||
|
"id": "extra_" + csv_file.stem,
|
||||||
|
"name": csv_file.stem,
|
||||||
|
"path": csv_file,
|
||||||
|
"crs": "EPSG:4326",
|
||||||
|
"visible": True,
|
||||||
|
"layer_type": "csv",
|
||||||
|
"x_field": "lon",
|
||||||
|
"y_field": "lat",
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print(f"[WARN] extra_csv_dir not found: {csv_dir}")
|
||||||
|
|
||||||
|
if not project.layers and not extra_layers:
|
||||||
|
print("[ERROR] No layers found in project.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
all_layers = project.layers + extra_layers
|
||||||
|
shp_count = sum(1 for l in all_layers if l.get("layer_type","shp") == "shp")
|
||||||
|
csv_count = sum(1 for l in all_layers if l.get("layer_type") == "csv")
|
||||||
|
print(f"Found {shp_count} SHP + {csv_count} CSV layer(s):\n")
|
||||||
|
layer_assignments = []
|
||||||
|
for layer in all_layers:
|
||||||
|
s57_class = resolve_s57_class(layer["name"], layer_map)
|
||||||
|
status = "+" if layer["visible"] else "o"
|
||||||
|
assigned = s57_class or "?? (unmapped)"
|
||||||
|
src_tag = " [extra]" if layer["id"].startswith("extra_") else ""
|
||||||
|
print(f" {status} {layer['name']:<30} -> {assigned}{src_tag}")
|
||||||
|
layer_assignments.append((layer, s57_class))
|
||||||
|
|
||||||
|
if list_only:
|
||||||
|
print("\n[INFO] --list mode. No conversion performed.")
|
||||||
|
return
|
||||||
|
|
||||||
|
unmapped = [(l, c) for l, c in layer_assignments if c is None]
|
||||||
|
if unmapped and not force:
|
||||||
|
print(f"\n[WARN] {len(unmapped)} unmapped layer(s).")
|
||||||
|
ans = input("Continue anyway? [y/N]: ")
|
||||||
|
if ans.lower() != "y":
|
||||||
|
print("Aborted.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if output_path is None:
|
||||||
|
cell_name = cfg.get("cell_name", "CHART01").upper()
|
||||||
|
output_path = Path(project_path).parent / (cell_name + ".000")
|
||||||
|
else:
|
||||||
|
output_path = Path(output_path)
|
||||||
|
|
||||||
|
print(f"\nOutput : {output_path}")
|
||||||
|
print(f"Cell : {cfg.get('cell_name','?')} Scale 1:{cfg.get('scale','?')}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
writer = S57CellWriter(str(output_path), cfg)
|
||||||
|
writer.open()
|
||||||
|
|
||||||
|
for layer, s57_class in layer_assignments:
|
||||||
|
if s57_class is None:
|
||||||
|
print(f" SKIP {layer['name']} (no S-57 mapping)")
|
||||||
|
continue
|
||||||
|
ltype = layer.get("layer_type", "shp")
|
||||||
|
crs_str = layer.get("crs", "EPSG:4326")
|
||||||
|
print(f" Converting: {layer['name']} -> {s57_class}")
|
||||||
|
if ltype == "csv":
|
||||||
|
if verbose:
|
||||||
|
print(f" CSV: {layer['path']}")
|
||||||
|
count = writer.add_features_from_csv(
|
||||||
|
layer["path"], s57_class, attr_map,
|
||||||
|
x_field=layer.get("x_field", "lon"),
|
||||||
|
y_field=layer.get("y_field", "lat"),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
shp_path = layer["path"]
|
||||||
|
if verbose:
|
||||||
|
print(f" CRS: {crs_str} | SHP: {shp_path}")
|
||||||
|
if not shp_path.exists():
|
||||||
|
print(f" [ERR] File not found: {shp_path}")
|
||||||
|
continue
|
||||||
|
count = writer.add_features_from_shp(shp_path, crs_str, s57_class, attr_map)
|
||||||
|
print(f" {count} feature(s) written")
|
||||||
|
|
||||||
|
writer.close()
|
||||||
|
writer.summary()
|
||||||
|
|
||||||
|
if output_path.exists():
|
||||||
|
print(f"\nOutput file: {output_path} ({output_path.stat().st_size // 1024} KB)")
|
||||||
|
print("Done.")
|
||||||
|
|
||||||
|
|
||||||
|
# ── interactive mode ──────────────────────────────────────────────────────────
|
||||||
|
def interactive_mode():
|
||||||
|
print("\n=== QGIS -> S-57 Converter (Interactive) ===\n")
|
||||||
|
qgs = input("QGIS project (.qgs or .qgz): ").strip().strip('"')
|
||||||
|
if not Path(qgs).exists():
|
||||||
|
print(f"File not found: {qgs}"); sys.exit(1)
|
||||||
|
out = input("Output .000 [blank=auto]: ").strip().strip('"') or None
|
||||||
|
cfg = input("Config file [blank=default]: ").strip().strip('"') or None
|
||||||
|
convert(qgs, out, cfg, list_only=False, force=False, verbose=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ── CLI ───────────────────────────────────────────────────────────────────────
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
interactive_mode()
|
||||||
|
return
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Convert QGIS project SHP layers to S-57 ENC format")
|
||||||
|
parser.add_argument("project", help=".qgs or .qgz QGIS project file")
|
||||||
|
parser.add_argument("--output", "-o", help="Output .000 file")
|
||||||
|
parser.add_argument("--config", "-c", help="JSON config file")
|
||||||
|
parser.add_argument("--list", "-l", action="store_true",
|
||||||
|
help="List layers then exit")
|
||||||
|
parser.add_argument("--force", "-f", action="store_true",
|
||||||
|
help="Skip prompts for unmapped layers")
|
||||||
|
parser.add_argument("--verbose", "-v", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if not Path(args.project).exists():
|
||||||
|
print(f"[ERROR] Not found: {args.project}"); sys.exit(1)
|
||||||
|
convert(args.project, args.output, args.config,
|
||||||
|
args.list, args.force, args.verbose)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
@@ -0,0 +1,65 @@
|
|||||||
|
no_dimar,OBJNAM,lon,lat,feat_type,LITCHR,LITCHR_TXT,SIGGRP,SIGPER,COLOUR,COLOUR_TXT,COLPAT,VALNMR,HEIGHT,ORIENT,CATLAM,CATCAM,BOYSHP,BCNSHP,TOPSHP,INFORM,_dimar_char_raw,_source
|
||||||
|
13,Faro F1 Recalada,-74.849500,11.106167,LIGHTS,7,Iso,,2,4,green,,9,20,,,,,,,Torre naranja bandas blancas. Faro de Recalada,Iso G 2s,DIMAR Lista de Luces 2015
|
||||||
|
14,Faro F2 Recalada,-74.854667,11.106000,LIGHTS,7,Iso,,2,3,red,,13.4,23,,,,,,,Torre naranja bandas blancas. Racon B,Iso R 2s,DIMAR Lista de Luces 2015
|
||||||
|
32,Faro Morro Hermoso,-75.017500,10.963333,LIGHTS,2,Fl,,4,1,white,,28,134,,,,,,,Torre blanca bandas rojas. Giratorio,Fl W 4s,DIMAR Lista de Luces 2015
|
||||||
|
33,Faro Galerazamba,-75.266000,10.785333,LIGHTS,2,Fl,,4,1,white,,11,14,,,,,,,Torre fibra vidrio blanca bandas rojas. Giratorio,Fl W 4s,DIMAR Lista de Luces 2015
|
||||||
|
15,Faro X1,-74.849500,11.102167,BCNLAT,4,Q(4)G,4,11,4,green,,6,6,,1,,,3,,Torre verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
16,Faro X2,-74.853333,11.100000,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,3,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
18,Faro X3,-74.847167,11.091333,BCNLAT,4,Q(4)G,4,11,4,green,,6,6,,1,,,3,,Torre verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
17,Faro X4,-74.851667,11.093000,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,3,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
19,Faro X5,-74.846667,11.089167,BCNLAT,4,Q(4)G,4,11,4,green,,6,6,,1,,,3,,Torre verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
20,Faro X6,-74.850500,11.087667,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,3,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
21,Faro X7,-74.814333,11.041500,BCNLAT,4,Q(4)G,4,11,4,green,,6,8,,1,,,4,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
22,Faro X8,-74.849167,11.081667,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,3,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
23,Faro X9,-74.804833,11.035833,BCNLAT,4,Q(4)G,4,11,4,green,,6,8,,1,,,4,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
24,Faro X10,-74.848000,11.076000,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,3,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
25,Faro X11,-74.795500,11.029833,BCNLAT,4,Q(4)G,4,11,4,green,,6,8,,1,,,4,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
26,Faro X12,-74.844167,11.065000,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,4,,Baliza enrejado roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
27,Faro X13,-74.789667,11.025833,BCNLAT,4,Q(4)G,4,11,4,green,,6,8,,1,,,4,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
28,Faro X14,-74.839500,11.057833,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,3,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
30,Faro X15,-74.785500,11.022833,BCNLAT,4,Q(4)G,4,11,4,green,,6,6,,1,,,4,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
29,Faro X16,-74.833000,11.050000,BCNLAT,4,Q(4)R,4,11,3,red,,6,6,,2,,,3,,Torre roja bandas blancas,Q(4)R 11s,DIMAR Lista de Luces 2015
|
||||||
|
31,Faro X17,-74.778333,11.018667,BCNLAT,4,Q(4)G,4,11,4,green,,6,6,,1,,,4,,Baliza enrejado verde bandas blancas,Q(4)G 11s,DIMAR Lista de Luces 2015
|
||||||
|
196,Enfilacion E1,-74.848333,11.103667,LIGHTS,7,Iso,,5,1,white,,13,10,135.7,,,,,,Baliza enrejado naranja y blanco. Rumbo 135.7,Iso Bu 5s,DIMAR Lista de Luces 2015
|
||||||
|
197,Enfilacion E3,-74.846333,11.101667,LIGHTS,7,Iso,,5,1,white,,9,22,139.3,,,,,,Torre enrejada naranja y blanco. Rumbo 139.3,Iso Bu 5s,DIMAR Lista de Luces 2015
|
||||||
|
198,Enfilacion E3A,-74.845000,11.100333,LIGHTS,7,Iso,,5,1,white,,12.3,20,135.7,,,,,,Torre naranja y blanco. Rumbo 135.7,Iso W 5s,DIMAR Lista de Luces 2015
|
||||||
|
201,Enfilacion E4,-74.846833,11.070167,LIGHTS,7,Iso,,4,3,red,,4.5,11,142.3,,,,,,Baliza enrejado naranja bandas blancas. Rumbo 142.3,Iso R 4s,DIMAR Lista de Luces 2015
|
||||||
|
203,Enfilacion E6,-74.843667,11.063000,LIGHTS,7,Iso,,4,3,red,,8,12,167.7,,,,,,Baliza enrejado roja bandas blancas. Rumbo 167.7,Iso Bu 4s,DIMAR Lista de Luces 2015
|
||||||
|
204,Enfilacion E8,-74.841833,11.058500,LIGHTS,7,Iso,,4,1,white,,14.5,25,167.7,,,,,,Baliza enrejado naranja bandas blancas. Rumbo 167.7,Iso Bu 4s,DIMAR Lista de Luces 2015
|
||||||
|
205,Enfilacion E10,-74.841667,11.059833,LIGHTS,7,Iso,,5,4,green,,10,11,167.3,,,,,,Torre naranja bandas blancas. Rumbo 167.3,Iso G 5s,DIMAR Lista de Luces 2015
|
||||||
|
206,Enfilacion E12,-74.840667,11.056167,LIGHTS,7,Iso,,5,4,green,,8,22,167.3,,,,,,Baliza tablero blanco franja roja. Rumbo 167.3,Iso G 5s,DIMAR Lista de Luces 2015
|
||||||
|
207,Enfilacion E14,-74.840667,11.056167,LIGHTS,7,Iso,,6,1,white,,8,22,122,,,,,,Tablero blanco con franja roja. Rumbo 122,Iso Bu 6s,DIMAR Lista de Luces 2015
|
||||||
|
237,Enfilacion E16,-74.836667,11.053667,LIGHTS,7,Iso,,6,1,white,,9,12,122,,,,,,Baliza enrejado naranja bandas blancas. Rumbo 122,Iso Bu 6s,DIMAR Lista de Luces 2015
|
||||||
|
238,Enfilacion E18,-74.825500,11.043000,LIGHTS,2,Fl,,4,"1,3,4",white/red/green,,6,18,142,,,,,,Torre roja bandas blancas. Sector 9 grados. Rumbo 142,Fl WRG 4s,DIMAR Lista de Luces 2015
|
||||||
|
199,Boya No. 1,-74.833500,11.084500,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
200,Boya No. 3,-74.844833,11.075833,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
202,Boya No. 5,-74.841000,11.065667,BOYLAT,4,Q,,1,4,green,,6,4,,1,,4,,,Castillete verde,Q G 1s,DIMAR Lista de Luces 2015
|
||||||
|
208,Boya No. 7,-74.837500,11.060000,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
209,Boya No. 9,-74.824000,11.046833,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
210,Boya No. 11,-74.812167,11.039667,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
211,Boya No. 12,-74.813500,11.037167,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
212,Boya No. 13,-74.802000,11.034333,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
213,Boya No. 14,-74.788333,11.021833,BOYLAT,2,Fl,,3,3,red,,6,3,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
214,Boya No. 15,-74.793833,11.028167,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
215,Boya No. 16,-74.797500,11.027333,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
218,Boya No. 18,-74.788333,11.021833,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
219,Boya No. 19,-74.776500,11.017500,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
220,Boya No. 20,-74.777333,11.015500,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
221,Boya No. 21,-74.772000,11.014000,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
222,Boya No. 22,-74.773333,11.012333,BOYLAT,4,Q,,1,3,red,,6,4,,2,,4,,,Castillete roja,Q R 1s,DIMAR Lista de Luces 2015
|
||||||
|
223,Boya No. 23,-74.755000,10.975000,BOYLAT,2,Fl,,1.3,4,green,,6,3,,1,,4,,,Castillete verde,Fl G 1.3s,DIMAR Lista de Luces 2015
|
||||||
|
224,Boya No. 24,-74.770500,11.009167,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
225,Boya No. 25,-74.766333,11.006667,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
226,Boya No. 26,-74.768333,11.005833,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
227,Boya No. 27,-74.762000,10.998500,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
228,Boya No. 28,-74.765333,10.999833,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
229,Boya No. 29,-74.758000,10.987333,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
230,Boya No. 30,-74.760667,10.987333,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
231,Boya No. 31,-74.754833,10.975000,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
232,Boya No. 33,-74.755667,10.959333,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
233,Boya No. 35,-74.754167,10.942667,BOYLAT,2,Fl,,3,4,green,,6,4,,1,,4,,,Castillete verde,Fl G 3s,DIMAR Lista de Luces 2015
|
||||||
|
234,Boya No. 36,-74.756667,10.941500,BOYLAT,2,Fl,,3,3,red,,6,4,,2,,4,,,Castillete roja,Fl R 3s,DIMAR Lista de Luces 2015
|
||||||
|
235,Boya Cardinal Norte,-74.753500,10.959167,BOYCAR,2,Fl,,1,1,white,1,6,4,,,1,4,,,Castillete cardinal N negros,Fl W 1s,DIMAR Lista de Luces 2015
|
||||||
|
236,Boya Cardinal Sur,-74.753500,10.959167,BOYCAR,2,Fl,,15,1,white,1,6,4,,,3,4,,,Castillete cardinal S negros,Fl W 15s,DIMAR Lista de Luces 2015
|
||||||
|
239,Boya de Oleaje,-74.758000,11.134000,BOYSPP,2,Fl,,20,6,yellow,,4.5,0.5,,,,3,,,Esferica amarilla. Recolectora datos oceanograficos,Fl Y 20s,DIMAR Lista de Luces 2015
|
||||||
|
240,Boya Peligro Aislado,-74.757333,10.954500,BOYISD,2,Fl(2),2,4,1,white,1,3,3.3,,,,4,,,Castillete roja bandas negras. Bajo rocoso,Fl(2) W 4s,DIMAR Lista de Luces 2015
|
||||||
|
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,324 @@
|
|||||||
|
"""
|
||||||
|
QGISS57Converter — GUI
|
||||||
|
Corre el converter en el mismo proceso (sin subprocess ni conda).
|
||||||
|
Completamente portable: solo necesita este .exe.
|
||||||
|
"""
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import queue
|
||||||
|
import sys as _sys
|
||||||
|
import threading
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog, messagebox, scrolledtext
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ── paths ─────────────────────────────────────────────────────────────────────
|
||||||
|
if getattr(_sys, "frozen", False):
|
||||||
|
SCRIPT_DIR = Path(_sys._MEIPASS)
|
||||||
|
else:
|
||||||
|
SCRIPT_DIR = Path(__file__).parent
|
||||||
|
|
||||||
|
_sys.path.insert(0, str(SCRIPT_DIR))
|
||||||
|
|
||||||
|
# Tell pyproj where its PROJ data is (bundled inside the exe)
|
||||||
|
if getattr(_sys, "frozen", False):
|
||||||
|
os.environ["PROJ_DATA"] = str(SCRIPT_DIR / "proj_data")
|
||||||
|
os.environ["PROJ_LIB"] = str(SCRIPT_DIR / "proj_data")
|
||||||
|
|
||||||
|
CONFIG = SCRIPT_DIR / "cell_config.json"
|
||||||
|
REF_PDF = SCRIPT_DIR / "CAPAS_REFERENCIA.pdf"
|
||||||
|
MANUAL_HTML = SCRIPT_DIR / "MANUAL.html"
|
||||||
|
|
||||||
|
# Import converter (bundled alongside gui in the exe)
|
||||||
|
from converter import convert as _do_convert
|
||||||
|
|
||||||
|
# ── theme ─────────────────────────────────────────────────────────────────────
|
||||||
|
BG = "#1e2532"
|
||||||
|
PANEL = "#252d3d"
|
||||||
|
ACCENT = "#1e7fc8"
|
||||||
|
ACCENT_HOV = "#1565a0"
|
||||||
|
TEXT_LIGHT = "#e8eaf0"
|
||||||
|
TEXT_DIM = "#8a94aa"
|
||||||
|
GREEN = "#2ecc71"
|
||||||
|
RED = "#e74c3c"
|
||||||
|
FONT_UI = ("Segoe UI", 10)
|
||||||
|
FONT_MONO = ("Consolas", 9)
|
||||||
|
|
||||||
|
|
||||||
|
# ── stdout capture ────────────────────────────────────────────────────────────
|
||||||
|
class _QueueWriter:
|
||||||
|
"""Redirects print() output to a queue for the GUI to consume."""
|
||||||
|
def __init__(self, q: queue.Queue):
|
||||||
|
self._q = q
|
||||||
|
def write(self, text):
|
||||||
|
if text:
|
||||||
|
self._q.put(text)
|
||||||
|
def flush(self):
|
||||||
|
pass
|
||||||
|
def isatty(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# ── app ───────────────────────────────────────────────────────────────────────
|
||||||
|
class App(tk.Tk):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.title("QGISS57Converter")
|
||||||
|
self.geometry("720x540")
|
||||||
|
self.resizable(True, True)
|
||||||
|
self.minsize(600, 440)
|
||||||
|
self.configure(bg=BG)
|
||||||
|
self._converting = False
|
||||||
|
self._log_queue = queue.Queue()
|
||||||
|
self._cancel_flag = threading.Event()
|
||||||
|
self._build_ui()
|
||||||
|
|
||||||
|
# ── UI ────────────────────────────────────────────────────────────────────
|
||||||
|
def _build_ui(self):
|
||||||
|
hdr = tk.Frame(self, bg=ACCENT, height=48)
|
||||||
|
hdr.pack(fill="x")
|
||||||
|
tk.Label(hdr, text="QGIS -> S-57 ENC Converter",
|
||||||
|
bg=ACCENT, fg="white",
|
||||||
|
font=("Segoe UI", 13, "bold")).pack(side="left", padx=16, pady=10)
|
||||||
|
tk.Button(hdr, text="? Manual de usuario",
|
||||||
|
bg=ACCENT_HOV, fg="white", relief="flat",
|
||||||
|
font=("Segoe UI", 9), cursor="hand2", bd=0, padx=8,
|
||||||
|
command=self._open_manual).pack(side="right", padx=4, pady=10)
|
||||||
|
tk.Button(hdr, text="? Referencia de capas",
|
||||||
|
bg=ACCENT_HOV, fg="white", relief="flat",
|
||||||
|
font=("Segoe UI", 9), cursor="hand2", bd=0, padx=8,
|
||||||
|
command=self._open_ref).pack(side="right", padx=4, pady=10)
|
||||||
|
|
||||||
|
body = tk.Frame(self, bg=BG)
|
||||||
|
body.pack(fill="both", expand=True, padx=20, pady=16)
|
||||||
|
body.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# entrada
|
||||||
|
tk.Label(body, text="Proyecto QGIS (.qgz / .qgs)",
|
||||||
|
bg=BG, fg=TEXT_DIM, font=FONT_UI).grid(row=0, column=0, sticky="w")
|
||||||
|
row_in = tk.Frame(body, bg=BG)
|
||||||
|
row_in.grid(row=1, column=0, sticky="ew", pady=(2, 10))
|
||||||
|
self._var_in = tk.StringVar()
|
||||||
|
self._ent_in = tk.Entry(row_in, textvariable=self._var_in,
|
||||||
|
bg=PANEL, fg=TEXT_LIGHT, insertbackground=TEXT_LIGHT,
|
||||||
|
relief="flat", font=FONT_UI, bd=6)
|
||||||
|
self._ent_in.pack(side="left", fill="x", expand=True)
|
||||||
|
self._ent_in.bind("<FocusOut>", lambda _: self._auto_output())
|
||||||
|
tk.Button(row_in, text="Examinar...", bg=ACCENT, fg="white",
|
||||||
|
relief="flat", font=FONT_UI, cursor="hand2", bd=0, padx=10,
|
||||||
|
command=self._pick_input).pack(side="left", padx=(6, 0))
|
||||||
|
|
||||||
|
# salida
|
||||||
|
tk.Label(body, text="Archivo de salida (.000)",
|
||||||
|
bg=BG, fg=TEXT_DIM, font=FONT_UI).grid(row=2, column=0, sticky="w")
|
||||||
|
row_out = tk.Frame(body, bg=BG)
|
||||||
|
row_out.grid(row=3, column=0, sticky="ew", pady=(2, 10))
|
||||||
|
self._var_out = tk.StringVar()
|
||||||
|
tk.Entry(row_out, textvariable=self._var_out,
|
||||||
|
bg=PANEL, fg=TEXT_LIGHT, insertbackground=TEXT_LIGHT,
|
||||||
|
relief="flat", font=FONT_UI, bd=6).pack(side="left", fill="x", expand=True)
|
||||||
|
tk.Button(row_out, text="Guardar en...", bg=PANEL, fg=TEXT_LIGHT,
|
||||||
|
relief="flat", font=FONT_UI, cursor="hand2", bd=0, padx=10,
|
||||||
|
command=self._pick_output).pack(side="left", padx=(6, 0))
|
||||||
|
|
||||||
|
# directorio extra de CSVs (opcional)
|
||||||
|
tk.Label(body, text="Directorio extra de CSVs (opcional — boyas, luces, etc.)",
|
||||||
|
bg=BG, fg=TEXT_DIM, font=FONT_UI).grid(row=4, column=0, sticky="w")
|
||||||
|
row_csv = tk.Frame(body, bg=BG)
|
||||||
|
row_csv.grid(row=5, column=0, sticky="ew", pady=(2, 4))
|
||||||
|
self._var_csv = tk.StringVar()
|
||||||
|
self._ent_csv = tk.Entry(row_csv, textvariable=self._var_csv,
|
||||||
|
bg=PANEL, fg=TEXT_LIGHT, insertbackground=TEXT_LIGHT,
|
||||||
|
relief="flat", font=FONT_UI, bd=6)
|
||||||
|
self._ent_csv.pack(side="left", fill="x", expand=True)
|
||||||
|
tk.Button(row_csv, text="Examinar...", bg=PANEL, fg=TEXT_LIGHT,
|
||||||
|
relief="flat", font=FONT_UI, cursor="hand2", bd=0, padx=10,
|
||||||
|
command=self._pick_csv_dir).pack(side="left", padx=(6, 0))
|
||||||
|
tk.Button(row_csv, text="✕", bg=PANEL, fg=TEXT_DIM,
|
||||||
|
relief="flat", font=("Segoe UI", 9), cursor="hand2", bd=0, padx=6,
|
||||||
|
command=lambda: self._var_csv.set("")).pack(side="left", padx=(2, 0))
|
||||||
|
tk.Label(body,
|
||||||
|
text="Los archivos .csv en ese directorio se incluyen como capas extra "
|
||||||
|
"(nombre del archivo = clase S-57, ej. BOYLAT.csv → BOYLAT).",
|
||||||
|
bg=BG, fg=TEXT_DIM, font=("Segoe UI", 8),
|
||||||
|
wraplength=640, justify="left").grid(row=6, column=0, sticky="w",
|
||||||
|
pady=(0, 8))
|
||||||
|
|
||||||
|
# botones
|
||||||
|
row_btn = tk.Frame(body, bg=BG)
|
||||||
|
row_btn.grid(row=7, column=0, sticky="ew", pady=(4, 12))
|
||||||
|
self._btn_convert = tk.Button(
|
||||||
|
row_btn, text=">> Convertir",
|
||||||
|
bg=GREEN, fg="white", activebackground="#27ae60",
|
||||||
|
relief="flat", font=("Segoe UI", 11, "bold"),
|
||||||
|
cursor="hand2", bd=0, padx=20, pady=8,
|
||||||
|
command=self._start_conversion)
|
||||||
|
self._btn_convert.pack(side="left")
|
||||||
|
self._btn_open = tk.Button(
|
||||||
|
row_btn, text="Abrir carpeta",
|
||||||
|
bg=PANEL, fg=TEXT_LIGHT, relief="flat", font=FONT_UI,
|
||||||
|
cursor="hand2", bd=0, padx=14, pady=8,
|
||||||
|
state="disabled", command=self._open_output_dir)
|
||||||
|
self._btn_open.pack(side="left", padx=(8, 0))
|
||||||
|
|
||||||
|
# estado
|
||||||
|
self._lbl_status = tk.Label(body, text="Listo.", bg=BG, fg=TEXT_DIM,
|
||||||
|
font=("Segoe UI", 9))
|
||||||
|
self._lbl_status.grid(row=8, column=0, sticky="w")
|
||||||
|
|
||||||
|
# log
|
||||||
|
tk.Label(body, text="Log de conversion",
|
||||||
|
bg=BG, fg=TEXT_DIM, font=FONT_UI).grid(row=9, column=0,
|
||||||
|
sticky="w", pady=(10, 2))
|
||||||
|
self._log = scrolledtext.ScrolledText(
|
||||||
|
body, bg="#0d1117", fg="#c9d1d9",
|
||||||
|
insertbackground="white", relief="flat",
|
||||||
|
font=FONT_MONO, bd=4, height=12, wrap="word")
|
||||||
|
self._log.grid(row=10, column=0, sticky="nsew")
|
||||||
|
self._log.configure(state="disabled")
|
||||||
|
self._log.tag_configure("green", foreground=GREEN)
|
||||||
|
self._log.tag_configure("red", foreground=RED)
|
||||||
|
body.rowconfigure(10, weight=1)
|
||||||
|
|
||||||
|
# ── acciones ──────────────────────────────────────────────────────────────
|
||||||
|
def _pick_input(self):
|
||||||
|
p = filedialog.askopenfilename(
|
||||||
|
title="Seleccionar proyecto QGIS",
|
||||||
|
filetypes=[("QGIS project", "*.qgz *.qgs"), ("Todos", "*.*")])
|
||||||
|
if p:
|
||||||
|
self._var_in.set(p)
|
||||||
|
self._auto_output()
|
||||||
|
|
||||||
|
def _auto_output(self):
|
||||||
|
src = Path(self._var_in.get().strip())
|
||||||
|
if src.exists():
|
||||||
|
self._var_out.set(str(src.parent / (src.stem.replace(" ", "_") + ".000")))
|
||||||
|
|
||||||
|
def _pick_output(self):
|
||||||
|
p = filedialog.asksaveasfilename(
|
||||||
|
title="Guardar carta S-57 como",
|
||||||
|
defaultextension=".000",
|
||||||
|
filetypes=[("S-57 ENC", "*.000"), ("Todos", "*.*")])
|
||||||
|
if p:
|
||||||
|
self._var_out.set(p)
|
||||||
|
|
||||||
|
def _open_manual(self):
|
||||||
|
if MANUAL_HTML.exists():
|
||||||
|
import webbrowser
|
||||||
|
webbrowser.open(MANUAL_HTML.as_uri())
|
||||||
|
else:
|
||||||
|
messagebox.showinfo("Manual", "No se encontro MANUAL.html.")
|
||||||
|
|
||||||
|
def _open_ref(self):
|
||||||
|
if REF_PDF.exists():
|
||||||
|
os.startfile(str(REF_PDF))
|
||||||
|
else:
|
||||||
|
messagebox.showinfo("Referencia", "No se encontro CAPAS_REFERENCIA.pdf.")
|
||||||
|
|
||||||
|
def _pick_csv_dir(self):
|
||||||
|
p = filedialog.askdirectory(title="Seleccionar directorio con archivos CSV extra")
|
||||||
|
if p:
|
||||||
|
self._var_csv.set(p)
|
||||||
|
|
||||||
|
def _open_output_dir(self):
|
||||||
|
out = Path(self._var_out.get().strip())
|
||||||
|
folder = out.parent if out.parent.exists() else SCRIPT_DIR
|
||||||
|
os.startfile(str(folder))
|
||||||
|
|
||||||
|
def _log_write(self, text, tag=None):
|
||||||
|
self._log.configure(state="normal")
|
||||||
|
self._log.insert("end", text, tag or ())
|
||||||
|
self._log.see("end")
|
||||||
|
self._log.configure(state="disabled")
|
||||||
|
|
||||||
|
def _set_status(self, msg, color=TEXT_DIM):
|
||||||
|
self._lbl_status.configure(text=msg, fg=color)
|
||||||
|
|
||||||
|
# ── conversion ────────────────────────────────────────────────────────────
|
||||||
|
def _start_conversion(self):
|
||||||
|
src = self._var_in.get().strip()
|
||||||
|
out = self._var_out.get().strip()
|
||||||
|
csv_dir = self._var_csv.get().strip() or None
|
||||||
|
if not src:
|
||||||
|
messagebox.showwarning("Falta archivo", "Selecciona un archivo .qgz primero.")
|
||||||
|
return
|
||||||
|
if not Path(src).exists():
|
||||||
|
messagebox.showerror("No encontrado", f"No existe:\n{src}")
|
||||||
|
return
|
||||||
|
if csv_dir and not Path(csv_dir).is_dir():
|
||||||
|
messagebox.showerror("Directorio no existe",
|
||||||
|
f"El directorio de CSVs no existe:\n{csv_dir}")
|
||||||
|
return
|
||||||
|
if not out:
|
||||||
|
self._auto_output()
|
||||||
|
out = self._var_out.get().strip()
|
||||||
|
|
||||||
|
self._log.configure(state="normal")
|
||||||
|
self._log.delete("1.0", "end")
|
||||||
|
self._log.configure(state="disabled")
|
||||||
|
self._btn_convert.configure(state="disabled")
|
||||||
|
self._btn_open.configure(state="disabled")
|
||||||
|
self._set_status("Convirtiendo...", ACCENT)
|
||||||
|
self._converting = True
|
||||||
|
|
||||||
|
# Start polling the log queue
|
||||||
|
self._poll_log()
|
||||||
|
|
||||||
|
threading.Thread(target=self._run_conversion,
|
||||||
|
args=(src, out, csv_dir), daemon=True).start()
|
||||||
|
|
||||||
|
def _poll_log(self):
|
||||||
|
"""Drain the log queue and update the text widget."""
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
text = self._log_queue.get_nowait()
|
||||||
|
self._log_write(text)
|
||||||
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
if self._converting:
|
||||||
|
self.after(40, self._poll_log)
|
||||||
|
|
||||||
|
def _run_conversion(self, src: str, out: str, csv_dir=None):
|
||||||
|
old_stdout = _sys.stdout
|
||||||
|
old_stderr = _sys.stderr
|
||||||
|
writer = _QueueWriter(self._log_queue)
|
||||||
|
_sys.stdout = writer
|
||||||
|
_sys.stderr = writer
|
||||||
|
rc = 0
|
||||||
|
try:
|
||||||
|
_do_convert(
|
||||||
|
project_path = src,
|
||||||
|
output_path = out,
|
||||||
|
config_path = str(CONFIG),
|
||||||
|
list_only = False,
|
||||||
|
force = True,
|
||||||
|
verbose = True,
|
||||||
|
extra_csv_dir = csv_dir,
|
||||||
|
)
|
||||||
|
except SystemExit as e:
|
||||||
|
rc = int(e.code) if e.code is not None else 1
|
||||||
|
except Exception as e:
|
||||||
|
_sys.stdout.write(f"\n[ERROR] {e}\n")
|
||||||
|
rc = 1
|
||||||
|
finally:
|
||||||
|
_sys.stdout = old_stdout
|
||||||
|
_sys.stderr = old_stderr
|
||||||
|
|
||||||
|
self.after(0, self._on_done, rc, out)
|
||||||
|
|
||||||
|
def _on_done(self, rc: int, out: str):
|
||||||
|
self._converting = False
|
||||||
|
self._btn_convert.configure(state="normal")
|
||||||
|
if rc == 0 and Path(out).exists():
|
||||||
|
size = Path(out).stat().st_size
|
||||||
|
self._log_write(f"\nListo: {out} ({size:,} bytes)\n", "green")
|
||||||
|
self._set_status(f"Completado: {Path(out).name}", GREEN)
|
||||||
|
self._btn_open.configure(state="normal")
|
||||||
|
else:
|
||||||
|
self._log_write(f"\nLa conversion fallo (codigo {rc})\n", "red")
|
||||||
|
self._set_status("Error en la conversion.", RED)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = App()
|
||||||
|
app.mainloop()
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
"""Genera CAPAS_REFERENCIA.pdf en el directorio del converter."""
|
||||||
|
from fpdf import FPDF
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
OUT = Path(__file__).parent / "CAPAS_REFERENCIA.pdf"
|
||||||
|
|
||||||
|
# ── datos ──────────────────────────────────────────────────────────────────────
|
||||||
|
SECCIONES = [
|
||||||
|
("OBJETOS PUNTUALES", [
|
||||||
|
("BOYLAT", "Boya lateral (babor/estribor)", "boyas, buoys"),
|
||||||
|
("BOYCAR", "Boya cardinal (N/S/E/W)", "boycar"),
|
||||||
|
("BOYISD", "Boya de peligro aislado", "boyisd"),
|
||||||
|
("BOYSAW", "Boya de aguas seguras", "boysaw"),
|
||||||
|
("BCNLAT", "Baliza lateral", "balizas, beacons"),
|
||||||
|
("BCNSPP", "Baliza especial", "bcnspp"),
|
||||||
|
("LIGHTS", "Luz / faro", "luces, lights, faroles"),
|
||||||
|
("LNDMRK", "Hito en tierra (torre, tanque...)", "Puntos del Terreno"),
|
||||||
|
("SOUNDG", "Sonda batimetrica", "sondas, soundings, profundidades"),
|
||||||
|
("UWTROC", "Roca sumergida / a flor de agua", "rocas, rocks"),
|
||||||
|
("WRECKS", "Naufragio", "naufragio, wreck"),
|
||||||
|
("OBSTRN", "Obstruccion", "obstruccion, obstruction"),
|
||||||
|
]),
|
||||||
|
("OBJETOS LINEALES", [
|
||||||
|
("COALNE", "Linea de costa", "Linderos, coastline, costa, linea_de_costa"),
|
||||||
|
("DEPCNT", "Curva batimetrica (isobata)", "isobata, curvas_nivel, depth_contour"),
|
||||||
|
("CBLSUB", "Cable submarino", "cable"),
|
||||||
|
("PIPSOL", "Tuberia submarina / en tierra", "tuberia"),
|
||||||
|
("RIVERS", "Rio / canal", "rio, river"),
|
||||||
|
]),
|
||||||
|
("OBJETOS DE AREA", [
|
||||||
|
("LNDARE", "Area terrestre", "Area Terreno, tierra, land"),
|
||||||
|
("DEPARE", "Area de profundidad", "fondos, batimetria, depth_area"),
|
||||||
|
("FAIRWY", "Canal de navegacion", "canal_navegacion, fairway"),
|
||||||
|
("RESARE", "Area restringida", "zona_restringida, restricted"),
|
||||||
|
("ACHARE", "Area de fondeo", "fondeadero, anchorage"),
|
||||||
|
("HRBARE", "Area portuaria", "puerto, harbor"),
|
||||||
|
("BERTHS", "Atraque / muelle", "atraque, berth"),
|
||||||
|
("SBDARE", "Area de fondo marino", "fondo_marino, seabed"),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
|
||||||
|
ATTR_ROWS = [
|
||||||
|
("nombre / name", "OBJNAM", "Nombre del objeto (aparece en tooltip)"),
|
||||||
|
("catlam / lateral", "CATLAM", "Categoria lateral: 1=babor, 2=estribor"),
|
||||||
|
("colour / color", "COLOUR", "Color: 1=blanco, 3=rojo, 4=verde, 6=amarillo"),
|
||||||
|
("colpat / patron", "COLPAT", "Patron de color: 1=horizontal, 2=vertical"),
|
||||||
|
("boyshp / forma", "BOYSHP", "Forma boya: 1=conica, 2=cilindrica, 4=esferica"),
|
||||||
|
("litchr / destello", "LITCHR", "Destello: 1=F, 2=Fl, 3=LFl, 4=Q, 8=Iso, 5=IQ"),
|
||||||
|
("sigper / periodo", "SIGPER", "Periodo en segundos (ej: 4.0)"),
|
||||||
|
("siggrp / grupo", "SIGGRP", "Grupo de destellos: (2), (2+1), etc."),
|
||||||
|
("alcance / range", "VALNMR", "Alcance nominal en millas nauticas"),
|
||||||
|
("altura / height", "HEIGHT", "Altura de la luz sobre el mar (metros)"),
|
||||||
|
("status / estado", "STATUS", "Estado: 1=permanente, 2=ocasional"),
|
||||||
|
("profundidad / depth", "DRVAL1", "Profundidad minima (metros)"),
|
||||||
|
("depth_max", "DRVAL2", "Profundidad maxima (metros)"),
|
||||||
|
("sonda / sounding", "VALSOU", "Valor de sonda (metros)"),
|
||||||
|
("contour / valor", "VALDCO", "Valor de curva batimetrica (metros)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
LITCHR_ROWS = [
|
||||||
|
("1", "F", "Fija"),
|
||||||
|
("2", "Fl", "Destellante"),
|
||||||
|
("3", "LFl", "Gran destello"),
|
||||||
|
("4", "Q", "Centelleante"),
|
||||||
|
("5", "IQ", "Centelleante interrumpido"),
|
||||||
|
("6", "Oc", "Ocultante"),
|
||||||
|
("8", "Iso", "Isofasica"),
|
||||||
|
("25", "VQ", "Rapida continua"),
|
||||||
|
("28", "Mo", "Morse"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ── PDF ────────────────────────────────────────────────────────────────────────
|
||||||
|
class PDF(FPDF):
|
||||||
|
def header(self):
|
||||||
|
self.set_font("Helvetica", "B", 11)
|
||||||
|
self.set_fill_color(30, 80, 140)
|
||||||
|
self.set_text_color(255, 255, 255)
|
||||||
|
self.cell(0, 10, "QGISS57Converter - Referencia de nombres de capas", fill=True, new_x="LMARGIN", new_y="NEXT")
|
||||||
|
self.set_text_color(0, 0, 0)
|
||||||
|
self.ln(2)
|
||||||
|
|
||||||
|
def footer(self):
|
||||||
|
self.set_y(-12)
|
||||||
|
self.set_font("Helvetica", "I", 8)
|
||||||
|
self.set_text_color(120, 120, 120)
|
||||||
|
self.cell(0, 10, f"Pagina {self.page_no()}", align="C")
|
||||||
|
|
||||||
|
|
||||||
|
pdf = PDF(orientation="P", unit="mm", format="A4")
|
||||||
|
pdf.set_auto_page_break(auto=True, margin=15)
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.set_font("Helvetica", size=9)
|
||||||
|
|
||||||
|
# intro
|
||||||
|
pdf.set_font("Helvetica", size=9)
|
||||||
|
pdf.multi_cell(0, 5,
|
||||||
|
"Nombra tus capas QGIS con cualquiera de los textos de la columna 'Nombres reconocidos' "
|
||||||
|
"(sin importar mayusculas). Tambien puedes usar el acronimo S-57 directamente como nombre de capa. "
|
||||||
|
"Para agregar nombres nuevos, edita 'layer_mappings' en cell_config.json.")
|
||||||
|
pdf.ln(3)
|
||||||
|
|
||||||
|
COL = [22, 58, 110] # x positions
|
||||||
|
W = [22, 55, 78] # widths
|
||||||
|
|
||||||
|
def th(texts, fill_rgb=(200, 215, 235)):
|
||||||
|
pdf.set_fill_color(*fill_rgb)
|
||||||
|
pdf.set_font("Helvetica", "B", 8)
|
||||||
|
for i, t in enumerate(texts):
|
||||||
|
pdf.set_x(10 + sum(W[:i]))
|
||||||
|
pdf.cell(W[i], 6, t, border=1, fill=True)
|
||||||
|
pdf.ln()
|
||||||
|
|
||||||
|
def tr(texts, alt=False):
|
||||||
|
fill_rgb = (245, 248, 252) if alt else (255, 255, 255)
|
||||||
|
pdf.set_fill_color(*fill_rgb)
|
||||||
|
pdf.set_font("Helvetica", size=8)
|
||||||
|
# measure max height needed
|
||||||
|
heights = []
|
||||||
|
for i, t in enumerate(texts):
|
||||||
|
lines = pdf.get_string_width(t) / W[i] + 1
|
||||||
|
heights.append(max(1, int(lines) + 1) * 5)
|
||||||
|
h = max(heights)
|
||||||
|
h = min(h, 10)
|
||||||
|
for i, t in enumerate(texts):
|
||||||
|
pdf.set_x(10 + sum(W[:i]))
|
||||||
|
pdf.multi_cell(W[i], h / max(1, pdf.get_string_width(t) / W[i] + 1),
|
||||||
|
t, border=1, fill=True, max_line_height=5)
|
||||||
|
# ensure we're on next line
|
||||||
|
pdf.ln(0)
|
||||||
|
|
||||||
|
for section_title, rows in SECCIONES:
|
||||||
|
pdf.set_font("Helvetica", "B", 9)
|
||||||
|
pdf.set_fill_color(30, 80, 140)
|
||||||
|
pdf.set_text_color(255, 255, 255)
|
||||||
|
pdf.cell(0, 7, f" {section_title}", fill=True, new_x="LMARGIN", new_y="NEXT")
|
||||||
|
pdf.set_text_color(0, 0, 0)
|
||||||
|
th(["Acronimo S-57", "Descripcion", "Nombres reconocidos en QGIS"])
|
||||||
|
for idx, (acro, desc, aliases) in enumerate(rows):
|
||||||
|
pdf.set_fill_color(245, 248, 252) if idx % 2 else pdf.set_fill_color(255, 255, 255)
|
||||||
|
pdf.set_font("Helvetica", "B", 8)
|
||||||
|
pdf.set_x(10)
|
||||||
|
pdf.cell(W[0], 6, acro, border=1, fill=True)
|
||||||
|
pdf.set_font("Helvetica", size=8)
|
||||||
|
pdf.cell(W[1], 6, desc, border=1, fill=True)
|
||||||
|
pdf.multi_cell(W[2], 6, aliases, border=1, fill=True)
|
||||||
|
pdf.ln(4)
|
||||||
|
|
||||||
|
# atributos
|
||||||
|
pdf.set_font("Helvetica", "B", 9)
|
||||||
|
pdf.set_fill_color(30, 80, 140)
|
||||||
|
pdf.set_text_color(255, 255, 255)
|
||||||
|
pdf.cell(0, 7, " CAMPOS DE ATRIBUTOS (columnas de tu SHP)", fill=True, new_x="LMARGIN", new_y="NEXT")
|
||||||
|
pdf.set_text_color(0, 0, 0)
|
||||||
|
|
||||||
|
AW = [55, 22, 78]
|
||||||
|
pdf.set_fill_color(200, 215, 235)
|
||||||
|
pdf.set_font("Helvetica", "B", 8)
|
||||||
|
pdf.set_x(10); pdf.cell(AW[0], 6, "Nombre campo en SHP", border=1, fill=True)
|
||||||
|
pdf.cell(AW[1], 6, "Atrib. S-57", border=1, fill=True)
|
||||||
|
pdf.cell(AW[2], 6, "Descripcion", border=1, fill=True)
|
||||||
|
pdf.ln()
|
||||||
|
|
||||||
|
for idx, (shp_col, s57, desc) in enumerate(ATTR_ROWS):
|
||||||
|
pdf.set_fill_color(245, 248, 252) if idx % 2 else pdf.set_fill_color(255, 255, 255)
|
||||||
|
pdf.set_font("Helvetica", size=8)
|
||||||
|
pdf.set_x(10); pdf.cell(AW[0], 6, shp_col, border=1, fill=True)
|
||||||
|
pdf.set_font("Helvetica", "B", 8)
|
||||||
|
pdf.cell(AW[1], 6, s57, border=1, fill=True)
|
||||||
|
pdf.set_font("Helvetica", size=8)
|
||||||
|
pdf.cell(AW[2], 6, desc, border=1, fill=True)
|
||||||
|
pdf.ln()
|
||||||
|
|
||||||
|
# tabla LITCHR
|
||||||
|
pdf.ln(4)
|
||||||
|
pdf.set_font("Helvetica", "B", 9)
|
||||||
|
pdf.set_fill_color(30, 80, 140)
|
||||||
|
pdf.set_text_color(255, 255, 255)
|
||||||
|
pdf.cell(0, 7, " CODIGOS LITCHR - Caracteristica de luz", fill=True,
|
||||||
|
new_x="LMARGIN", new_y="NEXT")
|
||||||
|
pdf.set_text_color(0, 0, 0)
|
||||||
|
|
||||||
|
LW = [20, 30, 100]
|
||||||
|
pdf.set_fill_color(200, 215, 235)
|
||||||
|
pdf.set_font("Helvetica", "B", 8)
|
||||||
|
pdf.set_x(10); pdf.cell(LW[0], 6, "Codigo", border=1, fill=True)
|
||||||
|
pdf.cell(LW[1], 6, "Abrev.", border=1, fill=True)
|
||||||
|
pdf.cell(LW[2], 6, "Descripcion", border=1, fill=True)
|
||||||
|
pdf.ln()
|
||||||
|
for idx, (code, abbr, desc) in enumerate(LITCHR_ROWS):
|
||||||
|
pdf.set_fill_color(245, 248, 252) if idx % 2 else pdf.set_fill_color(255, 255, 255)
|
||||||
|
pdf.set_font("Helvetica", size=8)
|
||||||
|
pdf.set_x(10); pdf.cell(LW[0], 6, code, border=1, fill=True)
|
||||||
|
pdf.cell(LW[1], 6, abbr, border=1, fill=True)
|
||||||
|
pdf.cell(LW[2], 6, desc, border=1, fill=True)
|
||||||
|
pdf.ln()
|
||||||
|
|
||||||
|
pdf.ln(5)
|
||||||
|
pdf.set_font("Helvetica", "I", 8)
|
||||||
|
pdf.set_text_color(80, 80, 80)
|
||||||
|
pdf.multi_cell(0, 5,
|
||||||
|
"Nota: para anadir un nombre de capa no listado aqui, edita 'layer_mappings' en cell_config.json. "
|
||||||
|
"Para anadir campos de atributos nuevos, edita 'attribute_mappings'.")
|
||||||
|
|
||||||
|
pdf.output(str(OUT))
|
||||||
|
print(f"PDF generado: {OUT} ({OUT.stat().st_size} bytes)")
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
lon,lat,OBJNAM,NOBJNM,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,SIGPER2,VALNMR,HEIGHT,SECTR1,SECTR2,MLTYLT,ORIENT,BOYSHP,CATLBR,CATCAM,INFORM,TXTDSC,_nga_feature,_nga_volume,_nga_region,_nga_char_raw,_nga_hfm_raw,_nga_range_raw,_nga_notice
|
||||||
|
-74.253333,11.153333,SBM,,2,Fl.W.,,1,W,8,,8,,,,,,,,,Radar Reflector. ,Yellow CALM superbuoy.,16831 J6255.30,PUB 110,,Fl.W. | period 8s | ,,8,201506
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
lon,lat,OBJNAM,NOBJNM,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,SIGPER2,VALNMR,HEIGHT,SECTR1,SECTR2,MLTYLT,ORIENT,BOYSHP,CATLBR,CATCAM,INFORM,TXTDSC,_nga_feature,_nga_volume,_nga_region,_nga_char_raw,_nga_hfm_raw,_nga_range_raw,_nga_notice
|
||||||
|
-75.598336,10.316667,Sea buoy,,5,Q.W.,,"1,3","W,R",,,,,,,,,3,,,,"SAFE WATER RW, buoy, topmark.",16704,PUB 110,CARTAGENA:,Q.W. | ,,,201622
|
||||||
|
@@ -0,0 +1,62 @@
|
|||||||
|
lon,lat,OBJNAM,NOBJNM,LITCHR,LITCHR_TXT,SIGGRP,COLOUR,COLOUR_TXT,SIGPER,SIGPER2,VALNMR,HEIGHT,SECTR1,SECTR2,MLTYLT,ORIENT,BOYSHP,CATLBR,CATCAM,INFORM,TXTDSC,_nga_feature,_nga_volume,_nga_region,_nga_char_raw,_nga_hfm_raw,_nga_range_raw,_nga_notice
|
||||||
|
-75.786389,9.529167,Tanker Loading Unit TLU-3,,13,Mo.(U)Y.,(U),6,Y,15,,,,,,,,,,,,,16674 J6153.6,PUB 110,GOLFO DE MORROSQUILLO:,Mo.(U)Y. | period 15s | ,,,201904
|
||||||
|
-75.992225,9.591111,Roca Morrosquillo,,2,Fl.W.,,1,W,3,0.3,10,6,,,,,,,,,"Red tower, white bands.",16678 J6154.5,PUB 110,GOLFO DE MORROSQUILLO:,"Fl.W. | period 3s | fl. 0.3s, ec. 2.7s | ",20ft/6,10,201506
|
||||||
|
-75.855281,9.693139,Isla Ceycen,,2,Fl.W.,,1,W,10,0.8,17,20,,,,,,,,,"Red tower, white bands; 59.",16679 J6156,PUB 110,,"Fl.W. | period 10s | fl. 0.8s, ec. 9.2s | ",66ft/20,17,201647
|
||||||
|
-75.87075,9.782528,Isla Mucura,,2,Fl.W.,,1,W,6.7,0.5,11,20,,,,,,,,,"Red tower, white bands; 59.",16680 J6157,PUB 110,,"Fl.W. | period 6.7s | fl. 0.5s, ec. 6.2s | ",66ft/20,11,201506
|
||||||
|
-75.72745,10.145556,Isla Arenas,,2,Fl.W.,,1,W,12,1.2,13,20,,,,,,,,,"White metal tower, red stripes; 59.",16684 J6159,PUB 110,,"Fl.W. | period 12s | fl. 1.2s, ec. 10.8s | ",66ft/20,13,201506
|
||||||
|
-75.80055,10.168056,Isla del Rosario,,2,Fl.W.,,1,W,10,0.79,12,14,,,,,,,,,"White tower, red bands; 39.",16687.1 J6159.7,PUB 110,,"Fl.W. | period 10s | fl. 0.79s, ec. 9.21s | ",46ft/14,12,201622
|
||||||
|
-75.74,10.235,Isla del Tesoro,,2,Fl.W.,,1,W,6.6,0.5,12,20,,,,,,,,,"White tower, red bands.",16688 J6160,PUB 110,,"Fl.W. | period 6.6s | fl. 0.5s, ec. 6.1s | ",66ft/20,12,201501
|
||||||
|
-75.58095,10.339944,Isla Tierra Bomba,,2,Fl.W.,,1,W,12,1.2,26,112,,,,,,,,,"Red metal framework tower, white bands; 131.",16700 J6166,PUB 110,CARTAGENA:,"Fl.W. | period 12s | fl. 1.2s, ec. 10.8s | ",367ft/112,26,201626
|
||||||
|
-75.556081,10.281361,"Bajos de Coquitos, pier, E",,2,Fl.(4)W.,(4),1,W,9,,4,11,,,,,,,,,"Yellow post, ""X"" topmark.",16703 J6166.1,PUB 110,CARTAGENA:,Fl.(4)W. | period 9s | ,36ft/11,4,201812
|
||||||
|
-75.558944,10.280222,W,,2,Fl.(4)W.,(4),1,W,9,,4,11,,,,,,,,,"Yellow post, ""X"" topmark.",16703.5 J6166.2,PUB 110,CARTAGENA:,Fl.(4)W. | period 9s | ,36ft/11,4,201812
|
||||||
|
-75.598336,10.316667,RACON,,,C(- • - • ),,1,W,,,,,,,,,,,,(3 & 10cm). ,,16704,PUB 110,CARTAGENA:,C(- • - • ) | ,,,201622
|
||||||
|
-75.511664,10.317889,"Port Mamonal, Ecopetrol pier, N",,5,Q.Bu.,,5,Bu,,,,,,,,,,,,5 Q.Bu. shown along pier. ,,16705 J6166.73,PUB 110,CARTAGENA:,Q.Bu. | ,,,201831
|
||||||
|
-75.511389,10.316667,S,,5,Q.Bu.,,5,Bu,,,,,,,,,,,,,,16705.1 J6166.69,PUB 110,CARTAGENA:,Q.Bu. | ,,,201831
|
||||||
|
-75.508361,10.345056,Atunes de Colombia,,2,Fl.Bu.,,5,Bu,4,1.5,,,,,,,,,,,,16705.5 J6166.92,PUB 110,CARTAGENA:,"Fl.Bu. | period 4s | fl. 1.5s, ec. 2.5s | ",,,201831
|
||||||
|
-75.530556,10.388611,"2 ENAP, Range, front",,5,Q.Y.,,6,Y,,,10,18,,,,,,,,,"White tower, red bands; 30.",16725.5 J6167.65,PUB 110,CARTAGENA:,Q.Y. | ,59ft/18,10,201506
|
||||||
|
-75.530556,10.389444,"1 ENAP, rear, about 110 meters 000^ from front",,5,Q.Y.,,6,Y,,,10,22,,,,,,,,,"White tower, red bands; 16.",16725.51 J6167.66,PUB 110,CARTAGENA:,Q.Y. | ,72ft/22,10,201506
|
||||||
|
-75.544975,10.391222,Castillo Grande,,2,Fl.W.,,1,W,15,1.2,20,24,,,,,,,,"Beacons ""E1'' Fl G 3s 5M and ""E2'' Fl R 3s 4M mark small craft passage 1.5 miles W. ",Beige concrete tower; 72.,16726 J6167.60,PUB 110,,"Fl.W. | period 15s | fl. 1.2s, ec. 13.8s | ",79ft/24,20,201919
|
||||||
|
-75.546031,10.411472,Naval Base pier,,2,Fl.Bu.,,5,Bu,3.5,1.0,5,4,,,,,,,,4 Fl.Bu. 3.5s shown along pier. ,,16727 J6167.2,PUB 110,,"Fl.Bu. | period 3.5s | fl. 1.0s, ec. 2.5s | ",11ft/4,5,201831
|
||||||
|
-75.51585,10.446103,Crespo AVIATION LIGHT,,20,Al.Fl.W.G.,,"1,4","W,G",10,,20,,,,,,,,,,,16732 J6172,PUB 110,,Al.Fl.W.G. | period 10s | ,,20,201501
|
||||||
|
-75.499169,10.573056,Punta Canoas,,2,Fl.W.,,1,W,5,0.4,12,96,,,,,,,,,Red and white tower; 39.,16736 J6174,PUB 110,,"Fl.W. | period 5s | fl. 0.4s, ec. 4.6s | ",315ft/96,12,201722
|
||||||
|
-75.266389,10.785361,Punta Galera,,9,Oc.W.,,1,W,4,2.5,18,12,,,,,,,,,"White fiberglass tower, red bands.",16740 J6176,PUB 110,,"Oc.W. | period 4s | fl. 2.5s, ec. 1.5s | ",39ft/12,18,201625
|
||||||
|
-75.017525,10.963222,Punta Hermosa,,9,Oc.W.,,1,W,4,2.5,28,134,,,,,,,,,"White tower, red bands; 39.",16744 J6180,PUB 110,,"Oc.W. | period 4s | fl. 2.5s, ec. 1.5s | ",440ft/134,28,201506
|
||||||
|
-74.849525,11.10625,"F-1, E. breakwater, head",,8,Iso.G.,,4,G,2,,9,20,,,,,,,,,"White tower, orange bands.",16747 J6190,PUB 110,RIO MAGDALENA:,Iso.G. | period 2s | ,66ft/20,9,201926
|
||||||
|
-74.8547,11.106222,"F-2, W. breakwater, head",,8,Iso.R.,,3,R,2,,13,23,,,,,,,,,"White framework tower, orange bands.",16748 J6188,PUB 110,RIO MAGDALENA:,Iso.R. | period 2s | ,75ft/23,13,201708
|
||||||
|
-74.8547,11.106222,RACON,,,B(- • • • ),,1,W,,,,,,,,,,,,(3 & 10cm). ,,16748 J6188,PUB 110,RIO MAGDALENA:,B(- • • • ) | ,,,201708
|
||||||
|
-74.848275,11.103672,"E-1 Range, front, on E. breakwater, common front",,8,Iso.Bu.,,5,Bu,5,,13,10,,,,176.0,,,,Rear 16760. ,"Metal framework tower, white rectangular daymark, red stripe.",16756 J6191,PUB 110,RIO MAGDALENA:,Iso.Bu. | period 5s | ,33ft/10,13,201927
|
||||||
|
-74.848275,11.103672,,,8,Iso.W.,,1,W,4,,9,10,,,,,,,,Rear 16761. ,,16756 J6191,PUB 110,RIO MAGDALENA:,Iso.W. | period 4s | ,33ft/10,9,201927
|
||||||
|
-74.846439,11.101583,"E-3 Rear, 310 meters 139^18' from front",,8,Iso.Bu.,,5,Bu,5,,9,22,,,,176.0,,,,,"Orange and white framework tower, white rectangular daymark, red stripe.",16760 J6191.10,PUB 110,RIO MAGDALENA:,Iso.Bu. | period 5s | ,72ft/22,9,201506
|
||||||
|
-74.844969,11.100306,"E-3A Rear, 135^42' from front",,8,Iso.W.,,1,W,5,,12,20,,,,176.0,,,,,"Metal framework tower, white rectangular daymark, red stripe.",16761 J6191.2,PUB 110,RIO MAGDALENA:,Iso.W. | period 5s | ,66ft/20,12,201927
|
||||||
|
-74.8495,11.102194,X-1,,2,Fl.(4)G.,(4),4,G,11,0.5,6,8,,,,,,,,,"Red framework tower, white bands.",16763 J6193.5,PUB 110,RIO MAGDALENA:,"Fl.(4)G. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",26ft/8,6,201522
|
||||||
|
-74.85325,11.1,"X-2, W. side of river",,2,Fl.(4)R.,(4),3,R,11,0.5,6,6,,,,,,,,,"Red framework tower, white bands.",16764 J6194,PUB 110,RIO MAGDALENA:,"Fl.(4)R. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",20ft/6,6,201605
|
||||||
|
-74.847219,11.091389,X-3,,2,Fl.(4)G.,(4),4,G,11,0.5,6,8,,,,,,,,,"Red framework tower, white bands.",16766 J6201,PUB 110,,"Fl.(4)G. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",26ft/8,6,201522
|
||||||
|
-74.851775,11.093167,"X-4, W. side of river",,8,Iso.R.,,3,R,4,,4,8,,,,,,,,,"Orange framework tower, white bands.",16768 J6196,PUB 110,RIO MAGDALENA:,Iso.R. | period 4s | ,26ft/8,4,201506
|
||||||
|
-74.846811,11.070278,E-4,,8,Dir.Iso.W.,,1,W,4,,4,11,,,,141.0,,,,Visible on bearing 322°12`. ,"Orange metal tower, white bands.",16772 J6198,PUB 110,RIO MAGDALENA:,Dir.Iso.W. | period 4s | ,36ft/11,4,201506
|
||||||
|
-74.846811,11.070278,,,8,Iso.R.,,3,R,4,,,,,,,,,,,,,16772 J6198,PUB 110,RIO MAGDALENA:,Iso.R. | period 4s | ,,,201506
|
||||||
|
-74.846725,11.089222,X-5,,2,Fl.(4)G.,(4),4,G,11,0.5,6,8,,,,,,,,,"Red framework tower, white bands.",16776 J6200,PUB 110,RIO MAGDALENA:,"Fl.(4)G. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",26ft/8,6,201522
|
||||||
|
-74.850556,11.087722,"X-6, W. side of river",,2,Fl.(4)R.,(4),3,R,11,0.5,6,6,,,,,,,,,"White framework tower, red bands.",16780 J6202,PUB 110,RIO MAGDALENA:,"Fl.(4)R. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",20ft/6,6,202007
|
||||||
|
-74.844189,11.064889,"E-6 Range, front",,8,Iso.Bu.,,5,Bu,2,,11,11,,,,167.7,,,,Visible 166°-170°. ,"Orangee framework tower, white bands, red rectangular daymark, white stripes.",16784 J6191.90,PUB 110,RIO MAGDALENA:,Iso.Bu. | period 2s | ,36ft/11,11,201506
|
||||||
|
-74.842833,11.058694,"E-8 Rear, 695 meters 167^42' from front",,8,Iso.Bu.,,5,Bu,4,,15,25,,,,167.7,,,,Visible on range line only. ,"Red and white framework tower, white rectangular daymark, red stripe.",16788 J6191.91,PUB 110,RIO MAGDALENA:,Iso.Bu. | period 4s | ,82ft/25,15,201927
|
||||||
|
-74.836769,11.053728,"E-16 Range, front",,8,Iso.Bu.,,5,Bu,6,,9,12,,,,176.0,,,,,Orange and white framework tower.,16790 J6193,PUB 110,RIO MAGDALENA:,Iso.Bu. | period 6s | ,39ft/12,9,201501
|
||||||
|
-74.840719,11.056147,"E-14 Rear, 512 meters 302^ from front",,8,Iso.Bu.,,5,Bu,6,,9,24,,,,176.0,,,,,Tower.,16790.5 J6192.10,PUB 110,RIO MAGDALENA:,Iso.Bu. | period 6s | ,79ft/24,9,201501
|
||||||
|
-74.849169,11.081833,"X-8, W. side of river",,2,Fl.R.,,3,R,4,1.0,4,8,,,,,,,,,"Orange framework tower, white bands.",16792 J6204,PUB 110,RIO MAGDALENA:,"Fl.R. | period 4s | fl. 1.0s, ec. 3.0s | ",26ft/8,4,201506
|
||||||
|
-74.848,11.076083,"X-10, W. side of river, below Las Flores",,2,Fl.(4)R.,(4),3,R,11,0.5,6,6,,,,,,,,,"White framework tower, red bands.",16804 J6206,PUB 110,RIO MAGDALENA:,"Fl.(4)R. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",20ft/6,6,202007
|
||||||
|
-74.825561,11.043097,"E-18 Range, front",,8,Dir.Iso.W.R.G.,,"1,3,4","W,R,G",4,,8,13,,,,176.0,,,,"R. 137°30`-141°30`, W.-142°30`, G.-146°30`. ","White metal framework tower, red bands.",16806 J6216,PUB 110,RIO MAGDALENA:,Dir.Iso.W.R.G. | period 4s | ,43ft/13,8,201927
|
||||||
|
-74.822556,11.03925,"E-20 Rear, 522 meters 142^ 12' from front",,8,Iso.Bu.,,5,Bu,4,,15,30,,,,142.2,,,,,"White and orange metal framework tower, white rectangular daymark, orange stripe.",16807 J6218,PUB 110,RIO MAGDALENA:,Iso.Bu. | period 4s | ,98ft/30,15,201508
|
||||||
|
-74.844189,11.064917,X-12,,2,Fl.(4)R.,(4),3,R,11,0.5,6,6,,,,,,,,,"Red framework tower, white bands.",16808 J6207,PUB 110,RIO MAGDALENA:,"Fl.(4)R. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",20ft/6,6,201605
|
||||||
|
-74.839614,11.057806,X-14,,2,Fl.(4)R.,(4),3,R,11,0.5,6,6,,,,,,,,,"White framework tower, red bands.",16810 J6208,PUB 110,RIO MAGDALENA:,"Fl.(4)R. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",20ft/6,6,201927
|
||||||
|
-74.814414,11.041583,X-7,,2,Fl.(4)G,(4),4,G,11,0.5,6,8,,,,,,,,,"Red framework tower, white bands.",16811 J6222.5,PUB 110,RIO MAGDALENA:,"Fl.(4)G | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",26ft/8,6,201522
|
||||||
|
-74.832919,11.05,X-16,,2,Fl.(3)R.,(3),3,R,9,0.8,4,8,,,,,,,,,"Orange framework tower, white bands.",16812 J6214,PUB 110,RIO MAGDALENA:,"Fl.(3)R. | period 9s | fl. 0.8s, ec. 1.2s | fl. 0.8s, ec. 1.2s | fl. 0.8s, ec. 4.2s | ",26ft/8,4,201506
|
||||||
|
-74.804886,11.035889,X-9,,2,Fl.(4)G.,(4),4,G,11,0.5,6,8,,,,,,,,,"Red framework tower, white bands.",16813 J6222.8,PUB 110,RIO MAGDALENA:,"Fl.(4)G. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",26ft/8,6,201522
|
||||||
|
-74.7955,11.03,X-11,,2,Fl.(4)G.,(4),4,G,11,0.5,6,8,,,,,,,,,"Red framework tower, white bands, red and white rectangular daymark.",16813.5 J6223,PUB 110,RIO MAGDALENA:,"Fl.(4)G. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",26ft/8,6,201519
|
||||||
|
-74.7898,11.026,X-13,,2,Fl.(4)G.,(4),4,G,11,0.5,6,8,,,,,,,,,"Red framework tower, white bands, red and white rectangular daymark.",16814 J6226,PUB 110,RIO MAGDALENA:,"Fl.(4)G. | period 11s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 1.5s | fl. 0.5s, ec. 4.5s | ",26ft/8,6,201519
|
||||||
|
-74.785669,11.022944,X-15,,5,Q.(3)Y.,(3),6,Y,5,,4,8,,,,,,,,,"Orange framework tower, white bands.",16815 J6229,PUB 110,RIO MAGDALENA:,Q.(3)Y. | period 5s | ,26ft/8,4,201506
|
||||||
|
-74.766811,11.010222,E-7,,8,Dir.Iso.W.R.G.,,"1,3,4","W,R,G",5,,8,16,,,,120.0,,,,"R. 118°30`-121°30`, W.-122°30`, G.-125°30` ",Red and white tower; 76.,16818 J6229.51,PUB 110,RIO MAGDALENA:,Dir.Iso.W.R.G. | period 5s | ,52ft/16,8,201918
|
||||||
|
-74.778219,11.018667,X-17,,5,Q.(3)Y.,(3),6,Y,5,,4,8,,,,,,,,,"Orange framework tower, white bands.",16819 J6229.2,PUB 110,RIO MAGDALENA:,Q.(3)Y. | period 5s | ,26ft/8,4,201506
|
||||||
|
-74.236664,11.071111,"Coal loading jetty, SW corner",,2,Fl.R.,,3,R,,,,,,,,,,,,,,16821 J6236,PUB 110,,Fl.R. | ,,,201433
|
||||||
|
-74.236947,11.071111,NE corner,,2,Fl.R.,,3,R,,,,,,,,,,,,,,16822 J6236.1,PUB 110,,Fl.R. | ,,,201433
|
||||||
|
-74.233311,11.1165,Simon Bolivar AVIATION LIGHT,,20,Al.Fl.W.G.,,"1,4","W,G",10,,,,,,,,,,,,Tower.,16828 J6254,PUB 110,,Al.Fl.W.G. | period 10s | ,,,201506
|
||||||
|
-74.253333,11.153333,RACON,,,C(- • - • ),,1,W,,,,,,,,,,,,(3 & 10cm). ,,16831 J6255.30,PUB 110,,C(- • - • ) | ,,,201506
|
||||||
|
-74.230581,11.25,Morro Grande,,2,Fl.W.,,1,W,15,1.0,22,85,,,,,,,,Obscured beyond Aguja Island 203°-212°. ,White and gray concrete tower; 76.,16832 J6256,PUB 110,BAHIA DE SANTA MARTA:,"Fl.W. | period 15s | fl. 1.0s, ec. 14.0s | ",279ft/85,22,201506
|
||||||
|
-74.230581,11.25,RACON,,,S(• • • ),,1,W,,,,,,,,,,,,(3 & 10cm). ,,16832 J6256,PUB 110,BAHIA DE SANTA MARTA:,S(• • • ) | ,,,201506
|
||||||
|
+425
@@ -0,0 +1,425 @@
|
|||||||
|
"""
|
||||||
|
nga_fetch.py — Descarga ayudas a la navegacion del API NGA MSI (Pub. 110)
|
||||||
|
y genera CSVs listos para importar en QGIS, uno por tipo de objeto S-57.
|
||||||
|
|
||||||
|
Uso:
|
||||||
|
python -X utf8 nga_fetch.py
|
||||||
|
python -X utf8 nga_fetch.py --lat0 10.0 --lat1 12.0 --lon0 -76.5 --lon1 -73.5
|
||||||
|
"""
|
||||||
|
|
||||||
|
import csv, json, re, ssl, sys, urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ── Bounding box por defecto: Costa Caribe colombiana ───────────────────────
|
||||||
|
DEFAULT_LAT0, DEFAULT_LON0 = 9.5, -77.0
|
||||||
|
DEFAULT_LAT1, DEFAULT_LON1 = 11.8, -73.5
|
||||||
|
|
||||||
|
OUT_DIR = Path(__file__).parent
|
||||||
|
|
||||||
|
# ── Headers NGA (requiere origen mismo dominio) ──────────────────────────────
|
||||||
|
NGA_HEADERS = {
|
||||||
|
"User-Agent": (
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
"Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
),
|
||||||
|
"Referer": "https://msi.nga.mil/Publications/NGALOL",
|
||||||
|
"Origin": "https://msi.nga.mil",
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "en-US,en;q=0.9",
|
||||||
|
"sec-fetch-site": "same-origin",
|
||||||
|
"sec-fetch-mode": "cors",
|
||||||
|
}
|
||||||
|
|
||||||
|
NGA_URL = (
|
||||||
|
"https://msi.nga.mil/api/publications/ngalol/lights-buoys"
|
||||||
|
"?latitudeLeft={lat0}&longitudeLeft={lon0}"
|
||||||
|
"&latitudeRight={lat1}&longitudeRight={lon1}"
|
||||||
|
"&includeRemovals=false&output=json"
|
||||||
|
)
|
||||||
|
|
||||||
|
_SSL_CTX = ssl.create_default_context()
|
||||||
|
_SSL_CTX.check_hostname = False
|
||||||
|
_SSL_CTX.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
# ── Rumbos conocidos de enfilaciones Bocas de Ceniza / Rio Magdalena ─────────
|
||||||
|
# Fuente: NGA Pub.110 Vol.D (Colombia) + geometria del canal DIMAR.
|
||||||
|
# E-1 y E-3: canal exterior (barra), E-6/E-8: primer tramo, etc.
|
||||||
|
# ATENCION: valores aproximados +/-1 grado. Verificar contra DIMAR Lista Faros.
|
||||||
|
KNOWN_ORIENT = {
|
||||||
|
'E-1': '176.0', # Bocas de Ceniza, barra exterior
|
||||||
|
'E-3': '176.0', # rear de E-1
|
||||||
|
'E-3A': '135.6', # ramal alternativo
|
||||||
|
'E-6': '167.7', # primer codo (visible 166-170 segun NGA)
|
||||||
|
'E-8': '167.7', # rear de E-6
|
||||||
|
'E-16': '122.0', # codo hacia Barranquilla
|
||||||
|
'E-14': '122.0', # rear de E-16 (302-122 opuesto)
|
||||||
|
'E-18': '142.2', # Dir luz W/R/G (sector W: 141.5-142.5)
|
||||||
|
'E-20': '142.2', # rear de E-18
|
||||||
|
'E-4': '141.0', # Dir Iso W (interior)
|
||||||
|
'E-7': '120.0', # Dir Iso W/R/G sector W 118.5-121.5
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Conversion DMS -> decimal ─────────────────────────────────────────────────
|
||||||
|
_DMS = re.compile(r'(\d+)[deg°º]\s*(\d+)[min′\'"]\s*([\d.]+)[sec"″]?\s*([NSEW])', re.I)
|
||||||
|
_DMS2 = re.compile(r'(\d+)[°º]\s*([\d.]+)[′\'"]\s*([NSEW])', re.I)
|
||||||
|
|
||||||
|
def dms_dec(txt: str) -> float | None:
|
||||||
|
txt = txt.replace('\n', ' ').replace('\\u00b0', '°')
|
||||||
|
m = _DMS.search(txt)
|
||||||
|
if m:
|
||||||
|
d, mi, s, h = int(m[1]), int(m[2]), float(m[3]), m[4].upper()
|
||||||
|
v = d + mi/60 + s/3600
|
||||||
|
return round(-v if h in ('S','W') else v, 6)
|
||||||
|
m = _DMS2.search(txt)
|
||||||
|
if m:
|
||||||
|
d, mi, h = int(m[1]), float(m[2]), m[3].upper()
|
||||||
|
v = d + mi/60
|
||||||
|
return round(-v if h in ('S','W') else v, 6)
|
||||||
|
# Unicode grado (JSON de NGA viene con °)
|
||||||
|
m2 = re.search(r'(\d+)°(\d+)\'([\d.]+)"([NSEW])', txt)
|
||||||
|
if m2:
|
||||||
|
d, mi, s, h = int(m2[1]), int(m2[2]), float(m2[3]), m2[4].upper()
|
||||||
|
v = d + mi/60 + s/3600
|
||||||
|
return round(-v if h in ('S','W') else v, 6)
|
||||||
|
nums = re.findall(r'[-\d.]+', txt)
|
||||||
|
return float(nums[0]) if nums else None
|
||||||
|
|
||||||
|
def parse_pos(pos: str):
|
||||||
|
parts = re.split(r'\n', pos)
|
||||||
|
if len(parts) < 2:
|
||||||
|
return None, None
|
||||||
|
return dms_dec(parts[0]), dms_dec(parts[1])
|
||||||
|
|
||||||
|
# ── Tablas S-57 ───────────────────────────────────────────────────────────────
|
||||||
|
LITCHR_FIXED = {
|
||||||
|
'F':1, 'Fl':2, 'LFl':4, 'Q':5, 'VQ':6, 'UQ':7,
|
||||||
|
'Iso':8, 'Oc':9, 'IQ':10, 'Mo':13, 'FFl':14,
|
||||||
|
'Al.Oc':18, 'Al.LFl':19, 'Al.Fl':20, 'Dir':28,
|
||||||
|
'Dir.Iso':8, 'Dir.Fl':2, 'Dir.Oc':9, 'Dir.LFl':4,
|
||||||
|
'Q+LFl':25, 'VQ+LFl':26,
|
||||||
|
}
|
||||||
|
|
||||||
|
_COLORES = {'Bu':5, 'Or':11, 'Am':6, 'Vi':7, 'W':1, 'R':3, 'G':4, 'Y':6, 'B':2}
|
||||||
|
|
||||||
|
COLOUR_TXT_MAP = {
|
||||||
|
'1':'W','2':'B','3':'R','4':'G','5':'Bu','6':'Y','7':'Vi','11':'Or',
|
||||||
|
}
|
||||||
|
|
||||||
|
_TIPOS = [
|
||||||
|
'Al.Fl','Al.Oc','Al.LFl','Dir.Iso','Dir.Fl','Dir.Oc','Dir.LFl',
|
||||||
|
'VQ+LFl','Q+LFl','FFl','LFl','VQ','UQ','IQ','Mo','Iso','Oc','Fl','Q','F',
|
||||||
|
]
|
||||||
|
|
||||||
|
# ── Parser de tokens de caracteristica ───────────────────────────────────────
|
||||||
|
def _tokenize(s: str) -> list:
|
||||||
|
"""'Fl.(4)W.R.' -> ['Fl','(4)','W','R'] sin puntos separadores."""
|
||||||
|
tokens, cur, depth = [], '', 0
|
||||||
|
for ch in s.rstrip('.'):
|
||||||
|
if ch == '(':
|
||||||
|
depth += 1; cur += ch
|
||||||
|
elif ch == ')':
|
||||||
|
depth -= 1; cur += ch
|
||||||
|
if depth == 0:
|
||||||
|
tokens.append(cur); cur = ''
|
||||||
|
elif ch == '.' and depth == 0:
|
||||||
|
if cur: tokens.append(cur); cur = ''
|
||||||
|
else:
|
||||||
|
cur += ch
|
||||||
|
if cur: tokens.append(cur)
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
_PER_RE = re.compile(r'period\s+([\d.]+)\s*s', re.I)
|
||||||
|
_FL_RE = re.compile(r'\bfl\.\s*([\d.]+)\s*s', re.I)
|
||||||
|
_SEC_RE = re.compile(
|
||||||
|
r'([WRGBY])\.\s*(\d+[°º]\d+[\'`])\s*[–-]\s*(\d+[°º]\d+[\'`])', re.I
|
||||||
|
)
|
||||||
|
_VIS_RE = re.compile(r'[Vv]isible\s+([\d.]+)[°º]\s*[–-]\s*([\d.]+)[°º]')
|
||||||
|
_BRG_RE = re.compile(r'(\d{2,3})\^([\d.]*)', re.I) # "139^18'" formato NGA
|
||||||
|
|
||||||
|
def _deg_min(txt: str) -> str:
|
||||||
|
txt = txt.strip().replace('`',"'")
|
||||||
|
m = re.match(r'(\d+)[°º](\d+)', txt)
|
||||||
|
if m: return str(round(int(m[1]) + int(m[2])/60, 2))
|
||||||
|
nums = re.findall(r'[\d.]+', txt)
|
||||||
|
return nums[0] if nums else txt
|
||||||
|
|
||||||
|
def parse_char(char_str: str, remarks: str = '', name: str = '') -> dict:
|
||||||
|
"""Extrae todos los campos S-57 de la caracteristica + remarks del NGA."""
|
||||||
|
out = {
|
||||||
|
'LITCHR':'', 'SIGGRP':'', 'COLOUR':'', 'SIGPER':'',
|
||||||
|
'SIGPER2':'', 'SECTR1':'', 'SECTR2':'', 'MLTYLT':'', 'ORIENT':'',
|
||||||
|
}
|
||||||
|
if not char_str:
|
||||||
|
return out
|
||||||
|
|
||||||
|
lines = char_str.split('\n')
|
||||||
|
base = lines[0].strip()
|
||||||
|
full = ' '.join(lines)
|
||||||
|
|
||||||
|
tokens = _tokenize(base)
|
||||||
|
remaining = list(tokens)
|
||||||
|
|
||||||
|
# 1) Tipo de luz (compuestos primero)
|
||||||
|
tipo_found = ''
|
||||||
|
for tipo in _TIPOS:
|
||||||
|
tp_tokens = tipo.split('.')
|
||||||
|
if remaining[:len(tp_tokens)] == tp_tokens:
|
||||||
|
tipo_found = tipo
|
||||||
|
remaining = remaining[len(tp_tokens):]
|
||||||
|
break
|
||||||
|
if tipo_found:
|
||||||
|
out['LITCHR'] = LITCHR_FIXED.get(tipo_found, tipo_found)
|
||||||
|
|
||||||
|
# 2) Grupo opcional "(N)" o "(N+M)"
|
||||||
|
if remaining and remaining[0].startswith('('):
|
||||||
|
out['SIGGRP'] = remaining.pop(0)
|
||||||
|
|
||||||
|
# 3) Colores: tokens de solo letras
|
||||||
|
colours = []
|
||||||
|
for t in remaining:
|
||||||
|
if re.match(r'^[A-Za-z]+$', t):
|
||||||
|
# 2 letras primero, luego 1
|
||||||
|
c = _COLORES.get(t) or _COLORES.get(t[:2]) or _COLORES.get(t[:1])
|
||||||
|
if c: colours.append(str(c))
|
||||||
|
out['COLOUR'] = ','.join(colours) if colours else '1'
|
||||||
|
|
||||||
|
# 4) Periodo
|
||||||
|
pm = _PER_RE.search(full)
|
||||||
|
if pm: out['SIGPER'] = pm.group(1)
|
||||||
|
|
||||||
|
# 5) Duracion destello
|
||||||
|
fm = _FL_RE.search(full)
|
||||||
|
if fm: out['SIGPER2'] = fm.group(1)
|
||||||
|
|
||||||
|
# 6) Sectores y orientacion desde remarks
|
||||||
|
rem = (remarks or '').replace('`',"'").replace('',"'")
|
||||||
|
secs = _SEC_RE.findall(rem)
|
||||||
|
if secs:
|
||||||
|
lst = [f"{c.upper()}:{_deg_min(s1)}-{_deg_min(s2)}"
|
||||||
|
for c, s1, s2 in secs]
|
||||||
|
out['MLTYLT'] = ' | '.join(lst)
|
||||||
|
_, s1, s2 = secs[0]
|
||||||
|
out['SECTR1'] = _deg_min(s1)
|
||||||
|
out['SECTR2'] = _deg_min(s2)
|
||||||
|
|
||||||
|
if not out['SECTR1']:
|
||||||
|
vm = _VIS_RE.search(rem + ' ' + full)
|
||||||
|
if vm:
|
||||||
|
out['SECTR1'] = vm.group(1)
|
||||||
|
out['SECTR2'] = vm.group(2)
|
||||||
|
|
||||||
|
# 7) Rumbo de enfilacion desde formato NGA "310 meters 139^18' from front"
|
||||||
|
bm = _BRG_RE.search(rem + ' ' + full)
|
||||||
|
if bm:
|
||||||
|
deg = int(bm.group(1))
|
||||||
|
frac = bm.group(2)
|
||||||
|
if frac:
|
||||||
|
deg_dec = round(deg + float(frac)/60, 1)
|
||||||
|
else:
|
||||||
|
deg_dec = float(deg)
|
||||||
|
# El rumbo NGA es desde el frente al trasero -> invertir para ORIENT
|
||||||
|
# (ORIENT = rumbo inbound = desde el mar hacia el muelle)
|
||||||
|
orient = round((deg_dec + 180) % 360, 1)
|
||||||
|
out['ORIENT'] = str(orient)
|
||||||
|
|
||||||
|
# 8) Rumbo conocido de tabla local (mas preciso que calculo)
|
||||||
|
# Buscar el ID de la enfilacion en el nombre
|
||||||
|
for key, val in KNOWN_ORIENT.items():
|
||||||
|
if key in name:
|
||||||
|
out['ORIENT'] = val
|
||||||
|
# Si el sector visible es estrecho y ORIENT conocido, derivar sectores
|
||||||
|
if not out['SECTR1'] and out['LITCHR'] == 28: # Dir light
|
||||||
|
ang = float(val)
|
||||||
|
out['SECTR1'] = str(round(ang - 1.5, 1))
|
||||||
|
out['SECTR2'] = str(round(ang + 1.5, 1))
|
||||||
|
break
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
# ── Altura en metros desde "heightFeetMeters" ───────────────────────────────
|
||||||
|
def parse_height(hfm: str) -> str:
|
||||||
|
if not hfm: return ''
|
||||||
|
parts = hfm.strip().split('\n')
|
||||||
|
if len(parts) >= 2 and parts[1].strip():
|
||||||
|
return parts[1].strip()
|
||||||
|
nums = re.findall(r'[\d.]+', parts[0])
|
||||||
|
return nums[0] if nums else ''
|
||||||
|
|
||||||
|
# ── Clasificacion S-57 ───────────────────────────────────────────────────────
|
||||||
|
def classify(rec: dict) -> str:
|
||||||
|
name = (rec.get('name') or '').lower()
|
||||||
|
struc = (rec.get('structure') or '').lower()
|
||||||
|
aid = (rec.get('aidType') or '').lower()
|
||||||
|
if 'buoy' in struc or 'buoy' in aid:
|
||||||
|
if any(w in name+struc for w in ['north','south','east','west','cardinal']):
|
||||||
|
return 'BOYCAR'
|
||||||
|
if any(w in name+struc for w in ['safe water','fairway','spherical']):
|
||||||
|
return 'BOYSAW'
|
||||||
|
return 'BOYLAT'
|
||||||
|
if 'beacon' in struc or 'beacon' in aid:
|
||||||
|
return 'BCNLAT'
|
||||||
|
return 'LIGHTS'
|
||||||
|
|
||||||
|
# ── Fetch ────────────────────────────────────────────────────────────────────
|
||||||
|
def fetch(lat0, lon0, lat1, lon1) -> list:
|
||||||
|
url = NGA_URL.format(lat0=lat0, lon0=lon0, lat1=lat1, lon1=lon1)
|
||||||
|
print(f"Consultando NGA MSI...\n{url}\n")
|
||||||
|
req = urllib.request.Request(url, headers=NGA_HEADERS)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=25, context=_SSL_CTX) as r:
|
||||||
|
data = json.loads(r.read().decode('utf-8'))
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print(f"Error HTTP {e.code}: {e.reason}"); sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error conexion: {e}"); sys.exit(1)
|
||||||
|
recs = data.get('ngalol') or []
|
||||||
|
print(f" -> {len(recs)} registros recibidos")
|
||||||
|
return recs
|
||||||
|
|
||||||
|
# ── Campos CSV (S-57 completo) ───────────────────────────────────────────────
|
||||||
|
FIELDS = [
|
||||||
|
'lon','lat',
|
||||||
|
'OBJNAM','NOBJNM',
|
||||||
|
# Luz
|
||||||
|
'LITCHR','LITCHR_TXT',
|
||||||
|
'SIGGRP',
|
||||||
|
'COLOUR','COLOUR_TXT',
|
||||||
|
'SIGPER','SIGPER2',
|
||||||
|
'VALNMR',
|
||||||
|
'HEIGHT',
|
||||||
|
# Sectores / orientacion
|
||||||
|
'SECTR1','SECTR2',
|
||||||
|
'MLTYLT',
|
||||||
|
'ORIENT',
|
||||||
|
# Boya
|
||||||
|
'BOYSHP','CATLBR','CATCAM',
|
||||||
|
# Texto
|
||||||
|
'INFORM','TXTDSC',
|
||||||
|
# Referencia NGA
|
||||||
|
'_nga_feature','_nga_volume','_nga_region',
|
||||||
|
'_nga_char_raw','_nga_hfm_raw','_nga_range_raw','_nga_notice',
|
||||||
|
]
|
||||||
|
|
||||||
|
def write_csv(path: Path, rows: list):
|
||||||
|
with open(path, 'w', newline='', encoding='utf-8-sig') as f:
|
||||||
|
w = csv.DictWriter(f, fieldnames=FIELDS, extrasaction='ignore')
|
||||||
|
w.writeheader()
|
||||||
|
w.writerows(rows)
|
||||||
|
print(f" OK {path.name} ({len(rows)} registros)")
|
||||||
|
|
||||||
|
# ── Main ─────────────────────────────────────────────────────────────────────
|
||||||
|
def main():
|
||||||
|
lat0, lon0, lat1, lon1 = DEFAULT_LAT0, DEFAULT_LON0, DEFAULT_LAT1, DEFAULT_LON1
|
||||||
|
args = sys.argv[1:]
|
||||||
|
for i, a in enumerate(args):
|
||||||
|
if a=='--lat0' and i+1<len(args): lat0=float(args[i+1])
|
||||||
|
if a=='--lat1' and i+1<len(args): lat1=float(args[i+1])
|
||||||
|
if a=='--lon0' and i+1<len(args): lon0=float(args[i+1])
|
||||||
|
if a=='--lon1' and i+1<len(args): lon1=float(args[i+1])
|
||||||
|
|
||||||
|
records = fetch(lat0, lon0, lat1, lon1)
|
||||||
|
|
||||||
|
buckets = {
|
||||||
|
'LIGHTS':[], 'BOYLAT':[], 'BOYCAR':[],
|
||||||
|
'BOYSAW':[], 'BCNLAT':[], 'OTHER':[],
|
||||||
|
}
|
||||||
|
|
||||||
|
for rec in records:
|
||||||
|
lat, lon = parse_pos(rec.get('position',''))
|
||||||
|
if lat is None: continue
|
||||||
|
|
||||||
|
char_raw = rec.get('characteristic','')
|
||||||
|
remarks = rec.get('remarks','') or ''
|
||||||
|
name_raw = (rec.get('name') or '').strip().lstrip('-').strip().rstrip('.')
|
||||||
|
|
||||||
|
cp = parse_char(char_raw, remarks, name_raw)
|
||||||
|
height = parse_height(rec.get('heightFeetMeters',''))
|
||||||
|
valnmr = (rec.get('range') or '').strip()
|
||||||
|
s57t = classify(rec)
|
||||||
|
|
||||||
|
col_txt = ','.join(
|
||||||
|
COLOUR_TXT_MAP.get(c.strip(), c.strip())
|
||||||
|
for c in cp['COLOUR'].split(',') if c.strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
row = {
|
||||||
|
'lon': lon,
|
||||||
|
'lat': lat,
|
||||||
|
'OBJNAM': name_raw,
|
||||||
|
'NOBJNM': '',
|
||||||
|
'LITCHR': cp['LITCHR'],
|
||||||
|
'LITCHR_TXT': char_raw.split('\n')[0].strip(),
|
||||||
|
'SIGGRP': cp['SIGGRP'],
|
||||||
|
'COLOUR': cp['COLOUR'],
|
||||||
|
'COLOUR_TXT': col_txt,
|
||||||
|
'SIGPER': cp['SIGPER'],
|
||||||
|
'SIGPER2': cp['SIGPER2'],
|
||||||
|
'VALNMR': valnmr,
|
||||||
|
'HEIGHT': height,
|
||||||
|
'SECTR1': cp['SECTR1'],
|
||||||
|
'SECTR2': cp['SECTR2'],
|
||||||
|
'MLTYLT': cp['MLTYLT'],
|
||||||
|
'ORIENT': cp['ORIENT'],
|
||||||
|
'BOYSHP': '',
|
||||||
|
'CATLBR': '',
|
||||||
|
'CATCAM': '',
|
||||||
|
'INFORM': remarks.replace('\n',' '),
|
||||||
|
'TXTDSC': (rec.get('structure') or '').replace('\n',' ').strip(),
|
||||||
|
'_nga_feature': (rec.get('featureNumber') or '').replace('\n',' '),
|
||||||
|
'_nga_volume': rec.get('volumeNumber',''),
|
||||||
|
'_nga_region': (rec.get('regionHeading') or ''),
|
||||||
|
'_nga_char_raw': char_raw.replace('\n',' | '),
|
||||||
|
'_nga_hfm_raw': (rec.get('heightFeetMeters') or '').replace('\n','ft/'),
|
||||||
|
'_nga_range_raw': valnmr,
|
||||||
|
'_nga_notice': str(rec.get('noticeNumber','')),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clasificacion detallada boyas
|
||||||
|
struc_low = (rec.get('structure') or '').lower()
|
||||||
|
if s57t == 'BOYLAT':
|
||||||
|
if any(w in struc_low for w in ['red','port','can']):
|
||||||
|
row.update({'CATLBR':'1','COLOUR':'3','COLOUR_TXT':'R','BOYSHP':'2'})
|
||||||
|
elif any(w in struc_low for w in ['green','starboard','conical']):
|
||||||
|
row.update({'CATLBR':'2','COLOUR':'4','COLOUR_TXT':'G','BOYSHP':'1'})
|
||||||
|
elif s57t == 'BOYCAR':
|
||||||
|
nm = name_raw.lower()
|
||||||
|
if 'north' in nm: row['CATCAM']='1'
|
||||||
|
elif 'east' in nm: row['CATCAM']='2'
|
||||||
|
elif 'south' in nm: row['CATCAM']='3'
|
||||||
|
elif 'west' in nm: row['CATCAM']='4'
|
||||||
|
row.update({'COLOUR':'6,1','COLOUR_TXT':'Y,W'})
|
||||||
|
elif s57t == 'BOYSAW':
|
||||||
|
row.update({'COLOUR':'1,3','COLOUR_TXT':'W,R','BOYSHP':'3'})
|
||||||
|
|
||||||
|
buckets[s57t if s57t in buckets else 'OTHER'].append(row)
|
||||||
|
|
||||||
|
# Resumen
|
||||||
|
print("\nResumen por tipo S-57:")
|
||||||
|
for k,v in buckets.items():
|
||||||
|
if v: print(f" {k:<10} {len(v):>3} registros")
|
||||||
|
|
||||||
|
# Escribir CSVs
|
||||||
|
print(f"\nEscribiendo CSVs en: {OUT_DIR}\n")
|
||||||
|
ts = datetime.now().strftime('%Y%m%d')
|
||||||
|
for k, rows in buckets.items():
|
||||||
|
if rows:
|
||||||
|
write_csv(OUT_DIR / f'nga_{k}_{ts}.csv', rows)
|
||||||
|
|
||||||
|
# Preview tabla completa
|
||||||
|
print(f"\n{'OBJNAM':<45} {'CHAR':<15} {'COL':<8} {'PER':>5} {'RNG':>4} {'HGT':>4} {'ORI':>6} {'SEC':}")
|
||||||
|
print('-'*115)
|
||||||
|
for r in buckets['LIGHTS']:
|
||||||
|
sec = f"{r['SECTR1']}-{r['SECTR2']}" if r['SECTR1'] else ''
|
||||||
|
print(f"{r['OBJNAM'][:44]:<45} {r['LITCHR_TXT'][:14]:<15} "
|
||||||
|
f"{r['COLOUR_TXT']:<8} {r['SIGPER']:>5}s {r['VALNMR']:>4}NM "
|
||||||
|
f"{r['HEIGHT']:>4}m {r['ORIENT']:>6} {sec}")
|
||||||
|
|
||||||
|
print("\nListo. En QGIS: Capa -> Anadir capa -> Texto delimitado")
|
||||||
|
print("X=lon Y=lat CRS=EPSG:4326 Renombre capa con codigo S-57")
|
||||||
|
print("ATENCION: ORIENT de enfilaciones son aproximados.")
|
||||||
|
print("Verificar contra DIMAR Lista de Faros antes de publicar carta.")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
015823LE1 0900201 ! 34040000123000000010470123DSID1650170DSSI1130335DSPM1300448VRID0780578ATTV0580656VRPT0760714SG2D0480790SG3D0700838FRID1000908FOID0701008ATTF0591078NATF0681137FFPT0861205FSPT09012910000;& 0001DSIDDSIDDSSI0001DSPM0001VRIDVRIDATTVVRIDVRPTVRIDSG2DVRIDSG3D0001FRIDFRIDFOIDFRIDATTFFRIDNATFFRIDFFPTFRIDFSPT0500;& ISO/IEC 8211 Record Identifier(b12)1600;& Data set identification fieldRCNM!RCID!EXPP!INTU!DSNM!EDTN!UPDN!UADT!ISDT!STED!PRSP!PSDN!PRED!PROF!AGEN!COMT(b11,b14,2b11,3A,2A(8),R(4),b11,2A,b11,b12,A)1600;& Data set structure information fieldDSTR!AALL!NALL!NOMR!NOCR!NOGR!NOLR!NOIN!NOCN!NOED!NOFA(3b11,8b14)1600;& Data set parameter fieldRCNM!RCID!HDAT!VDAT!SDAT!CSCL!DUNI!HUNI!PUNI!COUN!COMF!SOMF!COMT(b11,b14,3b11,b14,4b11,2b14,A)1600;& Vector record identifier fieldRCNM!RCID!RVER!RUIN(b11,b14,b12,b11)2600;& Vector record attribute field*ATTL!ATVL(b12,A)2600;& Vector record pointer field*NAME!ORNT!USAG!TOPI!MASK(B(40),4b11)2500;& 2-D coordinate field*YCOO!XCOO(2b24)2500;& 3-D coordinate (sounding array) field*YCOO!XCOO!VE3D(3b24)1600;& Feature record identifier fieldRCNM!RCID!PRIM!GRUP!OBJL!RVER!RUIN(b11,b14,2b11,2b12,b11)1600;& Feature object identifier fieldAGEN!FIDN!FIDS(b12,b14,b12)2600;&-A Feature record attribute field*ATTL!ATVL(b12,A)2600;&-A Feature record national attribute field*ATTL!ATVL(b12,A)2600;& Feature record to feature object pointer field*LNAM!RIND!COMT(B(64),b11,A)2600;& Feature record to spatial record pointer field*NAME!ORNT!USAG!MASK(B(40),3b11)
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
"""
|
||||||
|
parse_lista_luces.py
|
||||||
|
Genera CSV de ayudas a la navegacion de Barranquilla a partir de
|
||||||
|
Lista de Luces DIMAR 2015 (ya parseada manualmente).
|
||||||
|
"""
|
||||||
|
import csv, re, sys, os
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
def dms_to_dd(deg, minutes):
|
||||||
|
return float(deg) + float(minutes) / 60.0
|
||||||
|
|
||||||
|
# ---- DATA ----
|
||||||
|
# (no, name, lat_deg, lat_min, lon_deg, lon_min, char, height_m, range_nm, colour, description, feat_type, orient)
|
||||||
|
records = [
|
||||||
|
# FAROS MAYORES
|
||||||
|
(13, 'Faro F1 Recalada', 11, 6.37, 74, 50.97, 'Iso G 2s', 20, 9, 'G', 'Torre naranja bandas blancas. Faro de Recalada', 'LIGHTS', ''),
|
||||||
|
(14, 'Faro F2 Recalada', 11, 6.36, 74, 51.28, 'Iso R 2s', 23, 13.4, 'R', 'Torre naranja bandas blancas. Racon B', 'LIGHTS', ''),
|
||||||
|
(32, 'Faro Morro Hermoso', 10, 57.80, 75, 1.05, 'Fl W 4s', 134, 28, 'W', 'Torre blanca bandas rojas. Giratorio', 'LIGHTS', ''),
|
||||||
|
(33, 'Faro Galerazamba', 10, 47.12, 75, 15.96, 'Fl W 4s', 14, 11, 'W', 'Torre fibra vidrio blanca bandas rojas. Giratorio', 'LIGHTS', ''),
|
||||||
|
|
||||||
|
# FAROLES X (Tajamares Bocas de Ceniza)
|
||||||
|
# Estas son balizas laterales fijas en tierra → BCNLAT (no LIGHTS).
|
||||||
|
# IALA-B: Verde = babor (CATLAM=1), Rojo = estribor (CATLAM=2).
|
||||||
|
# La luz se extrae automáticamente de LITCHR/SIGGRP/SIGPER/COLOUR.
|
||||||
|
(15, 'Faro X1', 11, 6.13, 74, 50.97, 'Q(4)G 11s', 6, 6, 'G', 'Torre verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(16, 'Faro X2', 11, 6.00, 74, 51.20, 'Q(4)R 11s', 6, 6, 'R', 'Torre roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(18, 'Faro X3', 11, 5.48, 74, 50.83, 'Q(4)G 11s', 6, 6, 'G', 'Torre verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(17, 'Faro X4', 11, 5.58, 74, 51.10, 'Q(4)R 11s', 6, 6, 'R', 'Torre roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(19, 'Faro X5', 11, 5.35, 74, 50.80, 'Q(4)G 11s', 6, 6, 'G', 'Torre verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(20, 'Faro X6', 11, 5.26, 74, 51.03, 'Q(4)R 11s', 6, 6, 'R', 'Torre roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(21, 'Faro X7', 11, 2.49, 74, 48.86, 'Q(4)G 11s', 8, 6, 'G', 'Baliza enrejado verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(22, 'Faro X8', 11, 4.90, 74, 50.95, 'Q(4)R 11s', 6, 6, 'R', 'Torre roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(23, 'Faro X9', 11, 2.15, 74, 48.29, 'Q(4)G 11s', 8, 6, 'G', 'Baliza enrejado verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(24, 'Faro X10', 11, 4.56, 74, 50.88, 'Q(4)R 11s', 6, 6, 'R', 'Torre roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(25, 'Faro X11', 11, 1.79, 74, 47.73, 'Q(4)G 11s', 8, 6, 'G', 'Baliza enrejado verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(26, 'Faro X12', 11, 3.90, 74, 50.65, 'Q(4)R 11s', 6, 6, 'R', 'Baliza enrejado roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(27, 'Faro X13', 11, 1.55, 74, 47.38, 'Q(4)G 11s', 8, 6, 'G', 'Baliza enrejado verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(28, 'Faro X14', 11, 3.47, 74, 50.37, 'Q(4)R 11s', 6, 6, 'R', 'Torre roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(30, 'Faro X15', 11, 1.37, 74, 47.13, 'Q(4)G 11s', 6, 6, 'G', 'Baliza enrejado verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
(29, 'Faro X16', 11, 3.00, 74, 49.98, 'Q(4)R 11s', 6, 6, 'R', 'Torre roja bandas blancas', 'BCNLAT', ''),
|
||||||
|
(31, 'Faro X17', 11, 1.12, 74, 46.70, 'Q(4)G 11s', 6, 6, 'G', 'Baliza enrejado verde bandas blancas', 'BCNLAT', ''),
|
||||||
|
|
||||||
|
# ENFILACIONES / RANGE LIGHTS
|
||||||
|
(196, 'Enfilacion E1', 11, 6.22, 74, 50.90, 'Iso Bu 5s', 10, 13, 'W', 'Baliza enrejado naranja y blanco. Rumbo 135.7', 'LIGHTS', '135.7'),
|
||||||
|
(197, 'Enfilacion E3', 11, 6.10, 74, 50.78, 'Iso Bu 5s', 22, 9, 'W', 'Torre enrejada naranja y blanco. Rumbo 139.3', 'LIGHTS', '139.3'),
|
||||||
|
(198, 'Enfilacion E3A', 11, 6.02, 74, 50.70, 'Iso W 5s', 20, 12.3, 'W', 'Torre naranja y blanco. Rumbo 135.7', 'LIGHTS', '135.7'),
|
||||||
|
(201, 'Enfilacion E4', 11, 4.21, 74, 50.81, 'Iso R 4s', 11, 4.5, 'R', 'Baliza enrejado naranja bandas blancas. Rumbo 142.3', 'LIGHTS', '142.3'),
|
||||||
|
(203, 'Enfilacion E6', 11, 3.78, 74, 50.62, 'Iso Bu 4s', 12, 8, 'R', 'Baliza enrejado roja bandas blancas. Rumbo 167.7', 'LIGHTS', '167.7'),
|
||||||
|
(204, 'Enfilacion E8', 11, 3.51, 74, 50.51, 'Iso Bu 4s', 25, 14.5, 'W', 'Baliza enrejado naranja bandas blancas. Rumbo 167.7', 'LIGHTS', '167.7'),
|
||||||
|
(205, 'Enfilacion E10', 11, 3.59, 74, 50.50, 'Iso G 5s', 11, 10, 'G', 'Torre naranja bandas blancas. Rumbo 167.3', 'LIGHTS', '167.3'),
|
||||||
|
(206, 'Enfilacion E12', 11, 3.37, 74, 50.44, 'Iso G 5s', 22, 8, 'G', 'Baliza tablero blanco franja roja. Rumbo 167.3', 'LIGHTS', '167.3'),
|
||||||
|
(207, 'Enfilacion E14', 11, 3.37, 74, 50.44, 'Iso Bu 6s', 22, 8, 'W', 'Tablero blanco con franja roja. Rumbo 122', 'LIGHTS', '122'),
|
||||||
|
(237, 'Enfilacion E16', 11, 3.22, 74, 50.20, 'Iso Bu 6s', 12, 9, 'W', 'Baliza enrejado naranja bandas blancas. Rumbo 122', 'LIGHTS', '122'),
|
||||||
|
(238, 'Enfilacion E18', 11, 2.58, 74, 49.53, 'Fl WRG 4s', 18, 6, 'WRG', 'Torre roja bandas blancas. Sector 9 grados. Rumbo 142', 'LIGHTS', '142'),
|
||||||
|
|
||||||
|
# BOYAS LATERALES CANAL RIO MAGDALENA
|
||||||
|
(199, 'Boya No. 1', 11, 5.07, 74, 50.01, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(200, 'Boya No. 3', 11, 4.55, 74, 50.69, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(202, 'Boya No. 5', 11, 3.94, 74, 50.46, 'Q G 1s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(208, 'Boya No. 7', 11, 3.60, 74, 50.25, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(209, 'Boya No. 9', 11, 2.81, 74, 49.44, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(210, 'Boya No. 11', 11, 2.38, 74, 48.73, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(211, 'Boya No. 12', 11, 2.23, 74, 48.81, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(212, 'Boya No. 13', 11, 2.06, 74, 48.12, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(213, 'Boya No. 14', 11, 1.31, 74, 47.30, 'Fl R 3s', 3, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(214, 'Boya No. 15', 11, 1.69, 74, 47.63, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(215, 'Boya No. 16', 11, 1.64, 74, 47.85, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(218, 'Boya No. 18', 11, 1.31, 74, 47.30, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(219, 'Boya No. 19', 11, 1.05, 74, 46.59, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(220, 'Boya No. 20', 11, 0.93, 74, 46.64, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(221, 'Boya No. 21', 11, 0.84, 74, 46.32, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(222, 'Boya No. 22', 11, 0.74, 74, 46.40, 'Q R 1s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(223, 'Boya No. 23', 10, 58.50, 74, 45.30, 'Fl G 1.3s', 3, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(224, 'Boya No. 24', 11, 0.55, 74, 46.23, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(225, 'Boya No. 25', 11, 0.40, 74, 45.98, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(226, 'Boya No. 26', 11, 0.35, 74, 46.10, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(227, 'Boya No. 27', 10, 59.91, 74, 45.72, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(228, 'Boya No. 28', 10, 59.99, 74, 45.92, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(229, 'Boya No. 29', 10, 59.24, 74, 45.48, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(230, 'Boya No. 30', 10, 59.24, 74, 45.64, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(231, 'Boya No. 31', 10, 58.50, 74, 45.29, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(232, 'Boya No. 33', 10, 57.56, 74, 45.34, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(233, 'Boya No. 35', 10, 56.56, 74, 45.25, 'Fl G 3s', 4, 6, 'G', 'Castillete verde', 'BOYLAT', ''),
|
||||||
|
(234, 'Boya No. 36', 10, 56.49, 74, 45.40, 'Fl R 3s', 4, 6, 'R', 'Castillete roja', 'BOYLAT', ''),
|
||||||
|
(235, 'Boya Cardinal Norte', 10, 57.55, 74, 45.21, 'Fl W 1s', 4, 6, 'W', 'Castillete cardinal N negros', 'BOYCAR', ''),
|
||||||
|
(236, 'Boya Cardinal Sur', 10, 57.55, 74, 45.21, 'Fl W 15s', 4, 6, 'W', 'Castillete cardinal S negros', 'BOYCAR', ''),
|
||||||
|
(239, 'Boya de Oleaje', 11, 8.04, 74, 45.48, 'Fl Y 20s', 0.5, 4.5, 'Y', 'Esferica amarilla. Recolectora datos oceanograficos', 'BOYSPEC', ''),
|
||||||
|
(240, 'Boya Peligro Aislado', 10, 57.27, 74, 45.44, 'Fl(2) W 4s', 3.3, 3, 'W', 'Castillete roja bandas negras. Bajo rocoso', 'BOYISD', ''),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
# IHO S-57 Ed.3.1 — códigos OFICIALES (no inventados)
|
||||||
|
# Ref: s57attributes.csv distribuido con GDAL/OGR
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
# COLOUR codes (ATTL 75)
|
||||||
|
colour_s57 = {
|
||||||
|
'W': '1', # White
|
||||||
|
'K': '2', # Black
|
||||||
|
'R': '3', # Red
|
||||||
|
'G': '4', # Green
|
||||||
|
'B': '5', # Blue
|
||||||
|
'Y': '6', # Yellow
|
||||||
|
'Gy': '7', # Grey
|
||||||
|
'Br': '8', # Brown
|
||||||
|
'Amb': '9', # Amber
|
||||||
|
'Vi': '10', # Violet
|
||||||
|
'Or': '11', # Orange
|
||||||
|
'Mg': '12', # Magenta
|
||||||
|
'WRG': '1,3,4', # White/Red/Green (multi-colour sectors)
|
||||||
|
'': '1', # default white
|
||||||
|
}
|
||||||
|
colour_name = {
|
||||||
|
'W': 'white', 'K': 'black', 'R': 'red', 'G': 'green',
|
||||||
|
'B': 'blue', 'Y': 'yellow', 'Gy': 'grey', 'Br': 'brown',
|
||||||
|
'Amb': 'amber','Vi': 'violet','Or': 'orange', 'Mg': 'magenta',
|
||||||
|
'WRG': 'white/red/green', '': 'white',
|
||||||
|
}
|
||||||
|
|
||||||
|
# LITCHR codes (ATTL 107) — S-57 Ed.3.1 official values
|
||||||
|
char_map = {
|
||||||
|
'F': '1', # Fixed
|
||||||
|
'Fl': '2', # Flashing
|
||||||
|
'LFl': '3', # Long flashing
|
||||||
|
'Q': '4', # Quick (50-60/min)
|
||||||
|
'VQ': '5', # Very quick (100-120/min)
|
||||||
|
'UQ': '6', # Ultra quick (≥160/min)
|
||||||
|
'Iso': '7', # Isophase
|
||||||
|
'Oc': '8', # Occulting
|
||||||
|
'IQ': '9', # Interrupted quick
|
||||||
|
'IVQ': '10', # Interrupted very quick
|
||||||
|
'IUQ': '11', # Interrupted ultra quick
|
||||||
|
'Mo': '12', # Morse code
|
||||||
|
'FFl': '13', # Fixed and flashing
|
||||||
|
'Fl+LFl': '14',
|
||||||
|
'Oc+Fl': '15',
|
||||||
|
'Al.Oc': '25',
|
||||||
|
'Al.LFl': '26',
|
||||||
|
'Al.Fl': '27',
|
||||||
|
'Al.Grp': '28',
|
||||||
|
}
|
||||||
|
|
||||||
|
# BOYSHP codes (ATTL 4)
|
||||||
|
# 1=Conical 2=Can 3=Sphere 4=Pillar 5=Spar 6=Barrel 7=Super-buoy 8=Ice buoy
|
||||||
|
# Barranquilla buoys are "castillete" → pillar (4)
|
||||||
|
BOYSHP_CASTILLETE = '4'
|
||||||
|
|
||||||
|
# BCNSHP codes (ATTL 2)
|
||||||
|
# 1=Stake 2=Withy 3=Tower 4=Lattice 5=Pile 6=Cairn 7=Buoyant beacon
|
||||||
|
# "Torre" → 3, "Baliza enrejado" / "celosía" → 4
|
||||||
|
def bcnshp_from_desc(desc):
|
||||||
|
d = desc.lower()
|
||||||
|
if 'enrejad' in d or 'celosí' in d or 'lattice' in d:
|
||||||
|
return '4' # Lattice tower
|
||||||
|
return '3' # Solid tower (default)
|
||||||
|
|
||||||
|
# CATLAM codes (ATTL 36) for IALA-B (Américas):
|
||||||
|
# Green marks = port side (left hand entering) → CATLAM=1
|
||||||
|
# Red marks = starboard side (right hand entering) → CATLAM=2
|
||||||
|
def catlam_from_colour(col_letter):
|
||||||
|
return {'G': '1', 'R': '2'}.get(col_letter, '')
|
||||||
|
|
||||||
|
# CATCAM codes (ATTL 13) for cardinal marks:
|
||||||
|
# 1=N 2=E 3=S 4=W
|
||||||
|
def catcam_from_name(name):
|
||||||
|
nl = name.lower()
|
||||||
|
if 'norte' in nl or 'north' in nl: return '1'
|
||||||
|
if 'este' in nl or 'east' in nl: return '2'
|
||||||
|
if 'sur' in nl or 'south' in nl: return '3'
|
||||||
|
if 'oeste' in nl or 'west' in nl: return '4'
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def parse_char(char_str):
|
||||||
|
"""Parse human-readable light character string → (litchr_code, siggrp, sigper).
|
||||||
|
Examples: 'Q(4)G 11s' → ('4','4','11')
|
||||||
|
'Fl W 4s' → ('2','','4')
|
||||||
|
'Iso Bu 5s' → ('7','','5')
|
||||||
|
"""
|
||||||
|
# Extract base character (longest prefix match, try longest first)
|
||||||
|
base = re.split(r'[\s\(\.]', char_str)[0]
|
||||||
|
# Try compound chars first (Al.Fl, Fl+LFl …)
|
||||||
|
litchr = None
|
||||||
|
for key in sorted(char_map, key=len, reverse=True):
|
||||||
|
if char_str.startswith(key):
|
||||||
|
litchr = char_map[key]
|
||||||
|
break
|
||||||
|
if litchr is None:
|
||||||
|
litchr = char_map.get(base, '2') # default Fl if unknown
|
||||||
|
mg = re.search(r'\((\d+)\)', char_str)
|
||||||
|
siggrp = mg.group(1) if mg else ''
|
||||||
|
mp = re.search(r'([\d.]+)s', char_str)
|
||||||
|
sigper = mp.group(1) if mp else ''
|
||||||
|
return litchr, siggrp, sigper
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Campos del CSV de salida — usan nombres de atributos S-57 directamente
|
||||||
|
# para que el converter los recoja sin ningún mapeo adicional.
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
fields = ['no_dimar', 'OBJNAM', 'lon', 'lat', 'feat_type',
|
||||||
|
'LITCHR', 'LITCHR_TXT', 'SIGGRP', 'SIGPER',
|
||||||
|
'COLOUR', 'COLOUR_TXT', 'COLPAT',
|
||||||
|
'VALNMR', 'HEIGHT', 'ORIENT',
|
||||||
|
'CATLAM', 'CATCAM', 'BOYSHP', 'BCNSHP', 'TOPSHP',
|
||||||
|
'INFORM', '_dimar_char_raw', '_source']
|
||||||
|
|
||||||
|
out_path = r'D:\Proyectos Software\QGISS57Converter\dimar_ayudas_barranquilla.csv'
|
||||||
|
|
||||||
|
with open(out_path, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
w = csv.DictWriter(f, fieldnames=fields)
|
||||||
|
w.writeheader()
|
||||||
|
for rec in records:
|
||||||
|
no, name, lat_d, lat_m, lon_d, lon_m, char, ht, rng, col, desc, ftype, orient = rec
|
||||||
|
lat = dms_to_dd(lat_d, lat_m)
|
||||||
|
lon = -dms_to_dd(lon_d, lon_m) # West = negative
|
||||||
|
litchr, siggrp, sigper = parse_char(char)
|
||||||
|
|
||||||
|
catlam = ''
|
||||||
|
catcam = ''
|
||||||
|
boyshp = ''
|
||||||
|
bcnshp = ''
|
||||||
|
topshp = ''
|
||||||
|
colpat = ''
|
||||||
|
|
||||||
|
if ftype == 'BOYLAT':
|
||||||
|
# Castillete lateral: pillar (4), with CATLAM
|
||||||
|
boyshp = BOYSHP_CASTILLETE
|
||||||
|
catlam = catlam_from_colour(col)
|
||||||
|
elif ftype == 'BCNLAT':
|
||||||
|
# Faros de orilla (X marks): shore fixed beacon with CATLAM
|
||||||
|
bcnshp = bcnshp_from_desc(desc)
|
||||||
|
catlam = catlam_from_colour(col)
|
||||||
|
elif ftype in ('BOYCAR', 'BCNCAR'):
|
||||||
|
boyshp = '4' # pillar for cardinal buoys
|
||||||
|
catcam = catcam_from_name(name)
|
||||||
|
colpat = '1' # horizontal bands (standard cardinal pattern)
|
||||||
|
elif ftype == 'BOYISD':
|
||||||
|
boyshp = '4'
|
||||||
|
colpat = '1' # horizontal bands (black over red)
|
||||||
|
elif ftype == 'BOYSAW':
|
||||||
|
boyshp = '1' # conical or spherical for safe water
|
||||||
|
colpat = '4' # vertical stripes (white/red)
|
||||||
|
elif ftype in ('BOYSPEC', 'BOYSPP'):
|
||||||
|
boyshp = '3' # sphere for special/data buoys
|
||||||
|
ftype = 'BOYSPP' # normalise to S-57 acronym
|
||||||
|
|
||||||
|
w.writerow({
|
||||||
|
'no_dimar': no,
|
||||||
|
'OBJNAM': name,
|
||||||
|
'lon': f'{lon:.6f}',
|
||||||
|
'lat': f'{lat:.6f}',
|
||||||
|
'feat_type': ftype,
|
||||||
|
'LITCHR': litchr,
|
||||||
|
'LITCHR_TXT': char.split()[0],
|
||||||
|
'SIGGRP': siggrp,
|
||||||
|
'SIGPER': sigper,
|
||||||
|
'COLOUR': colour_s57.get(col, '1'),
|
||||||
|
'COLOUR_TXT': colour_name.get(col, 'white'),
|
||||||
|
'COLPAT': colpat,
|
||||||
|
'VALNMR': rng,
|
||||||
|
'HEIGHT': ht,
|
||||||
|
'ORIENT': orient,
|
||||||
|
'CATLAM': catlam,
|
||||||
|
'CATCAM': catcam,
|
||||||
|
'BOYSHP': boyshp,
|
||||||
|
'BCNSHP': bcnshp,
|
||||||
|
'TOPSHP': topshp,
|
||||||
|
'INFORM': desc,
|
||||||
|
'_dimar_char_raw': char,
|
||||||
|
'_source': 'DIMAR Lista de Luces 2015',
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f'Saved {len(records)} records -> {out_path}')
|
||||||
|
types = Counter(r[11] for r in records)
|
||||||
|
for t, c in sorted(types.items()):
|
||||||
|
print(f' {t}: {c}')
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
PoC: produce a minimal valid S-57 .000 for the Wabasso test area and verify it
|
||||||
|
opens cleanly with fiona (i.e., GDAL's S-57 driver).
|
||||||
|
|
||||||
|
Expected result: fiona.listlayers() shows M_COVR + BOYLAT (and no warnings about
|
||||||
|
broken bounds), and bounds match the envelope we encoded.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
from s57_writer import (
|
||||||
|
S57Cell, OBJL_M_COVR, OBJL_BOYLAT,
|
||||||
|
ATTL_CATCOV, ATTL_CATLAM,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wabasso area envelope (lon, lat):
|
||||||
|
W, S, E, N = -80.4588, 27.7519, -80.4574, 27.7609
|
||||||
|
|
||||||
|
OUTPUT = Path(__file__).parent / "test_wabasso.000"
|
||||||
|
|
||||||
|
cell = S57Cell(
|
||||||
|
dsnm="TESTPOC.000",
|
||||||
|
edition=1,
|
||||||
|
intu=5, # Harbour
|
||||||
|
scale=10000,
|
||||||
|
agen=999,
|
||||||
|
comt="QGISS57Converter PoC",
|
||||||
|
issue_date="20260428",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1) Mandatory M_COVR feature: rectangular envelope, CATCOV=1 (data covered)
|
||||||
|
cell.add_area_feature(
|
||||||
|
objl=OBJL_M_COVR,
|
||||||
|
ring=[(W, S), (E, S), (E, N), (W, N), (W, S)],
|
||||||
|
attrs=[(ATTL_CATCOV, "1")], # CATCOV=1 (coverage present)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2) One BOYLAT (lateral buoy, port hand IALA-B = CATLAM=1) at the centre
|
||||||
|
cx = (W + E) / 2
|
||||||
|
cy = (S + N) / 2
|
||||||
|
cell.add_point_feature(
|
||||||
|
objl=OBJL_BOYLAT,
|
||||||
|
lon=cx, lat=cy,
|
||||||
|
attrs=[(ATTL_CATLAM, "1")], # CATLAM=1 (port-hand lateral mark)
|
||||||
|
)
|
||||||
|
|
||||||
|
cell.write(OUTPUT)
|
||||||
|
print(f"Wrote {OUTPUT} ({OUTPUT.stat().st_size} bytes)")
|
||||||
|
|
||||||
|
# ── Verify with fiona ────────────────────────────────────────────────────────
|
||||||
|
print()
|
||||||
|
print("Verifying with fiona...")
|
||||||
|
try:
|
||||||
|
import fiona
|
||||||
|
layers = fiona.listlayers(str(OUTPUT))
|
||||||
|
print(f" fiona.listlayers: {layers}")
|
||||||
|
for L in layers:
|
||||||
|
try:
|
||||||
|
with fiona.open(str(OUTPUT), layer=L) as src:
|
||||||
|
print(f" {L:10s} bounds={src.bounds} features={len(src)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {L}: open ERROR: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" fiona import or listlayers failed: {e}")
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"_comment": "S-57 object class catalog — IHO S-57 Ed 3.1. Key: S-57 acronym. geom: A=Area, L=Line, P=Point, C=Collection",
|
||||||
|
"COALNE": { "desc": "Coastline", "geom": ["L"], "attrs": [] },
|
||||||
|
"LNDARE": { "desc": "Land Area", "geom": ["A"], "attrs": [] },
|
||||||
|
"DEPARE": { "desc": "Depth Area", "geom": ["A"], "attrs": ["DRVAL1","DRVAL2"] },
|
||||||
|
"DEPCNT": { "desc": "Depth Contour", "geom": ["L"], "attrs": ["VALDCO"] },
|
||||||
|
"SOUNDG": { "desc": "Sounding", "geom": ["P"], "attrs": ["VALSOU","QUASOU","SOUACC"] },
|
||||||
|
"DRGARE": { "desc": "Dredged Area", "geom": ["A"], "attrs": ["DRVAL1","DRVAL2"] },
|
||||||
|
"SBDARE": { "desc": "Seabed Area", "geom": ["A","P"], "attrs": ["NATSUR","NATQUA"] },
|
||||||
|
"OBSTRN": { "desc": "Obstruction", "geom": ["A","L","P"], "attrs": ["VALSOU","CATOBS"] },
|
||||||
|
"WRECKS": { "desc": "Wreck", "geom": ["A","P"], "attrs": ["VALSOU","CATWRK"] },
|
||||||
|
"UWTROC": { "desc": "Underwater / Awash Rock","geom": ["P"], "attrs": ["VALSOU","WATLEV"] },
|
||||||
|
"LIGHTS": { "desc": "Light", "geom": ["P"], "attrs": ["LITCHR","SIGPER","HEIGHT","VALNMR","COLOUR","ORIENT","SIGGRP","SECTR1","SECTR2"] },
|
||||||
|
"LNDMRK": { "desc": "Landmark", "geom": ["A","P"], "attrs": ["CATLMK","HEIGHT"] },
|
||||||
|
"BOYLAT": { "desc": "Lateral Buoy", "geom": ["P"], "attrs": ["CATLAM","COLOUR","COLPAT","LITCHR","SIGPER","SIGGRP","VALNMR"] },
|
||||||
|
"BOYCAR": { "desc": "Cardinal Buoy", "geom": ["P"], "attrs": ["CATCAM","COLOUR","LITCHR","SIGPER","VALNMR"] },
|
||||||
|
"BOYISD": { "desc": "Isolated Danger Buoy", "geom": ["P"], "attrs": ["COLOUR","LITCHR","SIGPER","SIGGRP","VALNMR"] },
|
||||||
|
"BOYSPP": { "desc": "Special Purpose Buoy", "geom": ["P"], "attrs": ["CATSPM","COLOUR","LITCHR","SIGPER","VALNMR"] },
|
||||||
|
"BOYSPEC":{ "desc": "Special Purpose Buoy (alias→BOYSPP)", "geom": ["P"], "attrs": ["CATSPM","COLOUR","LITCHR","SIGPER","VALNMR"] },
|
||||||
|
"BOYSAW": { "desc": "Safe Water Buoy", "geom": ["P"], "attrs": ["COLOUR","LITCHR","SIGPER","VALNMR"] },
|
||||||
|
"BCNLAT": { "desc": "Lateral Beacon", "geom": ["P"], "attrs": ["CATLAM","COLOUR"] },
|
||||||
|
"BCNCAR": { "desc": "Cardinal Beacon", "geom": ["P"], "attrs": ["CATCAM","COLOUR"] },
|
||||||
|
"BCNISD": { "desc": "Isolated Danger Beacon", "geom": ["P"], "attrs": ["COLOUR"] },
|
||||||
|
"BCNSAW": { "desc": "Safe Water Beacon", "geom": ["P"], "attrs": ["COLOUR"] },
|
||||||
|
"BCNSPP": { "desc": "Special Purpose Beacon", "geom": ["P"], "attrs": ["CATSPM","COLOUR"] },
|
||||||
|
"LITFLT": { "desc": "Light Float", "geom": ["P"], "attrs": ["LITCHR","SIGPER","COLOUR"] },
|
||||||
|
"LITVES": { "desc": "Light Vessel", "geom": ["P"], "attrs": ["LITCHR","SIGPER"] },
|
||||||
|
"TOPMAR": { "desc": "Topmark", "geom": ["P"], "attrs": ["TOPSHP","COLOUR"] },
|
||||||
|
"DAYMAR": { "desc": "Daymark", "geom": ["P"], "attrs": ["CATLAM","COLOUR"] },
|
||||||
|
"FOGSIG": { "desc": "Fog Signal", "geom": ["P"], "attrs": ["CATFOG","SIGPER"] },
|
||||||
|
"NAVLNE": { "desc": "Navigation Line", "geom": ["L"], "attrs": ["CATNAV"] },
|
||||||
|
"LDLINE": { "desc": "Leading Line", "geom": ["L"], "attrs": [] },
|
||||||
|
"TSSLPT": { "desc": "Traffic Separation Lane Pt", "geom": ["A"], "attrs": [] },
|
||||||
|
"TSSRON": { "desc": "Traffic Separation Roundabout", "geom": ["A"], "attrs": [] },
|
||||||
|
"TSSBND": { "desc": "Traffic Separation Boundary", "geom": ["L"], "attrs": [] },
|
||||||
|
"TSSCRS": { "desc": "Traffic Separation Crossing", "geom": ["A"], "attrs": [] },
|
||||||
|
"TSELNE": { "desc": "Traffic Separation Line", "geom": ["L"], "attrs": [] },
|
||||||
|
"DWRTCL": { "desc": "Deep Water Route Centre Line", "geom": ["L"], "attrs": [] },
|
||||||
|
"DWRTPT": { "desc": "Deep Water Route Part", "geom": ["A"], "attrs": [] },
|
||||||
|
"TWRTPT": { "desc": "Two-way Route Part", "geom": ["A"], "attrs": [] },
|
||||||
|
"SUBTLN": { "desc": "Submarine Transit Lane","geom": ["A"], "attrs": [] },
|
||||||
|
"ISTZNE": { "desc": "Inshore Traffic Zone", "geom": ["A"], "attrs": [] },
|
||||||
|
"RECTRC": { "desc": "Recommended Track", "geom": ["L"], "attrs": ["CATTRK"] },
|
||||||
|
"FERYRT": { "desc": "Ferry Route", "geom": ["A","L"], "attrs": ["CATFRY"] },
|
||||||
|
"FAIRWY": { "desc": "Fairway", "geom": ["A"], "attrs": ["CATFWY"] },
|
||||||
|
"ACHARE": { "desc": "Anchorage Area", "geom": ["A","P"], "attrs": ["CATACH"] },
|
||||||
|
"ACHBRT": { "desc": "Anchor Berth", "geom": ["A","P"], "attrs": ["CATACH"] },
|
||||||
|
"BERTHS": { "desc": "Berth", "geom": ["A","L","P"], "attrs": ["QUAPOS"] },
|
||||||
|
"HRBARE": { "desc": "Harbour Area", "geom": ["A"], "attrs": ["CATHAF"] },
|
||||||
|
"HRBFAC": { "desc": "Harbour Facility", "geom": ["A","P"], "attrs": ["CATHAF"] },
|
||||||
|
"PILBOP": { "desc": "Pilot Boarding Place", "geom": ["A","P"], "attrs": ["CATPIL"] },
|
||||||
|
"MORFAC": { "desc": "Mooring / Warping Facility", "geom": ["A","L","P"], "attrs": ["CATMOR"] },
|
||||||
|
"DOCARE": { "desc": "Dock Area", "geom": ["A"], "attrs": [] },
|
||||||
|
"DRYDOC": { "desc": "Dry Dock", "geom": ["A"], "attrs": [] },
|
||||||
|
"HULKES": { "desc": "Hulk", "geom": ["A","P"], "attrs": ["CATHLK"] },
|
||||||
|
"CRANES": { "desc": "Crane", "geom": ["A","P"], "attrs": ["CATCRN","HEIGHT"] },
|
||||||
|
"PILPNT": { "desc": "Pile / Bollard", "geom": ["L","P"], "attrs": ["CATPLE"] },
|
||||||
|
"PONTON": { "desc": "Pontoon", "geom": ["A","L","P"], "attrs": [] },
|
||||||
|
"CBLSUB": { "desc": "Submarine Cable", "geom": ["A","L"], "attrs": ["CATCBL"] },
|
||||||
|
"CBLOHD": { "desc": "Overhead Cable", "geom": ["L"], "attrs": ["CATCBL","VERCLR"] },
|
||||||
|
"PIPOHD": { "desc": "Overhead Pipe", "geom": ["L"], "attrs": ["CATPIP","VERCLR"] },
|
||||||
|
"PIPSOL": { "desc": "Pipeline on Land", "geom": ["A","L"], "attrs": ["CATPIP"] },
|
||||||
|
"BRIDGE": { "desc": "Bridge", "geom": ["A","L","P"], "attrs": ["VERCLR","HORACC"] },
|
||||||
|
"TUNNEL": { "desc": "Tunnel", "geom": ["A","L"], "attrs": ["CATTNL"] },
|
||||||
|
"GATCON": { "desc": "Gate / Sluice", "geom": ["A","L","P"], "attrs": ["CATGAT"] },
|
||||||
|
"LOKBSN": { "desc": "Lock Basin", "geom": ["A"], "attrs": [] },
|
||||||
|
"DMPGRD": { "desc": "Dumping Ground", "geom": ["A"], "attrs": ["CATDPG"] },
|
||||||
|
"SWPARE": { "desc": "Swept Area", "geom": ["A"], "attrs": ["DRVAL1"] },
|
||||||
|
"OSPARE": { "desc": "Offshore Production Area","geom": ["A"], "attrs": [] },
|
||||||
|
"OFSPLF": { "desc": "Offshore Platform", "geom": ["A","P"], "attrs": ["CATOFP"] },
|
||||||
|
"MARCUL": { "desc": "Marine Farm / Culture", "geom": ["A","P"], "attrs": ["CATMAC"] },
|
||||||
|
"PIPARE": { "desc": "Pipeline Area", "geom": ["A"], "attrs": ["CATPIP"] },
|
||||||
|
"RIVERS": { "desc": "River", "geom": ["A","L"], "attrs": [] },
|
||||||
|
"RIVBNK": { "desc": "River Bank", "geom": ["L"], "attrs": [] },
|
||||||
|
"TIDEWY": { "desc": "Tideway", "geom": ["A"], "attrs": [] },
|
||||||
|
"WATTUR": { "desc": "Water Turbulence", "geom": ["P"], "attrs": [] },
|
||||||
|
"BUISGL": { "desc": "Building, Single", "geom": ["A","P"], "attrs": [] },
|
||||||
|
"BUAARE": { "desc": "Built-up Area", "geom": ["A"], "attrs": [] },
|
||||||
|
"LAKARE": { "desc": "Lake", "geom": ["A"], "attrs": [] },
|
||||||
|
"CANALS": { "desc": "Canal", "geom": ["A","L"], "attrs": [] },
|
||||||
|
"ROADWY": { "desc": "Road", "geom": ["A","L"], "attrs": ["CATROD"] },
|
||||||
|
"RUNWAY": { "desc": "Runway", "geom": ["A","L"], "attrs": [] },
|
||||||
|
"SLOTOP": { "desc": "Slope Topline", "geom": ["L"], "attrs": [] },
|
||||||
|
"SLOGRD": { "desc": "Slope", "geom": ["A"], "attrs": [] },
|
||||||
|
"VEGATN": { "desc": "Vegetation", "geom": ["A","P"], "attrs": ["CATVEG"] },
|
||||||
|
"WEDKLP": { "desc": "Weed / Kelp", "geom": ["A","P"], "attrs": ["CATWED"] },
|
||||||
|
"LNDRGN": { "desc": "Land Region", "geom": ["A"], "attrs": ["CATLND"] },
|
||||||
|
"SEAARE": { "desc": "Sea Area / Named Water","geom": ["A","P"], "attrs": ["CATSEA"] },
|
||||||
|
"RESARE": { "desc": "Restricted Area", "geom": ["A"], "attrs": ["CATREA"] },
|
||||||
|
"PRCARE": { "desc": "Precautionary Area", "geom": ["A"], "attrs": [] },
|
||||||
|
"SPLARE": { "desc": "Special Purpose Area", "geom": ["A"], "attrs": ["CATSPM"] },
|
||||||
|
"MIPARE": { "desc": "Military Practice Area","geom": ["A"], "attrs": [] },
|
||||||
|
"CTSARE": { "desc": "Cargo Transhipment Area","geom": ["A"], "attrs": [] },
|
||||||
|
"FSHZNE": { "desc": "Fishery Zone", "geom": ["A"], "attrs": [] },
|
||||||
|
"FRPARE": { "desc": "Free Port Area", "geom": ["A"], "attrs": [] },
|
||||||
|
"TESARE": { "desc": "Territorial Sea Area", "geom": ["A"], "attrs": [] },
|
||||||
|
"EXEZNE": { "desc": "Exclusive Economic Zone","geom": ["A"], "attrs": [] },
|
||||||
|
"ISTZNE": { "desc": "ISPS Zone", "geom": ["A"], "attrs": [] },
|
||||||
|
"ADDMRN": { "desc": "Admiralty Note", "geom": ["A","L","P"], "attrs": [] },
|
||||||
|
"RDOCAL": { "desc": "Radio Calling-In Point","geom": ["L","P"], "attrs": [] },
|
||||||
|
"RDOSTA": { "desc": "Radio Station", "geom": ["P"], "attrs": ["CATROS"] },
|
||||||
|
"RADRFL": { "desc": "Radar Reflector", "geom": ["P"], "attrs": [] },
|
||||||
|
"RADSTA": { "desc": "Radar Station", "geom": ["P"], "attrs": ["CATRAS"] },
|
||||||
|
"RETRFL": { "desc": "Retro-reflector", "geom": ["P"], "attrs": [] },
|
||||||
|
"MAGVAR": { "desc": "Magnetic Variation", "geom": ["P"], "attrs": ["VALMAG","VALACM"] },
|
||||||
|
"MONUMT": { "desc": "Monument", "geom": ["P"], "attrs": ["CATLMK","HEIGHT"] },
|
||||||
|
"TIDALP": { "desc": "Tidal Stream Panel", "geom": ["P"], "attrs": [] },
|
||||||
|
"STSLNE": { "desc": "Straight Territorial Sea Baseline", "geom": ["L"], "attrs": [] },
|
||||||
|
"M_COVR": { "desc": "Coverage", "geom": ["A"], "attrs": ["CATCOV"] },
|
||||||
|
"M_NSYS": { "desc": "Navigation System of Marks", "geom": ["A"], "attrs": ["MARSYS"] },
|
||||||
|
"M_QUAL": { "desc": "Quality of Data", "geom": ["A"], "attrs": ["CATZOC"] },
|
||||||
|
"M_ACCY": { "desc": "Accuracy of Data", "geom": ["A"], "attrs": [] },
|
||||||
|
"M_CSCL": { "desc": "Compilation Scale", "geom": ["A"], "attrs": ["CSCALE"] },
|
||||||
|
"M_SDAT": { "desc": "Sounding Datum", "geom": ["A"], "attrs": ["VERDAT"] },
|
||||||
|
"M_HDAT": { "desc": "Horizontal Datum", "geom": ["A"], "attrs": ["HORDAT"] },
|
||||||
|
"M_UNIT": { "desc": "Units of Measurement", "geom": ["A"], "attrs": [] }
|
||||||
|
}
|
||||||
+674
@@ -0,0 +1,674 @@
|
|||||||
|
"""
|
||||||
|
S-57 ENC writer — produces structurally valid `.000` files that the GDAL
|
||||||
|
S-57 driver can read. Reuses a NOAA DDR template verbatim (the schema is fixed
|
||||||
|
by IHO S-57 ed. 3.1) and only encodes data records.
|
||||||
|
|
||||||
|
ISO 8211 record format (data records, leader entry-map "3404"):
|
||||||
|
Leader (24 bytes) + Directory (11 bytes per entry + FT) + Field area.
|
||||||
|
Directory entry layout: tag(4) + length(3) + position(4).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ── ISO 8211 control characters ────────────────────────────────────────────────
|
||||||
|
FT = b"\x1e" # Field terminator
|
||||||
|
UT = b"\x1f" # Unit terminator (subfield delimiter)
|
||||||
|
|
||||||
|
DDR_TEMPLATE = Path(__file__).parent / "noaa_ddr_template.bin"
|
||||||
|
|
||||||
|
# S-57 record names (RCNM)
|
||||||
|
RCNM_VI = 110 # Isolated Node (point primitive)
|
||||||
|
RCNM_VC = 120 # Connected Node
|
||||||
|
RCNM_VE = 130 # Edge (line primitive)
|
||||||
|
RCNM_VF = 140 # Face
|
||||||
|
RCNM_FE = 100 # Feature
|
||||||
|
|
||||||
|
# Object class codes — from s57objectclasses.csv (IHO S-57 Ed.3.1, Google Earth GDAL copy)
|
||||||
|
# fmt: off
|
||||||
|
OBJL_ACHBRT = 3 # Anchor berth
|
||||||
|
OBJL_ACHARE = 4 # Anchorage area
|
||||||
|
OBJL_BCNCAR = 5 # Beacon, cardinal
|
||||||
|
OBJL_BCNISD = 6 # Beacon, isolated danger
|
||||||
|
OBJL_BCNLAT = 7 # Beacon, lateral
|
||||||
|
OBJL_BCNSAW = 8 # Beacon, safe water
|
||||||
|
OBJL_BCNSPP = 9 # Beacon, special purpose/general
|
||||||
|
OBJL_BERTHS = 10 # Berth
|
||||||
|
OBJL_BRIDGE = 11 # Bridge
|
||||||
|
OBJL_BUISGL = 12 # Building, single
|
||||||
|
OBJL_BUAARE = 13 # Built-up area
|
||||||
|
OBJL_BOYCAR = 14 # Buoy, cardinal
|
||||||
|
OBJL_BOYISD = 16 # Buoy, isolated danger
|
||||||
|
OBJL_BOYLAT = 17 # Buoy, lateral
|
||||||
|
OBJL_BOYSAW = 18 # Buoy, safe water ← was 19 (BUG fixed)
|
||||||
|
OBJL_BOYSPP = 19 # Buoy, special purpose/general (S-57 std acronym)
|
||||||
|
OBJL_CBLOHD = 21 # Cable, overhead
|
||||||
|
OBJL_CBLSUB = 22 # Cable, submarine
|
||||||
|
OBJL_CANALS = 23 # Canal
|
||||||
|
OBJL_CTSARE = 25 # Cargo transshipment area
|
||||||
|
OBJL_COALNE = 30 # Coastline
|
||||||
|
OBJL_CRANES = 35 # Crane
|
||||||
|
OBJL_DAYMAR = 39 # Daymark
|
||||||
|
OBJL_DWRTCL = 40 # Deep water route centre line
|
||||||
|
OBJL_DWRTPT = 41 # Deep water route part
|
||||||
|
OBJL_DEPARE = 42 # Depth area
|
||||||
|
OBJL_DEPCNT = 43 # Depth contour
|
||||||
|
OBJL_DOCARE = 45 # Dock area
|
||||||
|
OBJL_DRGARE = 46 # Dredged area
|
||||||
|
OBJL_DRYDOC = 47 # Dry dock
|
||||||
|
OBJL_DMPGRD = 48 # Dumping ground
|
||||||
|
OBJL_EXEZNE = 50 # Exclusive Economic Zone
|
||||||
|
OBJL_FAIRWY = 51 # Fairway
|
||||||
|
OBJL_FERYRT = 53 # Ferry route
|
||||||
|
OBJL_FSHZNE = 54 # Fishery zone
|
||||||
|
OBJL_FOGSIG = 58 # Fog signal
|
||||||
|
OBJL_FRPARE = 60 # Free port area
|
||||||
|
OBJL_GATCON = 61 # Gate / sluice
|
||||||
|
OBJL_HRBARE = 63 # Harbour area (administrative)
|
||||||
|
OBJL_HRBFAC = 64 # Harbour facility
|
||||||
|
OBJL_HULKES = 65 # Hulk
|
||||||
|
OBJL_ISTZNE = 68 # Inshore traffic zone
|
||||||
|
OBJL_LAKARE = 69 # Lake
|
||||||
|
OBJL_LNDARE = 71 # Land area
|
||||||
|
OBJL_LNDRGN = 73 # Land region
|
||||||
|
OBJL_LNDMRK = 74 # Landmark
|
||||||
|
OBJL_LIGHTS = 75 # Light
|
||||||
|
OBJL_LITFLT = 76 # Light float
|
||||||
|
OBJL_LITVES = 77 # Light vessel
|
||||||
|
OBJL_LOKBSN = 79 # Lock basin
|
||||||
|
OBJL_MAGVAR = 81 # Magnetic variation
|
||||||
|
OBJL_MARCUL = 82 # Marine farm/culture
|
||||||
|
OBJL_MIPARE = 83 # Military practice area
|
||||||
|
OBJL_MORFAC = 84 # Mooring/warping facility
|
||||||
|
OBJL_NAVLNE = 85 # Navigation line
|
||||||
|
OBJL_OBSTRN = 86 # Obstruction
|
||||||
|
OBJL_OFSPLF = 87 # Offshore platform
|
||||||
|
OBJL_OSPARE = 88 # Offshore production area
|
||||||
|
OBJL_PILPNT = 90 # Pile
|
||||||
|
OBJL_PILBOP = 91 # Pilot boarding place
|
||||||
|
OBJL_PIPARE = 92 # Pipeline area
|
||||||
|
OBJL_PIPOHD = 93 # Pipeline, overhead
|
||||||
|
OBJL_PIPSOL = 94 # Pipeline, submarine/on land
|
||||||
|
OBJL_PONTON = 95 # Pontoon
|
||||||
|
OBJL_PRCARE = 96 # Precautionary area
|
||||||
|
OBJL_PYLONS = 98 # Pylon/bridge support
|
||||||
|
OBJL_RADRFL = 101 # Radar reflector
|
||||||
|
OBJL_RADSTA = 102 # Radar station
|
||||||
|
OBJL_RDOCAL = 104 # Radio calling-in point
|
||||||
|
OBJL_RDOSTA = 105 # Radio station
|
||||||
|
OBJL_RECTRC = 109 # Recommended track
|
||||||
|
OBJL_RESARE = 112 # Restricted area
|
||||||
|
OBJL_RETRFL = 113 # Retro-reflector
|
||||||
|
OBJL_RIVERS = 114 # River
|
||||||
|
OBJL_RIVBNK = 115 # River bank
|
||||||
|
OBJL_ROADWY = 116 # Road
|
||||||
|
OBJL_RUNWAY = 117 # Runway
|
||||||
|
OBJL_SEAARE = 119 # Sea area / named water area
|
||||||
|
OBJL_SPLARE = 120 # Sea-plane landing area (SPLARE)
|
||||||
|
OBJL_SBDARE = 121 # Seabed area
|
||||||
|
OBJL_SLCONS = 122 # Shoreline construction
|
||||||
|
OBJL_SLOTOP = 126 # Slope topline
|
||||||
|
OBJL_SLOGRD = 127 # Sloping ground
|
||||||
|
OBJL_SOUNDG = 129 # Sounding
|
||||||
|
OBJL_STSLNE = 132 # Straight territorial sea baseline
|
||||||
|
OBJL_SUBTLN = 133 # Submarine transit lane
|
||||||
|
OBJL_SWPARE = 134 # Swept area
|
||||||
|
OBJL_TESARE = 135 # Territorial sea area
|
||||||
|
OBJL_TIDEWY = 143 # Tideway
|
||||||
|
OBJL_TOPMAR = 144 # Top mark
|
||||||
|
OBJL_TSELNE = 145 # Traffic Separation Line
|
||||||
|
OBJL_TSSBND = 146 # Traffic Separation Scheme Boundary
|
||||||
|
OBJL_TSSCRS = 147 # Traffic Separation Scheme Crossing
|
||||||
|
OBJL_TSSLPT = 148 # Traffic Separation Scheme Lane part
|
||||||
|
OBJL_TSSRON = 149 # Traffic Separation Scheme Roundabout
|
||||||
|
OBJL_TUNNEL = 151 # Tunnel
|
||||||
|
OBJL_TWRTPT = 152 # Two-way route part
|
||||||
|
OBJL_UWTROC = 153 # Underwater rock / awash rock
|
||||||
|
OBJL_VEGATN = 155 # Vegetation
|
||||||
|
OBJL_WATTUR = 156 # Water turbulence
|
||||||
|
OBJL_WEDKLP = 158 # Weed/Kelp
|
||||||
|
OBJL_WRECKS = 159 # Wreck
|
||||||
|
OBJL_M_ACCY = 300 # Accuracy of data (meta)
|
||||||
|
OBJL_M_CSCL = 301 # Compilation scale (meta)
|
||||||
|
OBJL_M_COVR = 302 # Coverage (meta)
|
||||||
|
OBJL_M_HDAT = 303 # Horizontal datum (meta)
|
||||||
|
OBJL_M_NSYS = 306 # Navigational system of marks (meta)
|
||||||
|
OBJL_M_QUAL = 308 # Quality of data (meta)
|
||||||
|
OBJL_M_SDAT = 309 # Sounding datum (meta)
|
||||||
|
OBJL_M_UNIT = 311 # Units of measurement (meta)
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
# Lookup: S-57 acronym → OBJL code (complete IHO S-57 Ed 3.1 set)
|
||||||
|
OBJL_BY_ACRONYM: dict[str, int] = {
|
||||||
|
"ACHBRT": OBJL_ACHBRT, "ACHARE": OBJL_ACHARE,
|
||||||
|
"BCNCAR": OBJL_BCNCAR, "BCNISD": OBJL_BCNISD, "BCNLAT": OBJL_BCNLAT,
|
||||||
|
"BCNSAW": OBJL_BCNSAW, "BCNSPP": OBJL_BCNSPP,
|
||||||
|
"BERTHS": OBJL_BERTHS, "BRIDGE": OBJL_BRIDGE,
|
||||||
|
"BUISGL": OBJL_BUISGL, "BUAARE": OBJL_BUAARE,
|
||||||
|
"BOYCAR": OBJL_BOYCAR, "BOYISD": OBJL_BOYISD, "BOYLAT": OBJL_BOYLAT,
|
||||||
|
"BOYSAW": OBJL_BOYSAW, "BOYSPP": OBJL_BOYSPP,
|
||||||
|
"BOYSPEC": OBJL_BOYSPP, # user alias → BOYSPP
|
||||||
|
"CBLOHD": OBJL_CBLOHD, "CBLSUB": OBJL_CBLSUB,
|
||||||
|
"CANALS": OBJL_CANALS, "CTSARE": OBJL_CTSARE,
|
||||||
|
"COALNE": OBJL_COALNE, "CRANES": OBJL_CRANES,
|
||||||
|
"DAYMAR": OBJL_DAYMAR, "DWRTCL": OBJL_DWRTCL, "DWRTPT": OBJL_DWRTPT,
|
||||||
|
"DEPARE": OBJL_DEPARE, "DEPCNT": OBJL_DEPCNT,
|
||||||
|
"DOCARE": OBJL_DOCARE, "DRGARE": OBJL_DRGARE, "DRYDOC": OBJL_DRYDOC,
|
||||||
|
"DMPGRD": OBJL_DMPGRD,
|
||||||
|
"EXEZNE": OBJL_EXEZNE, "FAIRWY": OBJL_FAIRWY,
|
||||||
|
"FERYRT": OBJL_FERYRT, "FSHZNE": OBJL_FSHZNE, "FRPARE": OBJL_FRPARE,
|
||||||
|
"FOGSIG": OBJL_FOGSIG, "GATCON": OBJL_GATCON,
|
||||||
|
"HRBARE": OBJL_HRBARE, "HRBFAC": OBJL_HRBFAC, "HULKES": OBJL_HULKES,
|
||||||
|
"ISTZNE": OBJL_ISTZNE,
|
||||||
|
"LAKARE": OBJL_LAKARE, "LNDARE": OBJL_LNDARE, "LNDRGN": OBJL_LNDRGN,
|
||||||
|
"LNDMRK": OBJL_LNDMRK, "LIGHTS": OBJL_LIGHTS,
|
||||||
|
"LITFLT": OBJL_LITFLT, "LITVES": OBJL_LITVES,
|
||||||
|
"LOKBSN": OBJL_LOKBSN, "MAGVAR": OBJL_MAGVAR,
|
||||||
|
"MARCUL": OBJL_MARCUL, "MIPARE": OBJL_MIPARE, "MORFAC": OBJL_MORFAC,
|
||||||
|
"NAVLNE": OBJL_NAVLNE, "OBSTRN": OBJL_OBSTRN,
|
||||||
|
"OFSPLF": OBJL_OFSPLF, "OSPARE": OBJL_OSPARE,
|
||||||
|
"PILPNT": OBJL_PILPNT, "PILBOP": OBJL_PILBOP, "PIPARE": OBJL_PIPARE,
|
||||||
|
"PIPOHD": OBJL_PIPOHD, "PIPSOL": OBJL_PIPSOL, "PONTON": OBJL_PONTON,
|
||||||
|
"PRCARE": OBJL_PRCARE, "PYLONS": OBJL_PYLONS,
|
||||||
|
"RADRFL": OBJL_RADRFL, "RADSTA": OBJL_RADSTA,
|
||||||
|
"RDOCAL": OBJL_RDOCAL, "RDOSTA": OBJL_RDOSTA,
|
||||||
|
"RECTRC": OBJL_RECTRC, "RESARE": OBJL_RESARE, "RETRFL": OBJL_RETRFL,
|
||||||
|
"RIVERS": OBJL_RIVERS, "RIVBNK": OBJL_RIVBNK,
|
||||||
|
"ROADWY": OBJL_ROADWY, "RUNWAY": OBJL_RUNWAY,
|
||||||
|
"SEAARE": OBJL_SEAARE, "SPLARE": OBJL_SPLARE, "SBDARE": OBJL_SBDARE,
|
||||||
|
"SLCONS": OBJL_SLCONS, "SLOTOP": OBJL_SLOTOP, "SLOGRD": OBJL_SLOGRD,
|
||||||
|
"SOUNDG": OBJL_SOUNDG, "STSLNE": OBJL_STSLNE, "SUBTLN": OBJL_SUBTLN,
|
||||||
|
"SWPARE": OBJL_SWPARE, "TESARE": OBJL_TESARE, "TIDEWY": OBJL_TIDEWY,
|
||||||
|
"TOPMAR": OBJL_TOPMAR,
|
||||||
|
"TSELNE": OBJL_TSELNE, "TSSBND": OBJL_TSSBND, "TSSCRS": OBJL_TSSCRS,
|
||||||
|
"TSSLPT": OBJL_TSSLPT, "TSSRON": OBJL_TSSRON,
|
||||||
|
"TUNNEL": OBJL_TUNNEL, "TWRTPT": OBJL_TWRTPT,
|
||||||
|
"UWTROC": OBJL_UWTROC, "VEGATN": OBJL_VEGATN,
|
||||||
|
"WATTUR": OBJL_WATTUR, "WEDKLP": OBJL_WEDKLP, "WRECKS": OBJL_WRECKS,
|
||||||
|
"M_ACCY": OBJL_M_ACCY, "M_CSCL": OBJL_M_CSCL, "M_COVR": OBJL_M_COVR,
|
||||||
|
"M_HDAT": OBJL_M_HDAT, "M_NSYS": OBJL_M_NSYS, "M_QUAL": OBJL_M_QUAL,
|
||||||
|
"M_SDAT": OBJL_M_SDAT, "M_UNIT": OBJL_M_UNIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
# PRIM codes for FRID
|
||||||
|
PRIM_POINT = 1
|
||||||
|
PRIM_LINE = 2
|
||||||
|
PRIM_AREA = 3
|
||||||
|
|
||||||
|
# ── S-57 attribute codes (ATTL) — from s57attributes.csv (IHO S-57 Ed.3.1) ───
|
||||||
|
# fmt: off
|
||||||
|
ATTL_AGENCY = 1 # AGENCY — agency responsible for production
|
||||||
|
ATTL_BCNSHP = 2 # BCNSHP — beacon shape
|
||||||
|
ATTL_BOYSHP = 4 # BOYSHP — buoy shape
|
||||||
|
ATTL_BURDEP = 5 # BURDEP — buried depth
|
||||||
|
ATTL_CATCAM = 13 # CATCAM — category of cardinal mark
|
||||||
|
ATTL_CATCOV = 18 # CATCOV — category of coverage (1=coverage, 2=no coverage)
|
||||||
|
ATTL_CATFOG = 27 # CATFOG — category of fog signal
|
||||||
|
ATTL_CATLAM = 36 # CATLAM — category of lateral mark (1=port, 2=starboard)
|
||||||
|
ATTL_CATLMK = 35 # CATLMK — category of landmark
|
||||||
|
ATTL_CATLIT = 37 # CATLIT — category of light
|
||||||
|
ATTL_CATOBS = 42 # CATOBS — category of obstruction
|
||||||
|
ATTL_CATREA = 54 # CATREA — category of restricted area
|
||||||
|
ATTL_CATSIW = 63 # CATSIW — category of signal station warning
|
||||||
|
ATTL_COLOUR = 75 # COLOUR — colour (list)
|
||||||
|
ATTL_COLPAT = 76 # COLPAT — colour pattern
|
||||||
|
ATTL_CONDTN = 81 # CONDTN — condition
|
||||||
|
ATTL_DRVAL1 = 90 # DRVAL1 — draft value 1
|
||||||
|
ATTL_DRVAL2 = 91 # DRVAL2 — draft value 2
|
||||||
|
ATTL_HEIGHT = 95 # HEIGHT — height above sea surface
|
||||||
|
ATTL_LITCHR = 107 # LITCHR — light characteristic
|
||||||
|
ATTL_MLTYLT = 110 # MLTYLT — multiplicity of lights
|
||||||
|
ATTL_NATCON = 112 # NATCON — nature of construction
|
||||||
|
ATTL_OBJNAM = 116 # OBJNAM — object name (free text label)
|
||||||
|
ATTL_ORIENT = 117 # ORIENT — orientation (degrees)
|
||||||
|
ATTL_PEREND = 119 # PEREND — period end (season)
|
||||||
|
ATTL_PERSTA = 120 # PERSTA — period start (season)
|
||||||
|
ATTL_QUASOU = 126 # QUASOU — quality of sounding measurement
|
||||||
|
ATTL_SIGGRP = 141 # SIGGRP — signal group ("(2)", etc.)
|
||||||
|
ATTL_SIGPER = 142 # SIGPER — signal period (seconds)
|
||||||
|
ATTL_STATUS = 149 # STATUS — status (permanent, occasional…)
|
||||||
|
ATTL_VALSOU = 179 # VALSOU — value of sounding (metres)
|
||||||
|
ATTL_VERACC = 180 # VERACC — vertical accuracy
|
||||||
|
ATTL_VERCLR = 181 # VERCLR — vertical clearance
|
||||||
|
ATTL_VALNMR = 178 # VALNMR — value of nominal range (nautical miles)
|
||||||
|
ATTL_VERDAT = 187 # VERDAT — vertical datum
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
# Convenience dict: acronym → ATTL code (superset of the named constants above)
|
||||||
|
ATTR_CODE: dict[str, int] = {
|
||||||
|
"AGENCY": 1, "BCNSHP": 2, "BUISHP": 3, "BOYSHP": 4, "BURDEP": 5,
|
||||||
|
"CALSGN": 6, "CATAIR": 7, "CATACH": 8, "CATBRG": 9, "CATBUA": 10,
|
||||||
|
"CATCBL": 11, "CATCAN": 12, "CATCAM": 13, "CATCHP": 14, "CATCOA": 15,
|
||||||
|
"CATCTR": 16, "CATCON": 17, "CATCOV": 18, "CATCRN": 19, "CATDAM": 20,
|
||||||
|
"CATDIS": 21, "CATDOC": 22, "CATDPG": 23, "CATFNC": 24, "CATFRY": 25,
|
||||||
|
"CATFIF": 26, "CATFOG": 27, "CATFOR": 28, "CATGAT": 29, "CATHAF": 30,
|
||||||
|
"CATHLK": 31, "CATICE": 32, "CATINB": 33, "CATLND": 34, "CATLMK": 35,
|
||||||
|
"CATLAM": 36, "CATLIT": 37, "CATMFA": 38, "CATMPA": 39, "CATMOR": 40,
|
||||||
|
"CATNAV": 41, "CATOBS": 42, "CATOFP": 43, "CATOLB": 44, "CATPLE": 45,
|
||||||
|
"CATPIL": 46, "CATPIP": 47, "CATPRA": 48, "CATPYL": 49, "CATQUA": 50,
|
||||||
|
"CATRAS": 51, "CATRTB": 52, "CATROS": 53, "CATREA": 54, "CATSEA": 55,
|
||||||
|
"CATSIT": 56, "CATSLC": 57, "CATSPM": 58, "CATSCF": 59, "CATSUB": 60,
|
||||||
|
"CAATTS": 61, "CATTSS": 62, "CATSIW": 63, "CATTRK": 64, "CATVEG": 65,
|
||||||
|
"CATWED": 66, "CATWRK": 67, "CATZOC": 68, "CATWAT": 69, "COLOUR": 75,
|
||||||
|
"COLPAT": 76, "CONDTN": 81, "CONRAD": 82, "CONVIS": 83, "CURVEL": 84,
|
||||||
|
"DATEND": 85, "DATSTA": 86, "DRVAL1": 90, "DRVAL2": 91, "ELEVAT": 93,
|
||||||
|
"ESTRNG": 94, "HEIGHT": 95, "HORACC": 96, "HORCLR": 97, "HORLEN": 98,
|
||||||
|
"HORWID": 99, "ICEFAC": 100,"INFORM": 101,"JRSDTN": 102,"LIFCAP": 103,
|
||||||
|
"LITCHR": 107, "LITVIS": 108,"MARSYS": 109,"MLTYLT": 110,"NATION": 111,
|
||||||
|
"NATCON": 112, "NATQUA": 113,"NATSUR": 114,"NOBJNM": 115,"OBJNAM": 116,
|
||||||
|
"ORIENT": 117, "PEREND": 119,"PERSTA": 120,"PICREP": 121,"POSACC": 122,
|
||||||
|
"PRCTRY": 124, "PRODCT": 125,"QUASOU": 126,"RADWAL": 127,"RADIUS": 128,
|
||||||
|
"RYRMGV": 133, "SCAMAX": 134,"SCAMIN": 135,"SCVAL1": 136,"SCVAL2": 137,
|
||||||
|
"SECTR1": 138, "SECTR2": 139,"SHIPAM": 140,"SIGGRP": 141,"SIGPER": 142,
|
||||||
|
"SIGSEQ": 143, "SOUACC": 144,"SDISMX": 145,"SDISMN": 146,"SORDAT": 147,
|
||||||
|
"SORIND": 148, "STATUS": 149,"SURATH": 150,"SUREND": 151,"SURSTA": 152,
|
||||||
|
"SURTYP": 153, "TECSOU": 156,"TXTDSC": 157,"TS_TSP": 158,"TS_TSV": 159,
|
||||||
|
"T_ACWL": 160, "T_HWLW": 161,"T_MTOD": 162,"T_THDF": 163,"T_TIMS": 164,
|
||||||
|
"T_TRNP": 165, "T_VAHF": 166,"T_VAVL": 167,"TIMEND": 168,"TIMSTA": 169,
|
||||||
|
"TOPSHP": 170, "TRAFIC": 171,"VALDCO": 172,"VERACC": 180,"VERCLR": 181,
|
||||||
|
"VERCCL": 182, "VERCOP": 183,"VERCSA": 184,"VERDAT": 187,"VERLEN": 188,
|
||||||
|
"WATLEV": 187, "VALSOU": 179, "VALNMR": 178,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── Binary primitives (little-endian, S-57 convention) ─────────────────────────
|
||||||
|
def b11(n): return int(n).to_bytes(1, "little", signed=False)
|
||||||
|
def b12(n): return int(n).to_bytes(2, "little", signed=False)
|
||||||
|
def b14(n): return int(n).to_bytes(4, "little", signed=False)
|
||||||
|
def b21(n): return int(n).to_bytes(1, "little", signed=True)
|
||||||
|
def b22(n): return int(n).to_bytes(2, "little", signed=True)
|
||||||
|
def b24(n): return int(n).to_bytes(4, "little", signed=True)
|
||||||
|
|
||||||
|
|
||||||
|
def A_var(s: str) -> bytes:
|
||||||
|
"""Variable-length ASCII subfield, delimited by UT."""
|
||||||
|
return s.encode("latin-1") + UT
|
||||||
|
|
||||||
|
|
||||||
|
def A_fixed(s: str, n: int) -> bytes:
|
||||||
|
"""Fixed-width ASCII (no UT)."""
|
||||||
|
return s.encode("latin-1").ljust(n, b" ")[:n]
|
||||||
|
|
||||||
|
|
||||||
|
def R_fixed(value: float, n: int = 4) -> bytes:
|
||||||
|
"""Fixed-width ASCII real, e.g. R(4) = '03.1'."""
|
||||||
|
s = f"{value:.{max(0, n-2)}f}"
|
||||||
|
return s.encode("latin-1").ljust(n, b" ")[:n]
|
||||||
|
|
||||||
|
|
||||||
|
def name_5(rcnm: int, rcid: int) -> bytes:
|
||||||
|
"""S-57 NAME field: RCNM (1 byte) + RCID (4 bytes LE) = 5 bytes (= 40 bits)."""
|
||||||
|
return b11(rcnm) + b14(rcid)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Field builders (one per S-57 field tag) ────────────────────────────────────
|
||||||
|
def field_0001(rcid: int) -> bytes:
|
||||||
|
"""Record header (b12 RCID) + FT."""
|
||||||
|
return b12(rcid) + FT
|
||||||
|
|
||||||
|
|
||||||
|
def field_DSID(*, rcnm=10, rcid=1, expp=1, intu=3,
|
||||||
|
dsnm="", edtn="1", updn="0",
|
||||||
|
uadt="", isdt="", sted=3.1,
|
||||||
|
prsp=1, psdn="", pred="2.0",
|
||||||
|
prof=1, agen=999, comt="") -> bytes:
|
||||||
|
return (b11(rcnm) + b14(rcid) + b11(expp) + b11(intu) +
|
||||||
|
A_var(dsnm) + A_var(edtn) + A_var(updn) +
|
||||||
|
A_fixed(uadt, 8) + A_fixed(isdt, 8) +
|
||||||
|
R_fixed(sted, 4) +
|
||||||
|
b11(prsp) + A_var(psdn) + A_var(pred) +
|
||||||
|
b11(prof) + b12(agen) +
|
||||||
|
A_var(comt) + FT)
|
||||||
|
|
||||||
|
|
||||||
|
def field_DSSI(*, dstr=2, aall=1, nall=1, nomr=0, nocr=0,
|
||||||
|
nogr=0, nolr=0, noin=0, nocn=0, noed=0, nofa=0) -> bytes:
|
||||||
|
"""Format: (3b11, 8b14)"""
|
||||||
|
return (b11(dstr) + b11(aall) + b11(nall) +
|
||||||
|
b14(nomr) + b14(nocr) + b14(nogr) + b14(nolr) +
|
||||||
|
b14(noin) + b14(nocn) + b14(noed) + b14(nofa) + FT)
|
||||||
|
|
||||||
|
|
||||||
|
def field_DSPM(*, rcnm=20, rcid=1, hdat=2, vdat=17, sdat=23,
|
||||||
|
cscl=50000, duni=1, huni=1, puni=1, coun=1,
|
||||||
|
comf=10000000, somf=10, comt="") -> bytes:
|
||||||
|
"""Format: (b11, b14, 3b11, b14, 4b11, 2b14, A)
|
||||||
|
HDAT=2 (WGS84), VDAT=17 (MLLW), SDAT=23 (MLLW),
|
||||||
|
DUNI=1 (m), HUNI=1 (m), PUNI=1 (m), COUN=1 (lat/long)
|
||||||
|
COMF=10^7 means coords stored as int = (degrees * 10^7).
|
||||||
|
SOMF=10 means depths stored as int = (metres * 10)."""
|
||||||
|
return (b11(rcnm) + b14(rcid) +
|
||||||
|
b11(hdat) + b11(vdat) + b11(sdat) +
|
||||||
|
b14(cscl) +
|
||||||
|
b11(duni) + b11(huni) + b11(puni) + b11(coun) +
|
||||||
|
b14(comf) + b14(somf) +
|
||||||
|
A_var(comt) + FT)
|
||||||
|
|
||||||
|
|
||||||
|
def field_VRID(*, rcnm: int, rcid: int, rver: int = 1, ruin: int = 1) -> bytes:
|
||||||
|
"""Format: (b11, b14, b12, b11). RUIN=1 means insert."""
|
||||||
|
return b11(rcnm) + b14(rcid) + b12(rver) + b11(ruin) + FT
|
||||||
|
|
||||||
|
|
||||||
|
def field_SG2D(coords_deg: list[tuple[float, float]], comf: int) -> bytes:
|
||||||
|
"""Format: *(b24 YCOO, b24 XCOO). Coords are (lon, lat) in degrees → ints
|
||||||
|
multiplied by COMF (typically 10^7). Note S-57 stores YCOO (lat) before
|
||||||
|
XCOO (lon) for each pair."""
|
||||||
|
out = b""
|
||||||
|
for lon, lat in coords_deg:
|
||||||
|
y_int = int(round(lat * comf))
|
||||||
|
x_int = int(round(lon * comf))
|
||||||
|
out += b24(y_int) + b24(x_int)
|
||||||
|
return out + FT
|
||||||
|
|
||||||
|
|
||||||
|
def field_VRPT(pointers: list[tuple[int, int, int, int, int, int]]) -> bytes:
|
||||||
|
"""Format: *(B(40) NAME, b11 ORNT, b11 USAG, b11 TOPI, b11 MASK).
|
||||||
|
Each item: (rcnm, rcid, ornt, usag, topi, mask)."""
|
||||||
|
out = b""
|
||||||
|
for rcnm, rcid, ornt, usag, topi, mask in pointers:
|
||||||
|
out += name_5(rcnm, rcid) + b11(ornt) + b11(usag) + b11(topi) + b11(mask)
|
||||||
|
return out + FT
|
||||||
|
|
||||||
|
|
||||||
|
def field_FRID(*, rcnm: int = RCNM_FE, rcid: int, prim: int, grup: int = 1,
|
||||||
|
objl: int, rver: int = 1, ruin: int = 1) -> bytes:
|
||||||
|
"""Format: (b11, b14, 2b11, 2b12, b11)."""
|
||||||
|
return (b11(rcnm) + b14(rcid) +
|
||||||
|
b11(prim) + b11(grup) +
|
||||||
|
b12(objl) + b12(rver) +
|
||||||
|
b11(ruin) + FT)
|
||||||
|
|
||||||
|
|
||||||
|
def field_FOID(*, agen: int = 999, fidn: int, fids: int = 1) -> bytes:
|
||||||
|
"""Format: (b12, b14, b12)."""
|
||||||
|
return b12(agen) + b14(fidn) + b12(fids) + FT
|
||||||
|
|
||||||
|
|
||||||
|
def field_ATTF(attrs: list[tuple[int, str]]) -> bytes:
|
||||||
|
"""Format: *(b12 ATTL, A ATVL). Empty list → no field at all (caller skips)."""
|
||||||
|
out = b""
|
||||||
|
for attl, atvl in attrs:
|
||||||
|
out += b12(attl) + A_var(str(atvl))
|
||||||
|
return out + FT
|
||||||
|
|
||||||
|
|
||||||
|
def field_FSPT(pointers: list[tuple[int, int, int, int, int]]) -> bytes:
|
||||||
|
"""Format: *(B(40) NAME, b11 ORNT, b11 USAG, b11 MASK).
|
||||||
|
Each: (rcnm, rcid, ornt, usag, mask). ORNT=1=forward,2=reverse,255=null;
|
||||||
|
USAG=1=exterior,2=interior,3=both,255=null; MASK=1=mask,2=show,255=null."""
|
||||||
|
out = b""
|
||||||
|
for rcnm, rcid, ornt, usag, mask in pointers:
|
||||||
|
out += name_5(rcnm, rcid) + b11(ornt) + b11(usag) + b11(mask)
|
||||||
|
return out + FT
|
||||||
|
|
||||||
|
|
||||||
|
# ── Record builder (data record with entry-map 5504) ───────────────────────────
|
||||||
|
def build_record(fields: list[tuple[str, bytes]]) -> bytes:
|
||||||
|
"""fields: list of (4-char tag, field bytes). Field bytes include trailing FT.
|
||||||
|
|
||||||
|
Uses entry-map "5504": field-length=5 digits (max 99999 bytes per field),
|
||||||
|
position=5 digits (max 99999). Each directory entry is 14 bytes: tag(4)+len(5)+pos(5).
|
||||||
|
|
||||||
|
History of bugs:
|
||||||
|
"3404" (original): 3-digit length = max 999 bytes → LNDARE polygons produce
|
||||||
|
SG2D fields of 2000-53000 bytes → length truncated → GDAL: "Not enough byte
|
||||||
|
to initialize field 'SG2D'".
|
||||||
|
"4404": 4-digit = max 9999 bytes → still not enough for large coastal outlines.
|
||||||
|
"5504": 5-digit = max 99999 bytes → handles any realistic polygon after RDP.
|
||||||
|
|
||||||
|
The DDR template uses "3404" for its own directory, but that only applies to
|
||||||
|
how the DDR is parsed. GDAL reads each data record's leader independently, so
|
||||||
|
data records may use a different entry-map than the DDR — this is valid ISO 8211.
|
||||||
|
"""
|
||||||
|
field_area = b""
|
||||||
|
dir_str = ""
|
||||||
|
pos = 0
|
||||||
|
for tag, data in fields:
|
||||||
|
assert len(tag) == 4, f"tag must be 4 chars: {tag!r}"
|
||||||
|
flen = len(data)
|
||||||
|
assert flen <= 99999, f"field {tag} too large ({flen} bytes); apply more RDP"
|
||||||
|
assert pos <= 99999, f"field area position overflow at {tag} (pos={pos})"
|
||||||
|
dir_str += f"{tag:<4s}{flen:05d}{pos:05d}"
|
||||||
|
field_area += data
|
||||||
|
pos += flen
|
||||||
|
directory = dir_str.encode("latin-1") + FT
|
||||||
|
base_addr = 24 + len(directory)
|
||||||
|
record_len = base_addr + len(field_area)
|
||||||
|
assert record_len <= 99999, f"record too large ({record_len} bytes)"
|
||||||
|
|
||||||
|
leader = (
|
||||||
|
f"{record_len:05d}" # 0-4: record length
|
||||||
|
" " # 5: interchange level (space for DR)
|
||||||
|
"D" # 6: leader id
|
||||||
|
" " # 7: in-line code extension (space for DR)
|
||||||
|
" " # 8: version
|
||||||
|
" " # 9: app indicator
|
||||||
|
" " # 10-11: field control length (00 for DR)
|
||||||
|
f"{base_addr:05d}" # 12-16: base address of field area
|
||||||
|
" " # 17-19: extended character set indicator
|
||||||
|
"5504" # 20-23: entry map (len=5, pos=5, reserved=0, tag=4)
|
||||||
|
).encode("latin-1")
|
||||||
|
assert len(leader) == 24, f"leader len {len(leader)}"
|
||||||
|
return leader + directory + field_area
|
||||||
|
|
||||||
|
|
||||||
|
# ── High-level builder for a complete `.000` ──────────────────────────────────
|
||||||
|
class S57Cell:
|
||||||
|
"""Builds a complete S-57 cell. Call add_* methods then write()."""
|
||||||
|
|
||||||
|
def __init__(self, *, dsnm: str, edition: int = 1, intu: int = 5,
|
||||||
|
scale: int = 50000, agen: int = 999, comt: str = "AR ECDIS custom",
|
||||||
|
issue_date: str = "20260428"):
|
||||||
|
self.dsnm = dsnm
|
||||||
|
self.edition = edition
|
||||||
|
self.intu = intu
|
||||||
|
self.scale = scale
|
||||||
|
self.agen = agen
|
||||||
|
self.comt = comt
|
||||||
|
self.issue_date = issue_date
|
||||||
|
self.comf = 10_000_000 # 10^7 — coords stored as int(deg * COMF)
|
||||||
|
self.somf = 10 # depths × 10
|
||||||
|
|
||||||
|
# Spatial primitives & features accumulated by callers
|
||||||
|
self._next_vi = 1
|
||||||
|
self._next_vc = 1
|
||||||
|
self._next_ve = 1
|
||||||
|
self._next_fid = 1
|
||||||
|
self._records: list[tuple[str, list[tuple[str, bytes]]]] = []
|
||||||
|
|
||||||
|
# Counters for DSSI
|
||||||
|
self._n_meta = 0
|
||||||
|
self._n_pt = 0 # NOMR isolated nodes
|
||||||
|
self._n_cn = 0 # NOCN connected nodes
|
||||||
|
self._n_ed = 0 # NOED edges
|
||||||
|
self._n_geo = 0
|
||||||
|
self._n_col = 0 # NOCR collection
|
||||||
|
self._n_in = 0 # NOIN isolated nodes alt count
|
||||||
|
self._n_lr = 0 # NOLR
|
||||||
|
self._n_gr = 0 # NOGR
|
||||||
|
self._n_fa = 0 # NOFA faces
|
||||||
|
|
||||||
|
# --- Add an isolated node (for points: buoys, lights, landmarks) ---
|
||||||
|
def add_isolated_node(self, lon: float, lat: float) -> tuple[int, int]:
|
||||||
|
rcid = self._next_vi
|
||||||
|
self._next_vi += 1
|
||||||
|
fields = [
|
||||||
|
("0001", field_0001(rcid)),
|
||||||
|
("VRID", field_VRID(rcnm=RCNM_VI, rcid=rcid)),
|
||||||
|
("SG2D", field_SG2D([(lon, lat)], self.comf)),
|
||||||
|
]
|
||||||
|
self._records.append(("VI", fields))
|
||||||
|
self._n_pt += 1
|
||||||
|
self._n_in += 1
|
||||||
|
return (RCNM_VI, rcid)
|
||||||
|
|
||||||
|
# --- Add an edge connecting (start_lon, start_lat) → (end_lon, end_lat) ---
|
||||||
|
# First creates two connected nodes, then the edge that points to them with
|
||||||
|
# intermediate coordinates.
|
||||||
|
def add_edge(self, coords: list[tuple[float, float]]) -> tuple[int, int]:
|
||||||
|
if len(coords) < 2:
|
||||||
|
raise ValueError("edge needs ≥2 coordinates")
|
||||||
|
# Create connected nodes for the endpoints
|
||||||
|
cn_start = self._add_connected_node(*coords[0])
|
||||||
|
cn_end = self._add_connected_node(*coords[-1])
|
||||||
|
# The edge: VRID + VRPT (pointers to both CNs) + SG2D (intermediate coords)
|
||||||
|
rcid = self._next_ve
|
||||||
|
self._next_ve += 1
|
||||||
|
# Intermediate points only (S-57 stores edges as: start_CN, intermediates..., end_CN)
|
||||||
|
intermediate = coords[1:-1]
|
||||||
|
fields = [
|
||||||
|
("0001", field_0001(rcid)),
|
||||||
|
("VRID", field_VRID(rcnm=RCNM_VE, rcid=rcid)),
|
||||||
|
# VRPT: two pointers — to start node, to end node
|
||||||
|
("VRPT", field_VRPT([
|
||||||
|
(RCNM_VC, cn_start[1], 1, 1, 1, 255), # ORNT=1, USAG=1=ext, TOPI=1=begin, MASK=255
|
||||||
|
(RCNM_VC, cn_end[1], 2, 1, 2, 255), # ORNT=2=reverse, TOPI=2=end
|
||||||
|
])),
|
||||||
|
]
|
||||||
|
if intermediate:
|
||||||
|
fields.append(("SG2D", field_SG2D(intermediate, self.comf)))
|
||||||
|
self._records.append(("VE", fields))
|
||||||
|
self._n_ed += 1
|
||||||
|
return (RCNM_VE, rcid)
|
||||||
|
|
||||||
|
def _add_connected_node(self, lon: float, lat: float) -> tuple[int, int]:
|
||||||
|
rcid = self._next_vc
|
||||||
|
self._next_vc += 1
|
||||||
|
fields = [
|
||||||
|
("0001", field_0001(rcid)),
|
||||||
|
("VRID", field_VRID(rcnm=RCNM_VC, rcid=rcid)),
|
||||||
|
("SG2D", field_SG2D([(lon, lat)], self.comf)),
|
||||||
|
]
|
||||||
|
self._records.append(("VC", fields))
|
||||||
|
self._n_cn += 1
|
||||||
|
return (RCNM_VC, rcid)
|
||||||
|
|
||||||
|
# --- Feature: point ---
|
||||||
|
def add_point_feature(self, *, objl: int,
|
||||||
|
lon: float, lat: float,
|
||||||
|
attrs: list[tuple[int, str]] | None = None) -> int:
|
||||||
|
rcid = self._next_fid
|
||||||
|
self._next_fid += 1
|
||||||
|
fid = rcid
|
||||||
|
node_name = self.add_isolated_node(lon, lat)
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
("0001", field_0001(rcid)),
|
||||||
|
("FRID", field_FRID(rcid=rcid, prim=PRIM_POINT, objl=objl)),
|
||||||
|
("FOID", field_FOID(agen=self.agen, fidn=fid)),
|
||||||
|
]
|
||||||
|
if attrs:
|
||||||
|
fields.append(("ATTF", field_ATTF(attrs)))
|
||||||
|
fields.append(("FSPT", field_FSPT([
|
||||||
|
(node_name[0], node_name[1], 255, 255, 255), # ORNT/USAG/MASK = NULL for point
|
||||||
|
])))
|
||||||
|
self._records.append(("FE", fields))
|
||||||
|
self._n_geo += 1
|
||||||
|
return rcid
|
||||||
|
|
||||||
|
# --- Feature: line (e.g. COALNE, DEPCNT) ---
|
||||||
|
def add_line_feature(self, *, objl: int,
|
||||||
|
coords: list[tuple[float, float]],
|
||||||
|
attrs: list[tuple[int, str]] | None = None) -> int:
|
||||||
|
"""coords: ordered list of (lon, lat) vertices (at least 2). The whole
|
||||||
|
polyline is encoded as a single VE edge so that GDAL reassembles it into
|
||||||
|
a LineString geometry. For very long coastlines you can split into multiple
|
||||||
|
calls, each producing a separate feature."""
|
||||||
|
if len(coords) < 2:
|
||||||
|
raise ValueError("line feature needs ≥2 coordinates")
|
||||||
|
edge_name = self.add_edge(coords)
|
||||||
|
rcid = self._next_fid
|
||||||
|
self._next_fid += 1
|
||||||
|
fields = [
|
||||||
|
("0001", field_0001(rcid)),
|
||||||
|
("FRID", field_FRID(rcid=rcid, prim=PRIM_LINE, objl=objl)),
|
||||||
|
("FOID", field_FOID(agen=self.agen, fidn=rcid)),
|
||||||
|
]
|
||||||
|
if attrs:
|
||||||
|
fields.append(("ATTF", field_ATTF(attrs)))
|
||||||
|
fields.append(("FSPT", field_FSPT([
|
||||||
|
(edge_name[0], edge_name[1], 1, 255, 255), # ORNT=1=fwd, USAG=null, MASK=null
|
||||||
|
])))
|
||||||
|
self._records.append(("FE", fields))
|
||||||
|
self._n_geo += 1
|
||||||
|
return rcid
|
||||||
|
|
||||||
|
# --- Feature: area defined by closed boundary polygon ---
|
||||||
|
def add_area_feature(self, *, objl: int,
|
||||||
|
ring: list[tuple[float, float]],
|
||||||
|
attrs: list[tuple[int, str]] | None = None) -> int:
|
||||||
|
"""ring: list of (lon, lat) — must be closed (first == last) or will be."""
|
||||||
|
if ring[0] != ring[-1]:
|
||||||
|
ring = ring + [ring[0]]
|
||||||
|
edge_name = self.add_edge(ring)
|
||||||
|
rcid = self._next_fid
|
||||||
|
self._next_fid += 1
|
||||||
|
fields = [
|
||||||
|
("0001", field_0001(rcid)),
|
||||||
|
("FRID", field_FRID(rcid=rcid, prim=PRIM_AREA, objl=objl)),
|
||||||
|
("FOID", field_FOID(agen=self.agen, fidn=rcid)),
|
||||||
|
]
|
||||||
|
if attrs:
|
||||||
|
fields.append(("ATTF", field_ATTF(attrs)))
|
||||||
|
fields.append(("FSPT", field_FSPT([
|
||||||
|
(edge_name[0], edge_name[1], 1, 1, 255), # ORNT=1=fwd, USAG=1=exterior, MASK=null
|
||||||
|
])))
|
||||||
|
self._records.append(("FE", fields))
|
||||||
|
self._n_geo += 1
|
||||||
|
return rcid
|
||||||
|
|
||||||
|
# --- Build the full file ---
|
||||||
|
def write(self, output_path: str | Path) -> None:
|
||||||
|
if not DDR_TEMPLATE.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"DDR template not found: {DDR_TEMPLATE}. "
|
||||||
|
"Extract noaa_ddr_template.bin from a real ENC first."
|
||||||
|
)
|
||||||
|
with open(DDR_TEMPLATE, "rb") as f:
|
||||||
|
ddr = f.read()
|
||||||
|
|
||||||
|
# Build DSID record (record 2)
|
||||||
|
dsid_record = build_record([
|
||||||
|
("0001", field_0001(2)),
|
||||||
|
("DSID", field_DSID(
|
||||||
|
rcnm=10, rcid=1, expp=1, intu=self.intu,
|
||||||
|
dsnm=self.dsnm, edtn=str(self.edition), updn="0",
|
||||||
|
uadt=self.issue_date, isdt=self.issue_date,
|
||||||
|
sted=3.1, prsp=1, psdn="", pred="2.0",
|
||||||
|
prof=1, agen=self.agen, comt=self.comt,
|
||||||
|
)),
|
||||||
|
("DSSI", field_DSSI(
|
||||||
|
dstr=2, aall=1, nall=1,
|
||||||
|
nomr=self._n_meta, nocr=self._n_col, nogr=self._n_gr,
|
||||||
|
nolr=self._n_lr, noin=self._n_pt, nocn=self._n_cn,
|
||||||
|
noed=self._n_ed, nofa=self._n_fa,
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Build DSPM record (record 3)
|
||||||
|
dspm_record = build_record([
|
||||||
|
("0001", field_0001(3)),
|
||||||
|
("DSPM", field_DSPM(
|
||||||
|
rcnm=20, rcid=1, hdat=2, vdat=17, sdat=23,
|
||||||
|
cscl=self.scale, duni=1, huni=1, puni=1, coun=1,
|
||||||
|
comf=self.comf, somf=self.somf, comt="",
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Build all data records collected by the caller
|
||||||
|
rcid_seq = 4 # records 1=DDR, 2=DSID, 3=DSPM, then sequential
|
||||||
|
body_records = b""
|
||||||
|
for kind, fields in self._records:
|
||||||
|
# Replace the 0001 RCID with global record sequence (per-record-type
|
||||||
|
# numbering would also be valid; simplest is global sequence)
|
||||||
|
new_fields = [
|
||||||
|
("0001", field_0001(rcid_seq)) if t == "0001" else (t, d)
|
||||||
|
for t, d in fields
|
||||||
|
]
|
||||||
|
body_records += build_record(new_fields)
|
||||||
|
rcid_seq += 1
|
||||||
|
|
||||||
|
with open(output_path, "wb") as f:
|
||||||
|
f.write(ddr + dsid_record + dspm_record + body_records)
|
||||||
+4100
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Reference in New Issue
Block a user