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:
@@ -805,18 +805,27 @@ def _build_cache(cell_id: str, enc_path: Path):
|
||||
log.info("ENC %s → %d depth features", cell_id, len(depths))
|
||||
|
||||
land = _parse_land(enc_path)
|
||||
with open(cell_dir / "land.geojson", "w") as f:
|
||||
json.dump({"type": "FeatureCollection", "features": land}, f)
|
||||
log.info("ENC %s → %d land features", cell_id, len(land))
|
||||
land_path = cell_dir / "land.geojson"
|
||||
# Preserve existing land cache when the .000 has no LNDARE layer
|
||||
# (e.g. custom charts built from CSV that only contain nav aids)
|
||||
if land or not land_path.exists():
|
||||
with open(land_path, "w") as f:
|
||||
json.dump({"type": "FeatureCollection", "features": land}, f)
|
||||
log.info("ENC %s → %d land features%s", cell_id, len(land),
|
||||
" (preserved existing)" if not land and land_path.exists() else "")
|
||||
|
||||
hazards = _parse_hazards(enc_path)
|
||||
with open(cell_dir / "hazards.geojson", "w") as f:
|
||||
json.dump({"type": "FeatureCollection", "features": hazards}, f)
|
||||
hazards_path = cell_dir / "hazards.geojson"
|
||||
if hazards or not hazards_path.exists():
|
||||
with open(hazards_path, "w") as f:
|
||||
json.dump({"type": "FeatureCollection", "features": hazards}, f)
|
||||
log.info("ENC %s → %d hazard features", cell_id, len(hazards))
|
||||
|
||||
zones = _parse_zones(enc_path)
|
||||
with open(cell_dir / "zones.geojson", "w") as f:
|
||||
json.dump({"type": "FeatureCollection", "features": zones}, f)
|
||||
zones_path = cell_dir / "zones.geojson"
|
||||
if zones or not zones_path.exists():
|
||||
with open(zones_path, "w") as f:
|
||||
json.dump({"type": "FeatureCollection", "features": zones}, f)
|
||||
log.info("ENC %s → %d zone features", cell_id, len(zones))
|
||||
|
||||
# Cache count and bbox in meta.json so list_cells() doesn't need to read features.geojson
|
||||
|
||||
+35
-86
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user