fix(data): light info (LITCHR/range/height) en boyas y balizas de orilla
frontend/js/map.js: - Reemplaza merge exacto por merge de proximidad (~50m) en loadChartFeatures para capturar pares LIGHTS/BOYLAT con coordenadas no exactamente iguales - Guard null-canvas en encStyle con fallback visible + console.warn - Mejora JS de debug: log layer/aidType cuando usa fallback backend/services/chart_manager.py: - Expande extraccion de light_desc a category buoy+beacon+landmark (antes solo BOYLAT/BOYCAR; BCNLAT/BCNWTW/LNDMRK perdian LITCHR silenciosamente)
This commit is contained in:
+52
-17
@@ -1645,6 +1645,18 @@ function encStyle(feature, resolution) {
|
||||
}
|
||||
}
|
||||
|
||||
// Safety net: if any draw function threw, canvas may be null. Fall back to
|
||||
// a small colored circle so the feature is still visible and clickable.
|
||||
if (!canvas) {
|
||||
const fc = document.createElement('canvas'); fc.width = fc.height = 16;
|
||||
const fx = fc.getContext('2d');
|
||||
fx.beginPath(); fx.arc(8, 8, 7, 0, Math.PI * 2);
|
||||
fx.fillStyle = colours[0] ? _s57css(colours[0]) : '#607d8b'; fx.fill();
|
||||
fx.strokeStyle = '#fff'; fx.lineWidth = 1.5; fx.stroke();
|
||||
canvas = fc;
|
||||
console.warn('[ENC] canvas fallback for', layer, aidType, colours);
|
||||
}
|
||||
|
||||
// Compute zoom from resolution (deterministic, no map object access needed).
|
||||
// OL WebMercator: resolution ≈ 156543 / 2^zoom → zoom = log2(156543/res)
|
||||
const _zr = resolution ? Math.log2(156543.03392 / Math.max(resolution, 0.001)) : 14;
|
||||
@@ -2005,46 +2017,69 @@ window.loadChartFeatures = async function() {
|
||||
const fc = await r.json();
|
||||
if (!fc.features?.length) return;
|
||||
|
||||
// Merge LIGHTS into co-located structures. NOAA codifies a buoy and its
|
||||
// light as two features at the same coords. The structure often lacks
|
||||
// COLOUR / CATLAM while the light carries it — we backfill the structure
|
||||
// with the light's colour, then drop the (now redundant) LIGHT_POINT.
|
||||
const structByKey = new Map(); // coordKey → structure feature
|
||||
// ── Merge LIGHTS into nearby structure features ────────────────────────
|
||||
// S-57 encodes each buoy/beacon and its light as TWO separate objects.
|
||||
// They may be at exactly the same coordinates (NOAA pattern) or up to
|
||||
// ~50 m apart (≈0.00045°). We try exact match first, then proximity.
|
||||
// After merging: light attrs copied into host, LIGHT_POINT dropped.
|
||||
const MERGE_DEG = 0.00045; // ~50 m — matches backend proximity radius
|
||||
const structList = []; // [{ f, x, y }] — all structure features
|
||||
for (const f of fc.features) {
|
||||
const lyr = (f.properties?.layer || '').toUpperCase();
|
||||
if (lyr.startsWith('BOY') || lyr.startsWith('BCN') || lyr === 'LNDMRK') {
|
||||
const c = f.geometry.coordinates;
|
||||
structByKey.set(`${c[0].toFixed(6)}_${c[1].toFixed(6)}`, f);
|
||||
const [x, y] = f.geometry.coordinates;
|
||||
structList.push({ f, x, y });
|
||||
}
|
||||
}
|
||||
// Fast exact-coord lookup (grid key rounded to 6 dp ≈ 0.11 m)
|
||||
const structByKey = new Map();
|
||||
for (const s of structList) {
|
||||
structByKey.set(`${s.x.toFixed(6)}_${s.y.toFixed(6)}`, s.f);
|
||||
}
|
||||
|
||||
const before = fc.features.length;
|
||||
fc.features = fc.features.filter(f => {
|
||||
if (f.properties?.aid_type !== 'LIGHT_POINT') return true;
|
||||
const c = f.geometry.coordinates;
|
||||
const host = structByKey.get(`${c[0].toFixed(6)}_${c[1].toFixed(6)}`);
|
||||
if (!host) return true; // standalone light — keep
|
||||
// Backfill host colour from light if missing
|
||||
const [lx, ly] = f.geometry.coordinates;
|
||||
|
||||
// 1) Try exact coordinate match
|
||||
let host = structByKey.get(`${lx.toFixed(6)}_${ly.toFixed(6)}`);
|
||||
|
||||
// 2) Proximity fallback (~50 m radius)
|
||||
if (!host) {
|
||||
let bestDist = MERGE_DEG;
|
||||
for (const s of structList) {
|
||||
const d = Math.sqrt((lx - s.x) ** 2 + (ly - s.y) ** 2);
|
||||
if (d < bestDist) { bestDist = d; host = s.f; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!host) return true; // standalone light with no nearby structure — keep
|
||||
|
||||
const hp = host.properties;
|
||||
const lp = f.properties;
|
||||
|
||||
// Backfill colour from light if host has none
|
||||
if (!hp.colours?.length && lp.colours?.length) {
|
||||
hp.colours = lp.colours.slice();
|
||||
// Re-classify host now that we have colour info → infer CATLAM
|
||||
// Infer CATLAM from colour when missing
|
||||
const region = hp.cell_region || 'B';
|
||||
const ialaB = region === 'B';
|
||||
const c0 = lp.colours[0];
|
||||
if (!hp.catlam) {
|
||||
if (c0 === 4) hp.catlam = ialaB ? 1 : 2; // green → port in B, stbd in A
|
||||
else if (c0 === 3) hp.catlam = ialaB ? 2 : 1; // red → stbd in B, port in A
|
||||
if (c0 === 4) hp.catlam = ialaB ? 1 : 2;
|
||||
else if (c0 === 3) hp.catlam = ialaB ? 2 : 1;
|
||||
}
|
||||
// Update aid_type to reflect inferred lateral significance
|
||||
if (hp.catlam === 1) hp.aid_type = 'LATERAL_PORT';
|
||||
else if (hp.catlam === 2) hp.aid_type = 'LATERAL_STBD';
|
||||
}
|
||||
// Carry light attributes (character, range, height) into the host for the popup
|
||||
// Copy light character, range, height → these appear in the tooltip
|
||||
if (lp.light_desc && !hp.light_desc) hp.light_desc = lp.light_desc;
|
||||
if (lp.range_nm && !hp.range_nm) hp.range_nm = lp.range_nm;
|
||||
if (lp.height_m && !hp.height_m) hp.height_m = lp.height_m;
|
||||
return false; // drop the light, host now represents it
|
||||
// Copy orient so the buoy/beacon can anchor its leading line correctly
|
||||
if (lp.orient != null && hp.orient == null) hp.orient = lp.orient;
|
||||
return false; // drop the now-redundant LIGHT_POINT
|
||||
});
|
||||
const dropped = before - fc.features.length;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user