Rediseñar símbolos BCNLAT y LIGHTS + land Barranquilla

BCNLAT (_encBeaconCanvas): rectángulo delgado y alto en color IALA.
  Babor  (puerto, verde IALA-B): tope plano = rectángulo puro.
  Estribor (estribor, rojo IALA-B): tope triangular apuntando arriba +
  cuerpo rectangular debajo. Gradiente lateral 3-D. Aplica igual a
  daymarks del ICW Miami y balizas de orilla Colombia.

LIGHTS blanco (_ialaLight): rectángulo cuerpo blanco borde negro,
  estrella de 5 puntas centrada, lagrima estirada purpura a ~20° de
  la vertical saliendo del costado superior del rectángulo (S-52 style).

land.geojson BARRANQUILLA: polígono único trazando la costa desde
  Galerazamba (O) hasta la boca del canal Bocas de Ceniza (E),
  incluyendo ambas riberas del canal y la costa sur hasta el límite
  de la carta. Corrige tierra en 0 features.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 20:37:10 -04:00
parent 00f1eb92d8
commit 750d4ecbd2
2 changed files with 163 additions and 112 deletions
File diff suppressed because one or more lines are too long
+116 -111
View File
@@ -988,44 +988,83 @@ function _ialaBoySpp() {
return c; return c;
} }
// ── Light symbol — slim vertical post with a teardrop flame on top. // ── White-light structure symbol (LIGHTS blanco / faro de orilla) ─────────────
// Colour follows the emitted light: white, red, green, yellow. // Diseño: rectángulo cuerpo blanco con borde negro + estrella de 5 puntas centrada
// + lagrima estirada de color purpura que sale del costado superior hacia arriba
// a ~20° de la vertical (convención ECDIS / S-52 adaptada).
// Para luces de color (rojo/verde) se usa el símbolo de cuerpo de boya en _encLightCanvas.
function _ialaLight(colourCode) { function _ialaLight(colourCode) {
const W = 14, H = 34; const W = 26, H = 46;
const c = document.createElement('canvas'); const c = document.createElement('canvas');
c.width = W; c.height = H; c.width = W; c.height = H;
const ctx = c.getContext('2d'); const ctx = c.getContext('2d');
const cx = W / 2;
// Sólo se invoca para luces blancas (kind==='flare'); defensivamente mapeamos colores.
let col = '#ffffff'; let col = '#ffffff';
if (colourCode === 3) col = '#dd0000'; if (colourCode === 3) col = '#dd1111';
else if (colourCode === 4) col = '#00aa00'; else if (colourCode === 4) col = '#00aa00';
else if (colourCode === 6) col = '#ffcc00'; else if (colourCode === 6) col = '#ffcc00';
const cx = W / 2; // ── Cuerpo rectangulo (estructura del faro) ───────────────────────────────
const bW = 14, bH = 17;
const bTop = H * 0.50; // tope del rectángulo
const bBot = bTop + bH; // base del rectángulo
// Slim vertical post (the structure)
ctx.fillStyle = col;
ctx.strokeStyle = '#111';
ctx.lineWidth = 0.8;
ctx.fillRect(cx - 1.25, 12, 2.5, 18);
ctx.strokeRect(cx - 1.25, 12, 2.5, 18);
// Teardrop flame on top (apex up, rounded base sitting on the post)
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(cx, 1); ctx.rect(cx - bW / 2, bTop, bW, bH);
ctx.bezierCurveTo(cx + 4.5, 3.5, cx + 4.0, 9, cx, 11.5); ctx.fillStyle = col; // blanco (o color para casos defensivos)
ctx.bezierCurveTo(cx - 4.0, 9, cx - 4.5, 3.5, cx, 1);
ctx.closePath();
ctx.fillStyle = col;
ctx.fill(); ctx.fill();
ctx.lineWidth = 0.8;
ctx.strokeStyle = '#111'; ctx.strokeStyle = '#111';
ctx.lineWidth = 1.2;
ctx.stroke(); ctx.stroke();
// Position dot at the bottom (geo anchor) // ── Estrella de 5 puntas centrada en el rectángulo ────────────────────────
const starX = cx, starY = bTop + bH / 2;
const Ro = 4.5, Ri = 1.85; // radio exterior / interior
ctx.beginPath();
for (let i = 0; i < 10; i++) {
const ang = (i * Math.PI / 5) - Math.PI / 2; // empieza arriba
const rr = (i % 2 === 0) ? Ro : Ri;
const px = starX + Math.cos(ang) * rr;
const py = starY + Math.sin(ang) * rr;
i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
}
ctx.closePath();
ctx.fillStyle = (col === '#ffffff') ? '#222' : '#fff'; // contraste
ctx.fill();
// ── Lagrima estirada purpura (haz de luz) ─────────────────────────────────
// Sale del costado superior-derecho del rectángulo, ~20° de la vertical.
const PURP = '#8800cc';
const tearAng = 20 * Math.PI / 180; // 20° desde la vertical
const tearLen = 18; // longitud de la lagrima (px)
ctx.save();
// Ancla en el extremo superior-derecho del rectángulo
ctx.translate(cx + bW / 2 - 1, bTop + 1);
ctx.rotate(tearAng); // rotar 20° hacia la derecha
// Forma de lagrima: estrecha en la base, ancha a la mitad, punta arriba
ctx.beginPath();
ctx.moveTo(0, 0); // base (anclaje)
ctx.bezierCurveTo(-3.8, -5, -3.8, -11, 0, -tearLen);// lado izquierdo
ctx.bezierCurveTo( 3.8, -11, 3.8, -5, 0, 0); // lado derecho
ctx.closePath();
ctx.fillStyle = PURP;
ctx.globalAlpha = 0.88;
ctx.fill();
ctx.globalAlpha = 1.0;
ctx.strokeStyle = 'rgba(90,0,140,0.5)';
ctx.lineWidth = 0.5;
ctx.stroke();
ctx.restore();
// ── Punto de anclaje geográfico en la base ────────────────────────────────
ctx.beginPath(); ctx.arc(cx, H - 2, 2.2, 0, Math.PI * 2); ctx.beginPath(); ctx.arc(cx, H - 2, 2.2, 0, Math.PI * 2);
ctx.fillStyle = '#fff'; ctx.fill(); ctx.fillStyle = '#fff'; ctx.fill();
ctx.beginPath(); ctx.arc(cx, H - 2, 1, 0, Math.PI * 2); ctx.beginPath(); ctx.arc(cx, H - 2, 1.0, 0, Math.PI * 2);
ctx.fillStyle = '#111'; ctx.fill(); ctx.fillStyle = '#111'; ctx.fill();
return c; return c;
@@ -1107,109 +1146,75 @@ function _encLightCanvas(colours, region, sz = 32) {
return c; return c;
} }
// ── Lateral beacon (BCNLAT) — 3-D tripod + pole + topmark ────────────────── // ── Lateral beacon (BCNLAT) — rectángulo delgado alto + tope cuadrado/triangular ──
// Farillos de orilla: estructura fija (no flota). Tríangulo de patas + mástil // Símbolo: cuerpo principal es un rectángulo delgado y alto en el color IALA lateral.
// vertical + marca de tope cuadrada (babor) o cónica (estribor). // Babor (port, IALA-B verde): tope plano → rectángulo puro.
function _encBeaconCanvas(colours, catlam, region, sz = 44) { // Estribor (stbd, IALA-B rojo): tope triangular apuntando hacia arriba.
// Claramente distinguible de boyas flotantes. Funciona igual para faros de orilla
// colombianos y daymarks del ICW de Florida.
function _encBeaconCanvas(colours, catlam, region, sz = 36) {
const c = _mkC(sz); const ctx = c.getContext('2d'); const cx = sz / 2; const c = _mkC(sz); const ctx = c.getContext('2d'); const cx = sz / 2;
let cc = (colours || []).slice(); let cc = (colours || []).slice();
if (!cc.length && catlam) cc = _ialaLateralColours(catlam, region); if (!cc.length && catlam) cc = _ialaLateralColours(catlam, region);
const col = cc[0] ? _s57css(cc[0]) : '#78909c'; const col = cc[0] ? _s57css(cc[0]) : '#78909c';
const isPort = catlam === 1 || catlam === 3; const isPort = catlam === 1 || catlam === 3; // IALA-B: babor=verde, estribor=rojo
// Layout // ── Dimensiones del cuerpo ────────────────────────────────────────────────
const groundY = sz * 0.92; // ground level (shadow base) const bW = sz * 0.30; // ancho del cuerpo (delgado)
const legHub = sz * 0.72; // where tripod legs converge const bTop = sz * 0.07; // tope del cuerpo
const poleBot = legHub; const bBot = sz * 0.90; // base (nivel del suelo)
const poleTop = sz * 0.32; // top of mast const triH = bW * 1.05; // altura del triángulo = ≈ ancho (proporcional)
const tmH = sz * 0.18; // topmark height const bodyTop = isPort ? bTop : bTop + triH; // tope del rectángulo (debajo del tri)
const tmW = sz * 0.46; // topmark width
const tmY = poleTop; // topmark sits at poleTop
// ── Subtle glow ────────────────────────────────────────────────────────── // ── Sombra en la base ─────────────────────────────────────────────────────
const glow = ctx.createRadialGradient(cx, sz * 0.60, 0, cx, sz * 0.60, sz * 0.44); ctx.beginPath();
glow.addColorStop(0, 'rgba(255,255,255,0.0)'); ctx.ellipse(cx, bBot, sz * 0.12, sz * 0.022, 0, 0, Math.PI * 2);
glow.addColorStop(0.65, 'rgba(180,210,255,0.10)'); ctx.fillStyle = 'rgba(0,0,0,0.22)';
glow.addColorStop(1, 'rgba(100,160,255,0.20)'); ctx.fill();
ctx.fillStyle = glow; ctx.fillRect(0, 0, sz, sz);
// ── Ground shadow ───────────────────────────────────────────────────────── // ── Gradiente lateral (efecto 3-D suave) ─────────────────────────────────
ctx.beginPath(); ctx.ellipse(cx, groundY, sz * 0.22, sz * 0.04, 0, 0, Math.PI * 2); const grd = ctx.createLinearGradient(cx - bW / 2, 0, cx + bW / 2, 0);
ctx.fillStyle = 'rgba(0,0,0,0.22)'; ctx.fill(); grd.addColorStop(0, _lighten3D(col, 0.35));
grd.addColorStop(0.45, col);
grd.addColorStop(1, _darken3D(col, 0.28));
// ── Tripod legs (3 legs, perspective depth) ─────────────────────────────── ctx.strokeStyle = 'rgba(0,0,0,0.65)';
// Back leg (darker, stub) ctx.lineWidth = 0.9;
ctx.beginPath(); ctx.moveTo(cx, legHub); ctx.lineTo(cx + sz*0.07, groundY * 0.97);
ctx.strokeStyle = _darken3D(col, 0.50); ctx.lineWidth = 1.2; ctx.stroke();
// Left leg
ctx.beginPath(); ctx.moveTo(cx, legHub); ctx.lineTo(cx - sz*0.28, groundY);
ctx.strokeStyle = _darken3D(col, 0.25); ctx.lineWidth = 1.4; ctx.stroke();
ctx.strokeStyle = _lighten3D(col, 0.15); ctx.lineWidth = 0.5; ctx.stroke();
// Right leg
ctx.beginPath(); ctx.moveTo(cx, legHub); ctx.lineTo(cx + sz*0.28, groundY);
ctx.strokeStyle = _darken3D(col, 0.30); ctx.lineWidth = 1.4; ctx.stroke();
ctx.strokeStyle = _lighten3D(col, 0.10); ctx.lineWidth = 0.5; ctx.stroke();
// ── Hub ring where legs meet ──────────────────────────────────────────────
const hubGrd = ctx.createRadialGradient(cx - sz*0.05, legHub - sz*0.02, 1, cx, legHub, sz*0.11);
hubGrd.addColorStop(0, _lighten3D(col, 0.45));
hubGrd.addColorStop(1, _darken3D(col, 0.30));
ctx.beginPath(); ctx.ellipse(cx, legHub, sz*0.11, sz*0.038, 0, 0, Math.PI*2);
ctx.fillStyle = hubGrd; ctx.fill();
ctx.strokeStyle = '#333'; ctx.lineWidth = 0.5; ctx.stroke();
// ── Mast / pole (thin cylinder with 3-D shading) ─────────────────────────
const pw = sz * 0.07;
const mastGrd = ctx.createLinearGradient(cx - pw/2, 0, cx + pw/2, 0);
mastGrd.addColorStop(0, _darken3D('#888', 0.15));
mastGrd.addColorStop(0.35, '#cccccc');
mastGrd.addColorStop(1, _darken3D('#888', 0.42));
ctx.fillStyle = mastGrd;
ctx.fillRect(cx - pw/2, poleTop, pw, poleBot - poleTop);
ctx.strokeStyle = '#2a2a2a'; ctx.lineWidth = 0.4;
ctx.strokeRect(cx - pw/2, poleTop, pw, poleBot - poleTop);
// ── Topmark (3-D) ─────────────────────────────────────────────────────────
if (isPort) { if (isPort) {
// Square CAN — 3-D cylinder (short, wide) // ── Babor: rectángulo plano (tope cuadrado) ───────────────────────────
const tg = ctx.createLinearGradient(cx - tmW/2, 0, cx + tmW/2, 0); ctx.beginPath();
tg.addColorStop(0, _lighten3D(col, 0.45)); ctx.rect(cx - bW / 2, bTop, bW, bBot - bTop);
tg.addColorStop(0.42, col); ctx.fillStyle = grd;
tg.addColorStop(1, _darken3D(col, 0.45)); ctx.fill();
ctx.fillStyle = tg; ctx.stroke();
ctx.fillRect(cx - tmW/2, tmY - tmH, tmW, tmH); // Highlight lateral izquierdo
// Top highlight ellipse ctx.beginPath();
const ery = tmH * 0.13; ctx.rect(cx - bW / 2, bTop, bW * 0.22, bBot - bTop);
const eg = ctx.createRadialGradient(cx - tmW*0.25, tmY - tmH, 1, cx, tmY - tmH, tmW/2); ctx.fillStyle = 'rgba(255,255,255,0.16)';
eg.addColorStop(0, 'rgba(255,255,255,0.70)'); eg.addColorStop(1, col); ctx.fill();
ctx.beginPath(); ctx.ellipse(cx, tmY - tmH, tmW/2, ery, 0, 0, Math.PI*2);
ctx.fillStyle = eg; ctx.fill();
// Bottom ellipse (shadow)
ctx.beginPath(); ctx.ellipse(cx, tmY, tmW/2, ery, 0, 0, Math.PI*2);
ctx.fillStyle = _darken3D(col, 0.45); ctx.fill();
ctx.strokeStyle = '#222'; ctx.lineWidth = 0.6;
ctx.strokeRect(cx - tmW/2, tmY - tmH, tmW, tmH);
} else { } else {
// CONE pointing up — 3-D gradient // ── Estribor: triángulo (tope apuntado) + rectángulo (cuerpo) ──────────
const apexY = tmY - tmH, baseY2 = tmY; // Triángulo apuntando hacia arriba
const tg = ctx.createLinearGradient(cx - tmW/2, 0, cx + tmW/2, 0);
tg.addColorStop(0, _lighten3D(col, 0.45));
tg.addColorStop(0.42, col);
tg.addColorStop(1, _darken3D(col, 0.45));
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(cx, apexY); ctx.lineTo(cx + tmW/2, baseY2); ctx.lineTo(cx - tmW/2, baseY2); ctx.moveTo(cx, bTop); // vértice superior
ctx.closePath(); ctx.fillStyle = tg; ctx.fill(); ctx.lineTo(cx + bW / 2, bTop + triH); // esquina derecha de la base del tri
// Base ellipse ctx.lineTo(cx - bW / 2, bTop + triH); // esquina izquierda de la base del tri
const ery2 = tmH * 0.10; ctx.closePath();
ctx.beginPath(); ctx.ellipse(cx, baseY2, tmW/2, ery2, 0, 0, Math.PI*2); ctx.fillStyle = grd;
ctx.fillStyle = _darken3D(col, 0.40); ctx.fill(); ctx.fill();
// Highlight streak ctx.stroke();
// Cuerpo rectangular debajo del triángulo
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(cx - tmW*0.05, apexY + tmH*0.08); ctx.rect(cx - bW / 2, bTop + triH, bW, bBot - (bTop + triH));
ctx.lineTo(cx - tmW*0.28, baseY2 - ery2); ctx.fillStyle = grd;
ctx.strokeStyle = 'rgba(255,255,255,0.30)'; ctx.lineWidth = tmW * 0.07; ctx.stroke(); ctx.fill();
ctx.strokeStyle = '#222'; ctx.lineWidth = 0.6; ctx.stroke();
ctx.beginPath(); ctx.moveTo(cx, apexY); ctx.lineTo(cx+tmW/2, baseY2); ctx.lineTo(cx-tmW/2, baseY2); ctx.closePath(); ctx.stroke(); // Highlight lateral izquierdo
ctx.beginPath();
ctx.rect(cx - bW / 2, bTop + triH, bW * 0.22, bBot - (bTop + triH));
ctx.fillStyle = 'rgba(255,255,255,0.16)';
ctx.fill();
} }
return c; return c;