Files
AR-GPS/frontend/index.html
T

486 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPS Navigator</title>
<!-- NOTA: NO usar document.documentElement.style.zoom — rompe las coordenadas del mapa.
CSS zoom en el elemento raíz hace que offsetWidth (que OL usa para su viewport) y
getBoundingClientRect (que OL usa para calcular evt.pixel) devuelvan valores en
espacios de coordenadas distintos → desfase sistemático en todo el mapa. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v9.2.4/ol.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="app">
<!-- ── HEADER ──────────────────────────────────────────────────────────── -->
<header>
<!-- Brand -->
<div class="brand">
<img src="assets/images/ar_logo_full.png" class="brand-logo" alt="AR Electronics" />
<span class="brand-name">GPS<span class="brand-sub">NAVIGATOR</span></span>
</div>
<span class="hdr-sep"></span>
<!-- GPS status: dot + port label -->
<div class="hdr-gps">
<span class="status-dot" id="dot-gps"></span>
<span id="lbl-port" class="hdr-port">NO GPS</span>
</div>
<!-- Fix badge — separado del chip GPS -->
<span id="fix-badge" class="fix-badge fix-none">NO FIX</span>
<!-- Botón PORT — ícono pequeño -->
<button class="port-btn" id="btn-connect" onclick="showConnectModal()" title="Configure GPS port"></button>
<!-- Spacer: empuja los modos hacia la derecha -->
<div style="flex:1"></div>
<!-- Selector de modo — segmented control -->
<div class="mode-seg">
<button class="mode-btn" id="mode-night" onclick="setMode('night')">NIGHT</button>
<button class="mode-btn" id="mode-dusk" onclick="setMode('dusk')">DUSK</button>
<button class="mode-btn active" id="mode-day" onclick="setMode('day')">DAY</button>
<button class="mode-btn" id="mode-dayplus" onclick="setMode('dayplus')">DAY+</button>
</div>
<span class="hdr-sep"></span>
<!-- Reloj UTC -->
<div id="utc-clock" class="utc-clock">--:--:-- UTC</div>
</header>
<!-- ── MAIN AREA ────────────────────────────────────────────────────────── -->
<div id="main">
<!-- LEFT PANEL: GPS readout + Waypoints / Routes / NMEA tabs -->
<div id="left-panel">
<!-- ── Tab selector ──────────────────────────────────────────────── -->
<div class="lp-tabs">
<button class="lp-tab active" id="lptab-gps" onclick="lpTab('gps')">GPS</button>
<button class="lp-tab" id="lptab-wpt" onclick="lpTab('wpt')">📍 WPT</button>
<button class="lp-tab" id="lptab-rte" onclick="lpTab('rte')">🗺 RTE</button>
<button class="lp-tab" id="lptab-mrk" onclick="lpTab('mrk')">📍 MRK</button>
<button class="lp-tab" id="lptab-nmea" onclick="lpTab('nmea')">📡 NMEA</button>
</div>
<!-- ── Tab: GPS ───────────────────────────────────────────────────── -->
<div id="lp-gps" class="lp-content">
<div class="lp-section">
<div class="lp-title">POSITION</div>
<div class="readout-big" id="r-lat">--°--'-.--</div>
<div class="readout-big" id="r-lon">--°--'-.--</div>
</div>
<div class="lp-section">
<div class="lp-row">
<div class="lp-field"><div class="lp-lbl">SOG</div><div class="lp-val" id="r-sog">--</div></div>
<div class="lp-field"><div class="lp-lbl">COG</div><div class="lp-val" id="r-cog">--</div></div>
</div>
<div class="lp-row" style="margin-top:6px">
<div class="lp-field"><div class="lp-lbl">COG MAG</div><div class="lp-val" id="r-cogm">--</div></div>
<div class="lp-field"><div class="lp-lbl">MAGVAR</div><div class="lp-val" id="r-magvar">--</div></div>
</div>
</div>
<div class="lp-section">
<div class="lp-row">
<div class="lp-field"><div class="lp-lbl">ALT</div><div class="lp-val" id="r-alt">--</div></div>
<div class="lp-field"><div class="lp-lbl">FIX</div><div class="lp-val" id="r-fix">--</div></div>
</div>
<div class="lp-row" style="margin-top:6px">
<div class="lp-field"><div class="lp-lbl">HDOP</div><div class="lp-val" id="r-hdop">--</div></div>
<div class="lp-field"><div class="lp-lbl">VDOP</div><div class="lp-val" id="r-vdop">--</div></div>
</div>
<div class="lp-row" style="margin-top:6px">
<div class="lp-field"><div class="lp-lbl">PDOP</div><div class="lp-val" id="r-pdop">--</div></div>
<div class="lp-field"><div class="lp-lbl">SATS</div><div class="lp-val" id="r-sats">--</div></div>
</div>
</div>
<!-- Compass + Ecosonda -->
<div class="lp-section" id="sensors-section">
<div class="lp-title">SENSORS</div>
<div class="lp-row">
<div class="lp-field"><div class="lp-lbl">HDG TRUE</div><div class="lp-val" id="r-hdg-t">--</div></div>
<div class="lp-field"><div class="lp-lbl">HDG MAG</div><div class="lp-val" id="r-hdg-m">--</div></div>
</div>
<div class="lp-row" style="margin-top:6px">
<div class="lp-field"><div class="lp-lbl">DEPTH</div><div class="lp-val" id="r-depth">--</div></div>
<div class="lp-field"><div class="lp-lbl">TEMP</div><div class="lp-val" id="r-water-temp">--</div></div>
</div>
</div>
<!-- Active waypoint navigation (GO-TO) -->
<div class="lp-section" id="nav-section" style="display:none">
<div class="lp-title" style="color:var(--cyan)">▶ GOTO</div>
<div class="lp-field" style="margin-bottom:4px">
<div class="lp-lbl">WAYPOINT</div>
<div class="lp-val" id="nav-wpt-name" style="color:var(--cyan)">--</div>
</div>
<div class="lp-row">
<div class="lp-field"><div class="lp-lbl">BRG</div><div class="lp-val" id="nav-brg">--</div></div>
<div class="lp-field"><div class="lp-lbl">DIST</div><div class="lp-val" id="nav-dist">--</div></div>
</div>
<div class="lp-row" style="margin-top:6px">
<div class="lp-field"><div class="lp-lbl">XTE</div><div class="lp-val" id="nav-xte">--</div></div>
<div class="lp-field"><div class="lp-lbl">ETA</div><div class="lp-val" id="nav-eta">--</div></div>
</div>
<button class="small-btn" onclick="stopNav()" style="margin-top:8px;width:100%">■ STOP NAV</button>
</div>
</div><!-- /lp-gps -->
<!-- ── Tab: Waypoints ─────────────────────────────────────────────── -->
<div id="lp-wpt" class="lp-content hidden">
<div class="lp-section-tools">
<button class="small-btn" onclick="addWptFromGPS()">+ FROM GPS</button>
<button class="small-btn" onclick="addWptManual()">+ MANUAL</button>
</div>
<div id="wpt-list" class="item-list"></div>
</div>
<!-- ── Tab: Routes ────────────────────────────────────────────────── -->
<div id="lp-rte" class="lp-content hidden">
<div class="lp-section-tools">
<button class="small-btn" onclick="newRoute()">+ NEW ROUTE</button>
</div>
<div id="route-list" class="item-list"></div>
</div>
<!-- ── Tab: Marcas ──────────────────────────────────────────────────── -->
<div id="lp-mrk" class="lp-content hidden">
<div id="mark-list" class="item-list"></div>
</div>
<!-- ── Tab: NMEA ──────────────────────────────────────────────────── -->
<div id="lp-nmea" class="lp-content hidden" style="padding:8px">
<div id="nmea-log" class="nmea-log"></div>
</div>
<!-- ── Map Tools — siempre visible al fondo del panel ────────────── -->
<div id="lp-maptools">
<div class="mt-label">MAP TOOLS</div>
<!-- Boat center — botón prominente, ancho completo -->
<button class="mt-btn mt-boat-center" onclick="centerOnGPS()" title="Center map on own ship">
⛵ BOAT CENTER
</button>
<div class="mt-grid" style="margin-top:4px">
<button class="mt-btn active" id="btn-north" onclick="setOrientation('N')" title="North Up">N↑</button>
<button class="mt-btn" id="btn-course" onclick="setOrientation('C')" title="Course Up">C↑</button>
<button class="mt-btn" id="btn-track" onclick="toggleTrack()" title="Toggle track">TRK</button>
<button class="mt-btn" onclick="clearTrack()" title="Clear track">✕TRK</button>
<button class="mt-btn" id="btn-draw-wpt" onclick="toggleDrawWpt()" title="Click en el mapa para añadir WPT">✚WPT</button>
<button class="mt-btn" id="btn-draw-route" onclick="toggleDrawRoute()" title="Trazar ruta en el mapa">✚RTE</button>
<button class="mt-btn" id="btn-draw-mark" onclick="openMarcaModal()" title="Colocar marca en el mapa">📍MARCA</button>
<button class="mt-btn" onclick="showChartsModal()" title="ENC charts">⛵ENC</button>
</div>
<!-- ENC detail level -->
<div class="mt-label" style="margin-top:7px">CARTAS ENC</div>
<div class="enc-level-sel">
<button class="enc-lvl active" id="enc-lvl-basic"
onclick="ChartLayer.setDetailLevel('basic')"
title="Balizas + tierra (más rápido)">BÁSICO</button>
<button class="enc-lvl" id="enc-lvl-medium"
onclick="ChartLayer.setDetailLevel('medium')"
title="+ profundidades y peligros">MEDIO</button>
<button class="enc-lvl" id="enc-lvl-advanced"
onclick="openEncLayersModal()"
title="Seleccionar capas ENC">AVANZADO</button>
</div>
</div>
</div><!-- /left-panel -->
<!-- CENTER: Map -->
<div id="map-wrap">
<div id="map"></div>
<!-- Zoom controls — overlay esquina superior derecha del mapa -->
<div id="map-zoom-ctrl">
<button class="map-zoom-btn" onclick="GPSMap.zoomIn()" title="Zoom in">+</button>
<button class="map-zoom-btn" onclick="GPSMap.zoomOut()" title="Zoom out"></button>
</div>
<div id="map-coords">LAT -- &nbsp; LON --</div>
<!-- Chart name under cursor (auto-show) -->
<div id="map-chart-info"></div>
</div>
<!-- RIGHT PANEL: Satellites + Feature Info -->
<div id="right-panel">
<div class="rp-title">SATELLITES</div>
<div class="canvas-wrap">
<canvas id="sky-canvas" width="258" height="258"></canvas>
<div id="sky-labels" class="canvas-labels"></div>
</div>
<div class="rp-title" style="margin-top:10px">SIGNAL</div>
<div class="canvas-wrap">
<canvas id="snr-canvas" width="258" height="150"></canvas>
<div id="snr-labels" class="canvas-labels"></div>
</div>
<div class="rp-sat-count">
<span id="sat-used">0</span> used / <span id="sat-view">0</span> in view
</div>
<!-- Feature / AIS info — aparece al hacer click en el mapa -->
<div id="rp-feat-info" style="display:none"></div>
</div>
</div>
</div><!-- /app -->
<!-- ── MODALS ─────────────────────────────────────────────────────────────── -->
<div id="modal-overlay" class="modal-overlay hidden">
<!-- Connect port -->
<div id="modal-connect" class="modal">
<div class="modal-header">GPS PORT<button class="modal-close" onclick="closeModal()">×</button></div>
<div class="modal-body">
<div class="form-field">
<label class="form-lbl">Port</label>
<select class="form-sel" id="sel-port"></select>
</div>
<div class="form-field">
<label class="form-lbl">Baud rate</label>
<select class="form-sel" id="sel-baud">
<option value="4800">4800</option>
<option value="9600" selected>9600</option>
<option value="38400">38400</option>
<option value="115200">115200</option>
</select>
</div>
<div class="modal-btns">
<button class="btn-primary" onclick="doConnect()">CONNECT</button>
<button class="btn-secondary" onclick="doDisconnect()">DISCONNECT</button>
<button class="btn-secondary" onclick="closeModal()">CANCEL</button>
</div>
</div>
</div>
<!-- Add / Edit waypoint -->
<div id="modal-wpt" class="modal">
<div class="modal-header">WAYPOINT<button class="modal-close" onclick="closeModal()">×</button></div>
<div class="modal-body">
<div class="form-field">
<label class="form-lbl">Name *</label>
<input class="form-inp" id="wpt-name" type="text" placeholder="WPT 001">
</div>
<div class="form-field">
<label class="form-lbl">Latitude</label>
<input class="form-inp" id="wpt-lat" type="number" step="0.00001" placeholder="10.51234">
</div>
<div class="form-field">
<label class="form-lbl">Longitude</label>
<input class="form-inp" id="wpt-lon" type="number" step="0.00001" placeholder="-74.80700">
</div>
<div class="form-field">
<label class="form-lbl">Notes</label>
<input class="form-inp" id="wpt-notes" type="text" placeholder="optional">
</div>
<input type="hidden" id="wpt-id">
<div class="modal-btns">
<button class="btn-primary" onclick="saveWpt()">SAVE</button>
<button class="btn-secondary" onclick="closeModal()">CANCEL</button>
</div>
</div>
</div>
<!-- New / Edit route -->
<div id="modal-route" class="modal">
<div class="modal-header">ROUTE<button class="modal-close" onclick="closeModal()">×</button></div>
<div class="modal-body">
<div class="form-field">
<label class="form-lbl">Name *</label>
<input class="form-inp" id="rte-name" type="text" placeholder="Route 1">
</div>
<div class="form-field">
<label class="form-lbl">Waypoints (select in order)</label>
<div id="rte-wpt-selector" class="wpt-selector"></div>
</div>
<input type="hidden" id="rte-id">
<div class="modal-btns">
<button class="btn-primary" onclick="saveRoute()">SAVE</button>
<button class="btn-secondary" onclick="closeModal()">CANCEL</button>
</div>
</div>
</div>
<!-- Chart management -->
<div id="modal-charts" class="modal hidden" style="max-width:500px">
<div class="modal-header">ENC CHARTS<button class="modal-close" onclick="closeModal()">×</button></div>
<div class="modal-body">
<div style="display:flex;gap:8px;margin-bottom:6px;align-items:center">
<label class="form-lbl" style="margin:0;white-space:nowrap">ENC File</label>
<button class="btn-primary" id="btn-upload-chart" onclick="uploadChart()" style="white-space:nowrap;flex:1">📁 OPEN .000 / .ZIP</button>
</div>
<div style="display:flex;gap:8px;margin-bottom:4px;align-items:center">
<label class="form-lbl" style="margin:0;white-space:nowrap">📂 SD / Path</label>
<input type="text" id="chart-path-inp" placeholder="E:\ENC_Charts"
style="flex:1;font-size:0.72rem;color:var(--text);background:var(--bg3);border:1px solid var(--border);border-radius:3px;padding:3px 6px;font-family:monospace">
<button class="btn-primary" id="btn-scan-chart" onclick="scanChartsPath()" style="white-space:nowrap">SCAN</button>
</div>
<div style="font-size:0.68rem;color:var(--muted);margin-bottom:10px">
IHO S-57 ENC (.000) o NOAA/CIOH ZIP. Pega la ruta de la tarjeta SD para importar todo.
</div>
<div id="chart-cell-list" style="border-top:1px solid var(--border);padding-top:8px;max-height:260px;overflow-y:auto">
<div class="empty-list">No charts installed</div>
</div>
</div>
</div>
<!-- ── ENC Layer selector (AVANZADO) ──────────────────────────────────── -->
<div id="modal-enc-layers" class="modal hidden" style="max-width:340px">
<div class="modal-header">CAPAS ENC
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<div class="el-section-hdr">🌊 PROFUNDIDADES</div>
<div class="enc-layer-group">
<label class="enc-layer-lbl">
<input type="checkbox" id="el-depare" checked>
<div><span class="el-name">Áreas de profundidad</span><span class="el-desc">DEPARE — rellenos azules por rango de calado</span></div>
</label>
<label class="enc-layer-lbl">
<input type="checkbox" id="el-depcnt" checked>
<div><span class="el-name">Veriles / isobatas</span><span class="el-desc">DEPCNT — líneas de igual profundidad</span></div>
</label>
<label class="enc-layer-lbl">
<input type="checkbox" id="el-soundg">
<div><span class="el-name">Sondas</span><span class="el-desc">SOUNDG — valores numéricos de profundidad</span></div>
</label>
</div>
<div class="el-section-hdr">⚠️ PELIGROS Y ZONAS</div>
<div class="enc-layer-group">
<label class="enc-layer-lbl">
<input type="checkbox" id="el-hazards" checked>
<div><span class="el-name">Peligros</span><span class="el-desc">Naufragios, rocas sumergidas, obstrucciones</span></div>
</label>
<label class="enc-layer-lbl">
<input type="checkbox" id="el-zones">
<div><span class="el-name">Zonas náuticas</span><span class="el-desc">Restringidas, fondeo, tráfico separado</span></div>
</label>
</div>
<div class="el-section-hdr">🏝️ TIERRA Y COSTA</div>
<div class="enc-layer-group">
<label class="enc-layer-lbl">
<input type="checkbox" id="el-coalne" checked>
<div><span class="el-name">Línea de costa</span><span class="el-desc">COALNE — contorno de costa S-57</span></div>
</label>
<label class="enc-layer-lbl">
<input type="checkbox" id="el-landmask" checked>
<div><span class="el-name">Relleno de tierra</span><span class="el-desc">LANDMASK — color S-52 beige en áreas de tierra</span></div>
</label>
<label class="enc-layer-lbl">
<input type="checkbox" id="el-lndare">
<div><span class="el-name">Bordes polígono tierra</span><span class="el-desc">LNDARE — puede generar líneas diagonales</span></div>
</label>
<label class="enc-layer-lbl">
<input type="checkbox" id="el-buaare">
<div><span class="el-name">Zonas urbanas</span><span class="el-desc">BUAARE — límites de áreas edificadas</span></div>
</label>
</div>
<div class="el-section-hdr">🗺️ MAPA BASE</div>
<div class="enc-layer-group">
<label class="enc-layer-lbl">
<input type="checkbox" id="el-osm" checked>
<div><span class="el-name">Mapa OSM</span><span class="el-desc">OpenStreetMap — mapa base satelital/calles</span></div>
</label>
</div>
<div class="modal-btns" style="margin-top:14px">
<button class="btn-primary" onclick="applyEncLayers()">APLICAR</button>
<button class="btn-secondary" onclick="closeModal()">CANCELAR</button>
</div>
</div>
</div>
<!-- ── Selector de tipo de MARCA ─────────────────────────────────────── -->
<div id="modal-marca" class="modal hidden" style="max-width:380px">
<div class="modal-header">COLOCAR MARCA
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<p style="font-size:0.78rem;color:var(--muted);margin:0 0 8px">Selecciona el tipo de marca y haz click en el mapa:</p>
<div class="marca-grid" id="marca-type-grid">
<div class="marca-item" data-type="fishing" onclick="selectMarcaType(this)"><span class="marca-icon">🎣</span><span class="marca-label">Pesca</span></div>
<div class="marca-item" data-type="marina" onclick="selectMarcaType(this)"><span class="marca-icon"></span><span class="marca-label">Marina</span></div>
<div class="marca-item" data-type="fuel" onclick="selectMarcaType(this)"><span class="marca-icon"></span><span class="marca-label">Combustible</span></div>
<div class="marca-item" data-type="restaurant" onclick="selectMarcaType(this)"><span class="marca-icon">🍴</span><span class="marca-label">Restaurante</span></div>
<div class="marca-item" data-type="dive" onclick="selectMarcaType(this)"><span class="marca-icon">🤿</span><span class="marca-label">Buceo</span></div>
<div class="marca-item" data-type="anchorage" onclick="selectMarcaType(this)"><span class="marca-icon">🚢</span><span class="marca-label">Fondeo</span></div>
<div class="marca-item" data-type="beach" onclick="selectMarcaType(this)"><span class="marca-icon">🏖️</span><span class="marca-label">Playa</span></div>
<div class="marca-item" data-type="ramp" onclick="selectMarcaType(this)"><span class="marca-icon">🚤</span><span class="marca-label">Rampa</span></div>
<div class="marca-item" data-type="repair" onclick="selectMarcaType(this)"><span class="marca-icon">🔧</span><span class="marca-label">Taller</span></div>
<div class="marca-item" data-type="hospital" onclick="selectMarcaType(this)"><span class="marca-icon">🏥</span><span class="marca-label">Emergencia</span></div>
<div class="marca-item" data-type="customs" onclick="selectMarcaType(this)"><span class="marca-icon">🛂</span><span class="marca-label">Aduana</span></div>
<div class="marca-item" data-type="danger" onclick="selectMarcaType(this)"><span class="marca-icon">⚠️</span><span class="marca-label">Peligro</span></div>
<div class="marca-item" data-type="hotel" onclick="selectMarcaType(this)"><span class="marca-icon">🏨</span><span class="marca-label">Hotel</span></div>
<div class="marca-item" data-type="poi" onclick="selectMarcaType(this)"><span class="marca-icon">📍</span><span class="marca-label">Punto POI</span></div>
</div>
<div style="font-size:0.74rem;color:var(--muted);margin-top:6px" id="marca-type-hint">— Ningún tipo seleccionado —</div>
<div class="modal-btns" style="margin-top:10px">
<button class="btn-primary" id="btn-marca-ok" onclick="startMarcaDraw()" disabled>COLOCAR EN MAPA</button>
<button class="btn-secondary" onclick="closeModal()">CANCELAR</button>
</div>
</div>
</div>
<!-- ── Editar MARCA ───────────────────────────────────────────────────── -->
<div id="modal-mark" class="modal hidden" style="max-width:340px">
<div class="modal-header">EDITAR MARCA
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<input type="hidden" id="mark-id">
<input type="hidden" id="mark-type-val">
<div class="form-group">
<label class="form-label">Nombre</label>
<input type="text" id="mark-name" class="form-input">
</div>
<div class="form-group">
<label class="form-label">Tipo</label>
<div id="mark-type-display" style="font-size:1.2rem;padding:4px 0"></div>
</div>
<div class="form-group">
<label class="form-label">Lat</label>
<input type="number" id="mark-lat" class="form-input" step="0.000001">
</div>
<div class="form-group">
<label class="form-label">Lon</label>
<input type="number" id="mark-lon" class="form-input" step="0.000001">
</div>
<div class="form-group">
<label class="form-label">Notas</label>
<textarea id="mark-notes" class="form-input" rows="2"></textarea>
</div>
<div class="modal-btns">
<button class="btn-primary" onclick="saveMark()">GUARDAR</button>
<button class="btn-secondary" onclick="closeModal()">CANCELAR</button>
</div>
</div>
</div>
</div><!-- /modal-overlay -->
<script src="https://cdn.jsdelivr.net/npm/ol@v9.2.4/dist/ol.js"></script>
<script src="js/skyplot.js"></script>
<script src="js/map.js"></script>
<script src="js/chart_layer.js"></script>
<script src="js/app.js"></script>
<!-- bridge.js must load LAST — it calls bootApp() once QWebChannel is ready -->
<script src="js/bridge.js"></script>
</body>
</html>