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