diff --git a/frontend/js/map.js b/frontend/js/map.js index e50a050..b7966e3 100644 --- a/frontend/js/map.js +++ b/frontend/js/map.js @@ -1146,12 +1146,12 @@ function _encLightCanvas(colours, region, sz = 32) { return c; } -// ── Lateral beacon (BCNLAT) — rectángulo delgado alto + tope cuadrado/triangular ── -// Símbolo: cuerpo principal es un rectángulo delgado y alto en el color IALA lateral. -// Babor (port, IALA-B verde): tope plano → rectángulo puro. -// 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. +// ── Lateral beacon (BCNLAT) — poste + cuerpo + HUECO + marca de tope ───────── +// Estructura real IALA: palo delgado continuo + cuerpo rectangular en la parte +// inferior + tramo de palo libre (hueco visible) + marca de tope separada arriba. +// Babor (port, IALA-B verde): marca de tope = cuadrado. +// Estribor (stbd, IALA-B rojo): marca de tope = triángulo apuntando arriba. +// La marca de tope es levemente más ancha que el cuerpo y queda separada de él. function _encBeaconCanvas(colours, catlam, region, sz = 36) { const c = _mkC(sz); const ctx = c.getContext('2d'); const cx = sz / 2; let cc = (colours || []).slice(); @@ -1159,62 +1159,85 @@ function _encBeaconCanvas(colours, catlam, region, sz = 36) { const col = cc[0] ? _s57css(cc[0]) : '#78909c'; const isPort = catlam === 1 || catlam === 3; // IALA-B: babor=verde, estribor=rojo - // ── Dimensiones del cuerpo ──────────────────────────────────────────────── - const bW = sz * 0.30; // ancho del cuerpo (delgado) - const bTop = sz * 0.07; // tope del cuerpo - const bBot = sz * 0.90; // base (nivel del suelo) - const triH = bW * 1.05; // altura del triángulo = ≈ ancho (proporcional) - const bodyTop = isPort ? bTop : bTop + triH; // tope del rectángulo (debajo del tri) + // ── Layout: poste continuo + cuerpo (bajo) + hueco + marca de tope (arriba) ─ + const groundY = sz * 0.92; // base / nivel del suelo + const poleTopY = sz * 0.04; // cima del poste + const poleW = sz * 0.055; // grosor del poste delgado - // ── 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.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.fill(); - // ── Gradiente lateral (efecto 3-D suave) ───────────────────────────────── - const grd = ctx.createLinearGradient(cx - bW / 2, 0, cx + bW / 2, 0); - grd.addColorStop(0, _lighten3D(col, 0.35)); - grd.addColorStop(0.45, col); - grd.addColorStop(1, _darken3D(col, 0.28)); + // ── Poste continuo (todo el alto, dibujado primero = detrás) ───────────── + const poleGrd = ctx.createLinearGradient(cx - poleW, 0, cx + poleW, 0); + poleGrd.addColorStop(0, '#999'); + poleGrd.addColorStop(0.4, '#ddd'); + 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.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) { - // ── Babor: rectángulo plano (tope cuadrado) ─────────────────────────── + // Babor: cuadrado (square daymark) ctx.beginPath(); - ctx.rect(cx - bW / 2, bTop, bW, bBot - bTop); - ctx.fillStyle = grd; + ctx.rect(cx - tmW / 2, tmTopY, tmW, tmH); + ctx.fillStyle = mkGrd(tmW); ctx.fill(); ctx.stroke(); - // Highlight lateral izquierdo + // Highlight lateral ctx.beginPath(); - ctx.rect(cx - bW / 2, bTop, bW * 0.22, bBot - bTop); - ctx.fillStyle = 'rgba(255,255,255,0.16)'; + ctx.rect(cx - tmW / 2, tmTopY, tmW * 0.20, tmH); + ctx.fillStyle = 'rgba(255,255,255,0.15)'; ctx.fill(); } else { - // ── Estribor: triángulo (tope apuntado) + rectángulo (cuerpo) ────────── - // Triángulo apuntando hacia arriba + // Estribor: triángulo apuntando hacia arriba (triangle daymark) ctx.beginPath(); - ctx.moveTo(cx, bTop); // vértice superior - ctx.lineTo(cx + bW / 2, bTop + triH); // esquina derecha de la base del tri - ctx.lineTo(cx - bW / 2, bTop + triH); // esquina izquierda de la base del tri + ctx.moveTo(cx, tmTopY); // vértice superior (apex) + ctx.lineTo(cx + tmW / 2, tmBotY); // esquina inferior derecha + ctx.lineTo(cx - tmW / 2, tmBotY); // esquina inferior izquierda ctx.closePath(); - ctx.fillStyle = grd; + ctx.fillStyle = mkGrd(tmW); ctx.fill(); 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;