feat: simplify range-daymark to concentric triangles; preserve land cache

map.js — _encRangeDaymarkCanvas:
  Remove tripod legs, hub ring and mast. Symbol is now two clean
  concentric triangles pointing up: outer WHITE + inner BLACK,
  matching the standard IALA dayboard. Optional light flare at apex
  if the feature has LITCHR. No extra structures.

chart_manager.py — _build_cache:
  Don't overwrite existing land/hazard/zone GeoJSON files with an
  empty result when the .000 has no LNDARE/OBSTRN/ZONBND layer.
  Preserves hand-built or legacy cache data for custom charts
  (e.g. Barranquilla built from nav-aids-only CSV).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 16:19:01 -04:00
parent 296c6c1ace
commit aa1b93c7a8
2 changed files with 51 additions and 93 deletions
+35 -86
View File
@@ -1215,105 +1215,54 @@ function _encBeaconCanvas(colours, catlam, region, sz = 44) {
return c;
}
// ── Range / Leading-mark dayboard — dos triángulos concéntricos 3-D ─────────
// Outer triangle: WHITE (visible against any background)
// Inner triangle: BLACK (center — classic range-mark dayboard pattern)
// Both point UP. Mounted on a 3-D post with tripod base and optional light flare.
// This symbol is used for ANY LIGHTS/LNDMRK feature that carries an ORIENT
// attribute, meaning it defines a leading-line bearing.
function _encRangeDaymarkCanvas(colours, hasLight, sz = 52) {
// ── Range / Leading-mark dayboard — dos triángulos concéntricos ──────────────
// Símbolo estándar IALA: triángulo exterior BLANCO + triángulo interior NEGRO.
// Ambos apuntan hacia arriba. Sin patas, sin mástil — solo la marca diurna.
// Flare de luz en el ápice si la feature tiene LITCHR.
function _encRangeDaymarkCanvas(colours, hasLight, sz = 48) {
const c = _mkC(sz); const ctx = c.getContext('2d'); const cx = sz / 2;
// ── Layout ──────────────────────────────────────────────────────────────
const groundY = sz * 0.92;
const legHub = sz * 0.72;
const poleTop = sz * 0.42; // dayboard starts here (base of board)
const boardBot = poleTop;
const boardTop = sz * 0.08; // apex of outer triangle
const boardW = sz * 0.80; // max width of outer triangle at its base
// Light colour (top dot) from feature colour code
// Light colour for optional flare
const lightCss = colours[0] ? _s57css(colours[0]) : '#ffffff';
// ── Ground shadow ────────────────────────────────────────────────────────
ctx.beginPath(); ctx.ellipse(cx, groundY, sz * 0.20, sz * 0.04, 0, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.22)'; ctx.fill();
// ── Layout ───────────────────────────────────────────────────────────────
const triTop = sz * 0.06; // apex of outer triangle
const triBot = sz * 0.92; // base of outer triangle
const halfW = sz * 0.44; // half-width at base
// ── Tripod base ──────────────────────────────────────────────────────────
// Back stub
ctx.beginPath(); ctx.moveTo(cx, legHub); ctx.lineTo(cx + sz*0.06, groundY * 0.97);
ctx.strokeStyle = '#555'; ctx.lineWidth = 1.0; ctx.stroke();
// Left leg
ctx.beginPath(); ctx.moveTo(cx, legHub); ctx.lineTo(cx - sz*0.26, groundY);
ctx.strokeStyle = '#666'; ctx.lineWidth = 1.3; ctx.stroke();
ctx.strokeStyle = '#aaa'; ctx.lineWidth = 0.4; ctx.stroke();
// Right leg
ctx.beginPath(); ctx.moveTo(cx, legHub); ctx.lineTo(cx + sz*0.26, groundY);
ctx.strokeStyle = '#555'; ctx.lineWidth = 1.3; ctx.stroke();
ctx.strokeStyle = '#999'; ctx.lineWidth = 0.4; ctx.stroke();
// ── Hub ring ─────────────────────────────────────────────────────────────
ctx.beginPath(); ctx.ellipse(cx, legHub, sz*0.09, sz*0.030, 0, 0, Math.PI*2);
const hubG = ctx.createRadialGradient(cx - sz*0.04, legHub, 1, cx, legHub, sz*0.09);
hubG.addColorStop(0, '#ccc'); hubG.addColorStop(1, '#555');
ctx.fillStyle = hubG; ctx.fill();
ctx.strokeStyle = '#333'; ctx.lineWidth = 0.4; ctx.stroke();
// ── Mast / pole ───────────────────────────────────────────────────────────
const pw = sz * 0.07;
const mastG = ctx.createLinearGradient(cx - pw/2, 0, cx + pw/2, 0);
mastG.addColorStop(0, '#999'); mastG.addColorStop(0.3, '#eee'); mastG.addColorStop(1, '#666');
ctx.fillStyle = mastG;
ctx.fillRect(cx - pw/2, poleTop, pw, legHub - poleTop);
ctx.strokeStyle = '#333'; ctx.lineWidth = 0.4;
ctx.strokeRect(cx - pw/2, poleTop, pw, legHub - poleTop);
// ── OUTER triangle (WHITE) — 3-D face with gradient ──────────────────────
// The triangle board catches sunlight from upper-left → left face lighter,
// right face darker, giving it a 3-D planar look.
const outerGrad = ctx.createLinearGradient(cx - boardW/2, 0, cx + boardW/2, 0);
outerGrad.addColorStop(0, 'rgba(255,255,255,1.00)'); // lit left edge
outerGrad.addColorStop(0.40,'rgba(240,240,240,1.00)'); // face
outerGrad.addColorStop(1, 'rgba(190,190,190,1.00)'); // shadow right
// ── OUTER triangle — WHITE ───────────────────────────────────────────────
ctx.beginPath();
ctx.moveTo(cx, boardTop);
ctx.lineTo(cx + boardW / 2, boardBot);
ctx.lineTo(cx - boardW / 2, boardBot);
ctx.moveTo(cx, triTop);
ctx.lineTo(cx + halfW, triBot);
ctx.lineTo(cx - halfW, triBot);
ctx.closePath();
ctx.fillStyle = outerGrad; ctx.fill();
// Glow outline
ctx.shadowBlur = 6; ctx.shadowColor = 'rgba(255,255,255,0.8)';
ctx.strokeStyle = 'rgba(255,255,255,0.9)'; ctx.lineWidth = 1.2; ctx.stroke();
ctx.shadowBlur = 0; ctx.shadowColor = 'transparent';
ctx.strokeStyle = '#555'; ctx.lineWidth = 0.7; ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.fill();
// thin dark border so it reads against light backgrounds
ctx.strokeStyle = '#444';
ctx.lineWidth = 1.0;
ctx.stroke();
// ── INNER triangle (BLACK) — 3-D face ────────────────────────────────────
// Inner tri is 45% the size of the outer, centred on the board face.
// Its apex is at (cx, boardTop + inset) and base is near boardBot.
const iW = boardW * 0.45;
const iTop = boardTop + (boardBot - boardTop) * 0.18; // inset from apex
const iBot = boardBot - (boardBot - boardTop) * 0.08; // inset from base
const innerGrad = ctx.createLinearGradient(cx - iW/2, 0, cx + iW/2, 0);
innerGrad.addColorStop(0, '#3a3a3a'); // lit left face (dark but not pure black)
innerGrad.addColorStop(0.45,'#111111'); // center
innerGrad.addColorStop(1, '#000000'); // shadow right
// ── INNER triangle BLACK (45 % of outer) ───────────────────────────────
const iS = 0.45;
const triH = triBot - triTop;
const iTop = triTop + triH * (1 - iS) * 0.50;
const iBot = triBot - triH * (1 - iS) * 0.15;
const iHalf = halfW * iS * ((iBot - triTop) / triH);
ctx.beginPath();
ctx.moveTo(cx, iTop);
ctx.lineTo(cx + iW / 2, iBot);
ctx.lineTo(cx - iW / 2, iBot);
ctx.lineTo(cx + iHalf, iBot);
ctx.lineTo(cx - iHalf, iBot);
ctx.closePath();
ctx.fillStyle = innerGrad; ctx.fill();
// Left-edge highlight streak (3-D illusion)
ctx.beginPath();
ctx.moveTo(cx - iW * 0.04, iTop + (iBot - iTop) * 0.06);
ctx.lineTo(cx - iW * 0.30, iBot - (iBot - iTop) * 0.05);
ctx.strokeStyle = 'rgba(120,120,120,0.55)'; ctx.lineWidth = iW * 0.06; ctx.stroke();
ctx.strokeStyle = '#000'; ctx.lineWidth = 0.5;
ctx.beginPath(); ctx.moveTo(cx, iTop); ctx.lineTo(cx+iW/2, iBot); ctx.lineTo(cx-iW/2, iBot); ctx.closePath(); ctx.stroke();
ctx.fillStyle = '#111111';
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.5;
ctx.stroke();
// ── Light flare at apex (if feature has a light) ───────────────────────────
// ── Light flare at apex ───────────────────────────────────────────────────
if (hasLight) {
_drawLightFlare(ctx, cx, boardTop - sz * 0.02, lightCss);
_drawLightFlare(ctx, cx, triTop - sz * 0.02, lightCss);
}
return c;