BCNLAT: marca de tope separada del cuerpo por hueco visible

Rediseño del símbolo de baliza lateral para reflejar la estructura
real IALA: palo continuo delgado + cuerpo rectangular (parte baja) +
tramo de palo libre visible (hueco) + marca de tope separada arriba.

  Babor  (IALA-B verde): marca de tope = cuadrado.
  Estribor (IALA-B rojo): marca de tope = triángulo apuntando arriba.

La marca de tope es levemente más ancha que el cuerpo.
El hueco entre cuerpo y marca queda ocupado solo por el poste delgado.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 20:53:49 -04:00
parent 750d4ecbd2
commit 123c983ada
+65 -42
View File
@@ -1146,12 +1146,12 @@ function _encLightCanvas(colours, region, sz = 32) {
return c; return c;
} }
// ── Lateral beacon (BCNLAT) — rectángulo delgado alto + tope cuadrado/triangular ── // ── Lateral beacon (BCNLAT) — poste + cuerpo + HUECO + marca de tope ─────────
// Símbolo: cuerpo principal es un rectángulo delgado y alto en el color IALA lateral. // Estructura real IALA: palo delgado continuo + cuerpo rectangular en la parte
// Babor (port, IALA-B verde): tope plano → rectángulo puro. // inferior + tramo de palo libre (hueco visible) + marca de tope separada arriba.
// Estribor (stbd, IALA-B rojo): tope triangular apuntando hacia arriba. // Babor (port, IALA-B verde): marca de tope = cuadrado.
// Claramente distinguible de boyas flotantes. Funciona igual para faros de orilla // Estribor (stbd, IALA-B rojo): marca de tope = triángulo apuntando arriba.
// colombianos y daymarks del ICW de Florida. // La marca de tope es levemente más ancha que el cuerpo y queda separada de él.
function _encBeaconCanvas(colours, catlam, region, sz = 36) { 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();
@@ -1159,62 +1159,85 @@ function _encBeaconCanvas(colours, catlam, region, sz = 36) {
const col = cc[0] ? _s57css(cc[0]) : '#78909c'; const col = cc[0] ? _s57css(cc[0]) : '#78909c';
const isPort = catlam === 1 || catlam === 3; // IALA-B: babor=verde, estribor=rojo const isPort = catlam === 1 || catlam === 3; // IALA-B: babor=verde, estribor=rojo
// ── Dimensiones del cuerpo ─────────────────────────────────────────────── // ── Layout: poste continuo + cuerpo (bajo) + hueco + marca de tope (arriba)
const bW = sz * 0.30; // ancho del cuerpo (delgado) const groundY = sz * 0.92; // base / nivel del suelo
const bTop = sz * 0.07; // tope del cuerpo const poleTopY = sz * 0.04; // cima del poste
const bBot = sz * 0.90; // base (nivel del suelo) const poleW = sz * 0.055; // grosor del poste delgado
const triH = bW * 1.05; // altura del triángulo = ≈ ancho (proporcional)
const bodyTop = isPort ? bTop : bTop + triH; // tope del rectángulo (debajo del tri)
// ── Sombra en la base ───────────────────────────────────────────────────── // Cuerpo (shaft): rectángulo en la mitad inferior del poste
const bodyW = sz * 0.28;
const bodyTopY = sz * 0.48; // tope del cuerpo → aquí empieza el hueco visible
const bodyBotY = groundY;
// Marca de tope: separada del cuerpo, levemente más ancha
const tmW = sz * 0.36; // ancho de la marca de tope
const tmBotY = sz * 0.27; // base de la marca (límite superior del hueco)
const tmTopY = sz * 0.05; // cima de la marca (= ≈ tope del poste)
const tmH = tmBotY - tmTopY;
// ── Sombra elíptica en la base ────────────────────────────────────────────
ctx.beginPath(); ctx.beginPath();
ctx.ellipse(cx, bBot, sz * 0.12, sz * 0.022, 0, 0, Math.PI * 2); ctx.ellipse(cx, groundY, sz * 0.12, sz * 0.020, 0, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.22)'; ctx.fillStyle = 'rgba(0,0,0,0.22)';
ctx.fill(); ctx.fill();
// ── Gradiente lateral (efecto 3-D suave) ───────────────────────────────── // ── Poste continuo (todo el alto, dibujado primero = detrás) ─────────────
const grd = ctx.createLinearGradient(cx - bW / 2, 0, cx + bW / 2, 0); const poleGrd = ctx.createLinearGradient(cx - poleW, 0, cx + poleW, 0);
grd.addColorStop(0, _lighten3D(col, 0.35)); poleGrd.addColorStop(0, '#999');
grd.addColorStop(0.45, col); poleGrd.addColorStop(0.4, '#ddd');
grd.addColorStop(1, _darken3D(col, 0.28)); poleGrd.addColorStop(1, '#777');
ctx.beginPath();
ctx.rect(cx - poleW / 2, poleTopY, poleW, groundY - poleTopY);
ctx.fillStyle = poleGrd;
ctx.fill();
// ── Gradiente de color IALA (compartido por cuerpo y marca de tope) ───────
const mkGrd = (w) => {
const g = ctx.createLinearGradient(cx - w / 2, 0, cx + w / 2, 0);
g.addColorStop(0, _lighten3D(col, 0.35));
g.addColorStop(0.45, col);
g.addColorStop(1, _darken3D(col, 0.28));
return g;
};
ctx.strokeStyle = 'rgba(0,0,0,0.65)'; ctx.strokeStyle = 'rgba(0,0,0,0.65)';
ctx.lineWidth = 0.9; ctx.lineWidth = 0.9;
// ── Cuerpo (shaft rectangulo, parte inferior) ─────────────────────────────
ctx.beginPath();
ctx.rect(cx - bodyW / 2, bodyTopY, bodyW, bodyBotY - bodyTopY);
ctx.fillStyle = mkGrd(bodyW);
ctx.fill();
ctx.stroke();
// Highlight lateral izquierdo
ctx.beginPath();
ctx.rect(cx - bodyW / 2, bodyTopY, bodyW * 0.20, bodyBotY - bodyTopY);
ctx.fillStyle = 'rgba(255,255,255,0.15)';
ctx.fill();
// ── Marca de tope (separada del cuerpo por el hueco = tramo de poste libre) ─
if (isPort) { if (isPort) {
// ── Babor: rectángulo plano (tope cuadrado) ─────────────────────────── // Babor: cuadrado (square daymark)
ctx.beginPath(); ctx.beginPath();
ctx.rect(cx - bW / 2, bTop, bW, bBot - bTop); ctx.rect(cx - tmW / 2, tmTopY, tmW, tmH);
ctx.fillStyle = grd; ctx.fillStyle = mkGrd(tmW);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
// Highlight lateral izquierdo // Highlight lateral
ctx.beginPath(); ctx.beginPath();
ctx.rect(cx - bW / 2, bTop, bW * 0.22, bBot - bTop); ctx.rect(cx - tmW / 2, tmTopY, tmW * 0.20, tmH);
ctx.fillStyle = 'rgba(255,255,255,0.16)'; ctx.fillStyle = 'rgba(255,255,255,0.15)';
ctx.fill(); ctx.fill();
} else { } else {
// ── Estribor: triángulo (tope apuntado) + rectángulo (cuerpo) ────────── // Estribor: triángulo apuntando hacia arriba (triangle daymark)
// Triángulo apuntando hacia arriba
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(cx, bTop); // vértice superior ctx.moveTo(cx, tmTopY); // vértice superior (apex)
ctx.lineTo(cx + bW / 2, bTop + triH); // esquina derecha de la base del tri ctx.lineTo(cx + tmW / 2, tmBotY); // esquina inferior derecha
ctx.lineTo(cx - bW / 2, bTop + triH); // esquina izquierda de la base del tri ctx.lineTo(cx - tmW / 2, tmBotY); // esquina inferior izquierda
ctx.closePath(); ctx.closePath();
ctx.fillStyle = grd; ctx.fillStyle = mkGrd(tmW);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
// Cuerpo rectangular debajo del triángulo
ctx.beginPath();
ctx.rect(cx - bW / 2, bTop + triH, bW, bBot - (bTop + triH));
ctx.fillStyle = grd;
ctx.fill();
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;