Ponemos escala y calculamos millas
This commit is contained in:
1 parent
e6ce241462
commit
049f86727b
2 files changed
+210
-55
No files matched your search
+55
-13
@@ -147,9 +147,11 @@
|
||||
<i class="fa-solid fa-crosshairs mr-1.5 animate-pulse"></i> <span id="status-text">Haz clic para añadir puntos en la carta (A -> B -> C...)</span>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="absolute bottom-3 right-3 bg-slate-950/80 border border-slate-800 text-[9.5px] text-slate-400 px-2.5 py-1 rounded-md backdrop-blur-sm pointer-events-none z-10">
|
||||
Usa el zoom o las barras de desplazamiento para explorar la carta
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
</main>
|
||||
@@ -559,7 +561,6 @@
|
||||
}
|
||||
|
||||
function updateReadouts() {
|
||||
// Cálculo preciso unificado del Rumbo Verdadero (Rv) considerando la diferencia entre regla y brújula
|
||||
let baseReferenceAngle = (360 - state.rulerAngle) % 360;
|
||||
let rv = (state.rulerAngle + 90 - (state.diskAngle - baseReferenceAngle)) % 360;
|
||||
if (rv < 0) rv += 360;
|
||||
@@ -609,10 +610,11 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
// --- GESTIÓN DE RATÓN Y TÁCTIL UNIFICADA ---
|
||||
function handleInteractionStart(clientX, clientY) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const clickX = e.clientX - rect.left;
|
||||
const clickY = e.clientY - rect.top;
|
||||
const clickX = clientX - rect.left;
|
||||
const clickY = clientY - rect.top;
|
||||
|
||||
if (state.mode === 'ADD_POINTS') {
|
||||
const nextChar = String.fromCharCode(65 + state.points.length);
|
||||
@@ -629,7 +631,6 @@
|
||||
if (angleDeg < 0) angleDeg += 360;
|
||||
|
||||
state.rulerAngle = Math.round(angleDeg);
|
||||
// Configuración automática de la rosa según la orientación de la regla
|
||||
state.diskAngle = (360 - state.rulerAngle) % 360;
|
||||
|
||||
document.getElementById('ruler-angle-slider').value = state.rulerAngle;
|
||||
@@ -640,34 +641,75 @@
|
||||
}
|
||||
|
||||
draw();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const dist = Math.hypot(clickX - state.rulerX, clickY - state.rulerY);
|
||||
if (dist < 250) {
|
||||
// Radio de toque amplio para asegurar que coja la regla cómodamente en tablets (aprox 300px)
|
||||
if (dist < 300) {
|
||||
state.isDragging = true;
|
||||
state.dragStartX = clickX - state.rulerX;
|
||||
state.dragStartY = clickY - state.rulerY;
|
||||
canvas.classList.remove('cursor-grab-custom');
|
||||
canvas.classList.add('cursor-grabbing');
|
||||
return true; // Capturado por la regla
|
||||
}
|
||||
});
|
||||
return false; // Tocó fuera de la regla, permitir scroll del mapa
|
||||
}
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
function handleInteractionMove(clientX, clientY) {
|
||||
if (state.isDragging && state.mode === 'MOVE') {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
state.rulerX = (e.clientX - rect.left) - state.dragStartX;
|
||||
state.rulerY = (e.clientY - rect.top) - state.dragStartY;
|
||||
state.rulerX = (clientX - rect.left) - state.dragStartX;
|
||||
state.rulerY = (clientY - rect.top) - state.dragStartY;
|
||||
draw();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
function handleInteractionEnd() {
|
||||
if (state.isDragging) {
|
||||
state.isDragging = false;
|
||||
canvas.classList.remove('cursor-grabbing');
|
||||
canvas.classList.add('cursor-grab-custom');
|
||||
}
|
||||
}
|
||||
|
||||
// Eventos de Ratón
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
handleInteractionStart(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
handleInteractionMove(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
handleInteractionEnd();
|
||||
});
|
||||
|
||||
// Eventos Táctiles (Tablet / Móvil)
|
||||
canvas.addEventListener('touchstart', (e) => {
|
||||
if (e.touches.length === 1) {
|
||||
const touch = e.touches[0];
|
||||
const captured = handleInteractionStart(touch.clientX, touch.clientY);
|
||||
if (captured || state.mode === 'ADD_POINTS') {
|
||||
e.preventDefault(); // Evita scroll solo si estás moviendo la regla o añadiendo puntos
|
||||
}
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
canvas.addEventListener('touchmove', (e) => {
|
||||
if (state.isDragging && e.touches.length === 1) {
|
||||
const touch = e.touches[0];
|
||||
handleInteractionMove(touch.clientX, touch.clientY);
|
||||
e.preventDefault(); // Bloquea el scroll de la página mientras arrastras la regla
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
canvas.addEventListener('touchend', () => {
|
||||
handleInteractionEnd();
|
||||
});
|
||||
|
||||
document.getElementById('disk-angle-slider').addEventListener('input', (e) => {
|
||||
|
||||
+155
-42
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Regla Portland Plotter - Visor Pro Avanzado</title>
|
||||
<title>Regla Portland Plotter - Visor Pro Avanzado con Escala</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
@@ -32,7 +32,7 @@
|
||||
<div>
|
||||
<h1 class="text-sm font-bold text-white tracking-wide flex items-center gap-2">
|
||||
Regla Portland Plotter
|
||||
<span class="text-[9px] bg-amber-500/20 text-amber-300 border border-amber-500/30 px-2 py-0.5 rounded-full font-mono">Modo Rutas Múltiples & Guía</span>
|
||||
<span class="text-[9px] bg-amber-500/20 text-amber-300 border border-amber-500/30 px-2 py-0.5 rounded-full font-mono">Modo Distancias & Escala</span>
|
||||
</h1>
|
||||
<p class="text-[10px] text-slate-400">Simulador Profesional de Alta Fidelidad</p>
|
||||
</div>
|
||||
@@ -48,6 +48,11 @@
|
||||
<div><span class="text-slate-500">Rm/Ra:</span> <span id="readout-rm" class="text-emerald-400 font-bold">090°</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Botón Marcar Escala -->
|
||||
<button id="btn-mode-scale" onclick="toggleScaleMode()" class="bg-cyan-600 hover:bg-cyan-500 text-white font-bold px-3 py-1 rounded-lg text-xs transition-all flex items-center gap-1.5 shadow-md" title="Calibrar escala sobre las latitudes (5 millas)">
|
||||
<i class="fa-solid fa-ruler-combined"></i> <span id="btn-scale-text">Marcar Escala (5M)</span>
|
||||
</button>
|
||||
|
||||
<!-- Botón Ayuda -->
|
||||
<button onclick="toggleHelpModal(true)" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold px-3 py-1 rounded-lg text-xs transition-all flex items-center gap-1.5 shadow-md" title="Guía de uso de la regla Portland">
|
||||
<i class="fa-solid fa-circle-question"></i> Ayuda
|
||||
@@ -101,6 +106,9 @@
|
||||
<input type="range" id="zoom-slider" min="0.5" max="3.0" step="0.05" value="1.0" class="w-full accent-amber-500">
|
||||
<button onclick="adjustZoom(0.1)" class="bg-slate-800 hover:bg-slate-700 text-white px-2.5 py-1 rounded font-bold text-sm">+</button>
|
||||
</div>
|
||||
<div id="scale-status" class="text-[10px] text-cyan-400 font-mono mt-1">
|
||||
Escala: No calibrada (Usar botón Marcar Escala)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-b border-slate-800 pb-1.5 pt-1">
|
||||
@@ -147,11 +155,9 @@
|
||||
<i class="fa-solid fa-crosshairs mr-1.5 animate-pulse"></i> <span id="status-text">Haz clic para añadir puntos en la carta (A -> B -> C...)</span>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="absolute bottom-3 right-3 bg-slate-950/80 border border-slate-800 text-[9.5px] text-slate-400 px-2.5 py-1 rounded-md backdrop-blur-sm pointer-events-none z-10">
|
||||
Usa el zoom o las barras de desplazamiento para explorar la carta
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
</main>
|
||||
@@ -168,20 +174,15 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2.5 leading-relaxed">
|
||||
<p><strong>1. Medir o Trazar un Rumbo Verdadero (Rv):</strong></p>
|
||||
<p><strong>1. Calibrar Escala (Distancias):</strong></p>
|
||||
<ul class="list-disc list-inside space-y-1 text-slate-400 pl-2">
|
||||
<li>Coloca el borde recto de la regla uniendo tu origen y tu destino. Asegúrate de que la <strong>Gran Flecha Azul</strong> apunte en el sentido de la navegación.</li>
|
||||
<li>Gira el disco central de la brújula (Rosa de los Vientos) hasta que las líneas de su cuadrícula interna queden perfectamente paralelas a los <strong>meridianos (líneas Norte-Sur)</strong> de la carta.</li>
|
||||
<li>Lee directamente en el transportador el <strong>Rumbo Verdadero (Rv)</strong> indicado por la marca de lectura fija.</li>
|
||||
<li>Haz clic en <strong>"Marcar Escala"</strong>[cite: 14].</li>
|
||||
<li>Haz clic en el primer punto (Punto 0) sobre la escala de latitudes de la carta[cite: 14].</li>
|
||||
<li>Haz clic en el segundo punto (Punto 1) situado exactamente a <strong>5 millas náuticas</strong> de distancia[cite: 14]. El sistema memorizará la proporción de píxeles por milla[cite: 14].</li>
|
||||
</ul>
|
||||
<p><strong>2. Corrección Total (Ct) y Rumbo de Aguja (Ra):</strong></p>
|
||||
<p><strong>2. Medir o Trazar un Rumbo y Distancias[cite: 3, 10, 11, 12]:</strong></p>
|
||||
<ul class="list-disc list-inside space-y-1 text-slate-400 pl-2">
|
||||
<li>Ajusta la corrección total Ct = (±dm) + (±Δ) en el deslizador lateral.</li>
|
||||
<li>Obtén el Rumbo Magnético o de Aguja (Rm/Ra) aplicando la fórmula clásica de corrección.</li>
|
||||
</ul>
|
||||
<p><strong>3. Trazado de Rutas Múltiples (A ➞ B ➞ C ➞ ...):</strong></p>
|
||||
<ul class="list-disc list-inside space-y-1 text-slate-400 pl-2">
|
||||
<li>Haz clic en <strong>"Añadir Puntos Ruta"</strong> para marcar secuencialmente los waypoints en la carta. El primer tramo (A ➞ B) orientará la regla automáticamente; para los siguientes tramos (B ➞ C ➞ ...), el usuario debe orientar y mover la regla manualmente sobre la línea trazada.</li>
|
||||
<li>Usa <strong>"Añadir Puntos Ruta"</strong> para trazar waypoints ($A \rightarrow B \rightarrow C$)[cite: 10, 11, 12]. Sobre cada línea de ruta se mostrará automáticamente su distancia en millas náuticas[cite: 14].</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pt-2 flex justify-end">
|
||||
@@ -203,8 +204,10 @@
|
||||
dragStartX: 0,
|
||||
dragStartY: 0,
|
||||
zoom: 1.0,
|
||||
mode: 'MOVE',
|
||||
mode: 'MOVE', // 'MOVE', 'ADD_POINTS', 'SCALE_P0', 'SCALE_P1'
|
||||
points: [],
|
||||
scalePoints: [], // Almacena [ {x, y} para P0, {x, y} para P1 ] base a zoom 1.0
|
||||
pixelsPerNauticalMileBase: null, // Píxeles por milla a zoom 1.0
|
||||
customChartImg: null,
|
||||
baseWidth: 1400,
|
||||
baseHeight: 900
|
||||
@@ -259,6 +262,8 @@
|
||||
const scale = img.width > maxW ? maxW / img.width : 1;
|
||||
state.baseWidth = img.width * scale;
|
||||
state.baseHeight = img.height * scale;
|
||||
state.pixelsPerNauticalMileBase = null; // Reiniciar escala al cambiar de carta
|
||||
updateScaleStatusUI();
|
||||
initCanvasSize();
|
||||
centerRulerAbsolute();
|
||||
};
|
||||
@@ -274,6 +279,8 @@
|
||||
state.baseWidth = 1400;
|
||||
state.baseHeight = 900;
|
||||
state.zoom = 1.0;
|
||||
state.pixelsPerNauticalMileBase = null;
|
||||
updateScaleStatusUI();
|
||||
document.getElementById('zoom-slider').value = 1.0;
|
||||
initCanvasSize();
|
||||
centerRulerAbsolute();
|
||||
@@ -295,6 +302,17 @@
|
||||
setZoom(parseFloat(e.target.value));
|
||||
});
|
||||
|
||||
function updateScaleStatusUI() {
|
||||
const st = document.getElementById('scale-status');
|
||||
if (state.pixelsPerNauticalMileBase !== null) {
|
||||
st.innerText = "Escala: Calibrada OK (5 Millas configuradas)";
|
||||
st.className = "text-[10px] text-emerald-400 font-mono mt-1";
|
||||
} else {
|
||||
st.innerText = "Escala: No calibrada (Usar botón Marcar Escala)";
|
||||
st.className = "text-[10px] text-cyan-400 font-mono mt-1";
|
||||
}
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
if (state.customChartImg) {
|
||||
ctx.drawImage(state.customChartImg, 0, 0, canvas.width, canvas.height);
|
||||
@@ -336,7 +354,39 @@
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
|
||||
// Dibujar líneas de ruta encadenadas (A -> B -> C -> ...)
|
||||
// Dibujar puntos y líneas de calibración de escala si existen
|
||||
if (state.scalePoints.length > 0) {
|
||||
state.scalePoints.forEach((sp, idx) => {
|
||||
let currX = sp.x * state.zoom;
|
||||
let currY = sp.y * state.zoom;
|
||||
ctx.fillStyle = '#06b6d4';
|
||||
ctx.beginPath();
|
||||
ctx.arc(currX, currY, 6, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#ffffff';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = '#06b6d4';
|
||||
ctx.font = 'bold 11px sans-serif';
|
||||
ctx.fillText(`Escala P${idx} (${idx === 0 ? '0M' : '5M'})`, currX + 10, currY + 4);
|
||||
});
|
||||
|
||||
if (state.scalePoints.length === 2) {
|
||||
let p0 = { x: state.scalePoints[0].x * state.zoom, y: state.scalePoints[0].y * state.zoom };
|
||||
let p1 = { x: state.scalePoints[1].x * state.zoom, y: state.scalePoints[1].y * state.zoom };
|
||||
ctx.strokeStyle = '#06b6d4';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([3, 3]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p0.x, p0.y);
|
||||
ctx.lineTo(p1.x, p1.y);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
}
|
||||
|
||||
// Dibujar líneas de ruta encadenadas (A -> B -> C -> ...) y sus distancias
|
||||
if (state.points.length > 1) {
|
||||
ctx.strokeStyle = '#f59e0b';
|
||||
ctx.lineWidth = 3;
|
||||
@@ -348,9 +398,41 @@
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Escribir la distancia en millas náuticas sobre cada segmento de ruta si la escala está calibrada
|
||||
if (state.pixelsPerNauticalMileBase !== null) {
|
||||
let currentPixelsPerNM = state.pixelsPerNauticalMileBase * state.zoom;
|
||||
for (let i = 0; i < state.points.length - 1; i++) {
|
||||
let pA = state.points[i];
|
||||
let pB = state.points[i + 1];
|
||||
let distPixels = Math.hypot(pB.x - pA.x, pB.y - pA.y);
|
||||
let distNM = distPixels / currentPixelsPerNM;
|
||||
|
||||
let midX = (pA.x + pB.x) / 2;
|
||||
let midY = (pA.y + pB.y) / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(midX, midY);
|
||||
ctx.fillStyle = 'rgba(15, 23, 42, 0.85)';
|
||||
ctx.strokeStyle = '#f59e0b';
|
||||
ctx.lineWidth = 1;
|
||||
let text = `${distNM.toFixed(2)} M`;
|
||||
ctx.font = 'bold 11px monospace';
|
||||
let textWidth = ctx.measureText(text).width;
|
||||
|
||||
ctx.fillRect(-textWidth / 2 - 6, -10, textWidth + 12, 20);
|
||||
ctx.strokeRect(-textWidth / 2 - 6, -10, textWidth + 12, 20);
|
||||
|
||||
ctx.fillStyle = '#f59e0b';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(text, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibujar marcadores de puntos
|
||||
// Dibujar marcadores de puntos de ruta
|
||||
state.points.forEach(p => {
|
||||
ctx.fillStyle = '#ef4444';
|
||||
ctx.beginPath();
|
||||
@@ -390,7 +472,7 @@
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(rx + 2, ry + 2, rw - 4, rh - 4);
|
||||
|
||||
// Gran flecha azul "DIRECTION OF BEARING" (hacia la derecha en el sistema local)
|
||||
// Gran flecha azul "DIRECTION OF BEARING"
|
||||
ctx.fillStyle = 'rgba(2, 132, 199, 0.85)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(rx + rw - 40, 0);
|
||||
@@ -409,7 +491,6 @@
|
||||
ctx.lineWidth = 0.8;
|
||||
const gridStep = 20;
|
||||
|
||||
// Líneas Verticales (omitiendo el círculo central)
|
||||
for (let x = rx + 30; x <= rx + rw - 30; x += gridStep) {
|
||||
if (Math.abs(x) >= radius + 2) {
|
||||
ctx.beginPath();
|
||||
@@ -419,7 +500,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Líneas Horizontales (omitiendo el círculo central)
|
||||
for (let y = ry + 15; y <= ry + rh - 15; y += gridStep) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(rx + 30, y);
|
||||
@@ -432,7 +512,6 @@
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Marcas milimetradas/graduadas en los bordes superiores e inferiores
|
||||
ctx.fillStyle = '#0f172a';
|
||||
ctx.font = '8px monospace';
|
||||
ctx.textAlign = 'center';
|
||||
@@ -454,7 +533,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// --- DISCO CENTRAL GIRATORIO (TOTALMENTE TRANSPARENTE) ---
|
||||
// --- DISCO CENTRAL GIRATORIO ---
|
||||
ctx.save();
|
||||
const diskAngleRad = (state.diskAngle * Math.PI) / 180;
|
||||
ctx.rotate(diskAngleRad);
|
||||
@@ -465,7 +544,6 @@
|
||||
ctx.arc(0, 0, radius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
// Cuadrícula propia del disco giratorio
|
||||
ctx.strokeStyle = 'rgba(15, 23, 42, 0.4)';
|
||||
ctx.lineWidth = 0.8;
|
||||
const innerStep = 14;
|
||||
@@ -484,7 +562,6 @@
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Marcas de grados del transportador
|
||||
for (let deg = 0; deg < 360; deg += 2) {
|
||||
const rad = (deg * Math.PI) / 180;
|
||||
const isMajor = deg % 10 === 0;
|
||||
@@ -514,7 +591,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// TRIÁNGULO ROJO EN EL NORTE (0º) DEL DISCO
|
||||
ctx.fillStyle = '#ef4444';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, -radius + 12);
|
||||
@@ -523,9 +599,8 @@
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore(); // Fin de rotación del disco
|
||||
ctx.restore();
|
||||
|
||||
// Perno central de la brújula
|
||||
ctx.fillStyle = '#f59e0b';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 7, 0, Math.PI * 2);
|
||||
@@ -538,7 +613,6 @@
|
||||
ctx.arc(0, 0, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// --- MARCA DE LECTURA FIJA (Índice triangular negro a la derecha) ---
|
||||
ctx.save();
|
||||
const markerX = radius;
|
||||
ctx.fillStyle = '#000000';
|
||||
@@ -550,7 +624,7 @@
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
|
||||
ctx.restore(); // Fin de la regla principal
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
@@ -577,6 +651,25 @@
|
||||
document.getElementById('error-val').innerText = `${Math.abs(state.totalError)}° ${state.totalError < 0 ? 'W' : 'E'}`;
|
||||
}
|
||||
|
||||
function toggleScaleMode() {
|
||||
const btn = document.getElementById('btn-mode-scale');
|
||||
const badge = document.getElementById('status-badge');
|
||||
const statusText = document.getElementById('status-text');
|
||||
|
||||
if (state.mode !== 'SCALE_P0' && state.mode !== 'SCALE_P1') {
|
||||
state.mode = 'SCALE_P0';
|
||||
state.scalePoints = [];
|
||||
canvas.className = "cursor-crosshair-custom block m-auto";
|
||||
btn.className = "bg-rose-600 hover:bg-rose-500 text-white font-bold px-3 py-1 rounded-lg text-xs transition-all flex items-center gap-1.5 shadow-md";
|
||||
document.getElementById('btn-scale-text').innerText = "Cancelar Escala";
|
||||
badge.classList.remove('hidden');
|
||||
statusText.innerText = "Haz clic en el Punto 0 (origen de 5 millas en escala de latitud)";
|
||||
} else {
|
||||
resetToMoveMode();
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function toggleRouteMode() {
|
||||
const btn = document.getElementById('btn-mode-ab');
|
||||
const badge = document.getElementById('status-badge');
|
||||
@@ -598,9 +691,14 @@
|
||||
function resetToMoveMode() {
|
||||
state.mode = 'MOVE';
|
||||
canvas.className = "cursor-grab-custom block m-auto";
|
||||
const btn = document.getElementById('btn-mode-ab');
|
||||
btn.className = "bg-amber-500 hover:bg-amber-400 text-slate-950 font-bold px-3.5 py-1 rounded-lg text-xs transition-all flex items-center gap-2 shadow-md";
|
||||
const btnAb = document.getElementById('btn-mode-ab');
|
||||
btnAb.className = "bg-amber-500 hover:bg-amber-400 text-slate-950 font-bold px-3.5 py-1 rounded-lg text-xs transition-all flex items-center gap-2 shadow-md";
|
||||
document.getElementById('btn-ab-text').innerText = "Añadir Puntos Ruta";
|
||||
|
||||
const btnScale = document.getElementById('btn-mode-scale');
|
||||
btnScale.className = "bg-cyan-600 hover:bg-cyan-500 text-white font-bold px-3 py-1 rounded-lg text-xs transition-all flex items-center gap-1.5 shadow-md";
|
||||
document.getElementById('btn-scale-text').innerText = "Marcar Escala (5M)";
|
||||
|
||||
document.getElementById('status-badge').classList.add('hidden');
|
||||
}
|
||||
|
||||
@@ -610,12 +708,30 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
// --- GESTIÓN DE RATÓN Y TÁCTIL UNIFICADA ---
|
||||
function handleInteractionStart(clientX, clientY) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const clickX = clientX - rect.left;
|
||||
const clickY = clientY - rect.top;
|
||||
|
||||
if (state.mode === 'SCALE_P0') {
|
||||
state.scalePoints = [{ x: clickX / state.zoom, y: clickY / state.zoom }];
|
||||
state.mode = 'SCALE_P1';
|
||||
document.getElementById('status-text').innerText = "Haz clic en el Punto 1 (a 5 millas náuticas exactas del Punto 0)";
|
||||
draw();
|
||||
return true;
|
||||
} else if (state.mode === 'SCALE_P1') {
|
||||
state.scalePoints.push({ x: clickX / state.zoom, y: clickY / state.zoom });
|
||||
// Calcular píxeles por milla a zoom 1.0
|
||||
let p0 = state.scalePoints[0];
|
||||
let p1 = state.scalePoints[1];
|
||||
let distPixelsBase = Math.hypot(p1.x - p0.x, p1.y - p0.y);
|
||||
state.pixelsPerNauticalMileBase = distPixelsBase / 5.0; // 5 millas náuticas
|
||||
updateScaleStatusUI();
|
||||
resetToMoveMode();
|
||||
draw();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state.mode === 'ADD_POINTS') {
|
||||
const nextChar = String.fromCharCode(65 + state.points.length);
|
||||
state.points.push({ x: clickX, y: clickY, label: nextChar });
|
||||
@@ -645,16 +761,15 @@
|
||||
}
|
||||
|
||||
const dist = Math.hypot(clickX - state.rulerX, clickY - state.rulerY);
|
||||
// Radio de toque amplio para asegurar que coja la regla cómodamente en tablets (aprox 300px)
|
||||
if (dist < 300) {
|
||||
state.isDragging = true;
|
||||
state.dragStartX = clickX - state.rulerX;
|
||||
state.dragStartY = clickY - state.rulerY;
|
||||
canvas.classList.remove('cursor-grab-custom');
|
||||
canvas.classList.add('cursor-grabbing');
|
||||
return true; // Capturado por la regla
|
||||
return true;
|
||||
}
|
||||
return false; // Tocó fuera de la regla, permitir scroll del mapa
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleInteractionMove(clientX, clientY) {
|
||||
@@ -676,7 +791,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Eventos de Ratón
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
handleInteractionStart(e.clientX, e.clientY);
|
||||
});
|
||||
@@ -689,13 +803,12 @@
|
||||
handleInteractionEnd();
|
||||
});
|
||||
|
||||
// Eventos Táctiles (Tablet / Móvil)
|
||||
canvas.addEventListener('touchstart', (e) => {
|
||||
if (e.touches.length === 1) {
|
||||
const touch = e.touches[0];
|
||||
const captured = handleInteractionStart(touch.clientX, touch.clientY);
|
||||
if (captured || state.mode === 'ADD_POINTS') {
|
||||
e.preventDefault(); // Evita scroll solo si estás moviendo la regla o añadiendo puntos
|
||||
if (captured || state.mode !== 'MOVE') {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}, { passive: false });
|
||||
@@ -704,7 +817,7 @@
|
||||
if (state.isDragging && e.touches.length === 1) {
|
||||
const touch = e.touches[0];
|
||||
handleInteractionMove(touch.clientX, touch.clientY);
|
||||
e.preventDefault(); // Bloquea el scroll de la página mientras arrastras la regla
|
||||
e.preventDefault();
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
@@ -733,4 +846,4 @@
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in new issue
Block a user