Etiquetas

Captura y localiza


 indica las diferentes formas de manejar una coordenada :

creamos el primer componente :

import { memo, useRef, useMemo, useCallback, useEffect, useState } from 'react';
import { MapContainer, TileLayer, LayersControl, LayerGroup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css'; // Importa los estilos CSS de Leaflet.

import ZoomDisplay from './Zoommap'; // Componente para mostrar el nivel de zoom del mapa.
import UbicarCoordenadaDD from './UbucarDD'; // Componente para ubicar el mapa mediante coordenadas decimales (DD).
import UbicarCoordenadadms from './UbicarDMS'; // Componente para ubicar el mapa mediante coordenadas en grados, minutos y segundos (DMS).
import Ubicardm from './UbicarDM'; // Componente para ubicar el mapa mediante coordenadas en grados y minutos decimales (DM).
import UbicarMGRS from './UbicarGRMS'; // Componente para ubicar el mapa mediante el sistema de referencia de cuadrícula militar (MGRS).
import UbicarMetrica from './UbicarMetrica'; // Componente para ubicar el mapa mediante coordenadas métricas.
import CapturaClick from './CapturaClick'; // Componente para capturar clics en el mapa y posiblemente mostrar información.

// 🔹 Estilos y propiedades constantes
const mapStyles = {
  position: 'absolute', // Permite que el mapa ocupe todo el contenedor padre.
  top: 0,
  left: 0,
  right: 0,
  bottom: 0,
  overflow: 'hidden', // Oculta cualquier parte del mapa que se salga de los límites.
  touchAction: 'none', // Deshabilita las acciones táctiles predeterminadas del navegador en el mapa.
  zIndex: 0, // Asegura que el mapa esté en la capa base de apilamiento.
  willChange: 'transform', // Indica al navegador que la propiedad 'transform' va a cambiar (optimización).
  backfaceVisibility: 'hidden', // Oculta la parte posterior del elemento durante las transformaciones 3D (optimización).
  backgroundColor: '#f0f0f0', // Color de fondo del contenedor del mapa.
};

const tileLayerProps = {
  url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', // URL de los tiles de OpenStreetMap.
  attribution: '© OpenStreetMap contributors', // Atribución requerida por OpenStreetMap.
  updateWhenIdle: true, // Actualiza los tiles solo cuando el mapa está quieto.
  updateWhenZooming: false, // No actualiza los tiles durante el zoom (mejora la fluidez).
  keepBuffer: 4, // Número de tiles a mantener en el buffer alrededor de la vista actual.
  noWrap: true, // Evita la repetición horizontal de los tiles.
  detectRetina: false, // Deshabilita la detección de pantallas Retina.
  bounds: [ // Límites geográficos para la visualización de los tiles.
    [-85, -175], // Suroeste.
    [85, 175],   // Noreste.
  ],
};

const mapOptions = {
  center: [19.285653, -99.147809] as [number, number], // Centro inicial del mapa (Ciudad de México).
  zoom: 5, // Nivel de zoom inicial.
  preferCanvas: true, // Utiliza Canvas para renderizar los tiles (puede mejorar el rendimiento).
  fadeAnimation: false, // Deshabilita la animación de fundido al cargar nuevos tiles.
  attributionControl: false, // Oculta el control de atribución de Leaflet.
  zoomControl: false, // Oculta los controles de zoom por defecto de Leaflet.
  maxBoundsViscosity: 1.0, // Fuerza el mapa a mantenerse dentro de los límites máximos.
  maxBounds: [ // Límites máximos del mapa.
    [-85, -175], // Suroeste.
    [85, 175],   // Noreste.
  ],
  worldCopyJump: false, // Deshabilita el salto de copia del mundo al hacer zoom out.
};

// 🔹 Botón Toggle reusable
// Componente funcional memoizado para crear botones que activan/desactivan funcionalidades.
const ToggleButton = memo(({ children, label, top, left, isActive, onClick }) => (
  <>
    <button
      onClick={onClick} // Llama a la función onClick proporcionada al hacer clic.
      style={{
        position: 'absolute', // Permite posicionar el botón exactamente en las coordenadas.
        top, // Posición vertical del botón.
        left, // Posición horizontal del botón.
        zIndex: 1000, // Asegura que el botón esté por encima del mapa.
        backgroundColor: isActive ? '#4CAF50' : '#f44336', // Cambia el color según si el botón está activo.
        color: 'white', // Color del texto del botón.
        padding: '5px 10px', // Espaciado interno del botón.
        border: 'none', // Elimina el borde del botón.
        borderRadius: '4px', // Bordes ligeramente redondeados.
        cursor: 'pointer', // Cambia el cursor a una mano al pasar por encima.
      }}
    >
      {label} {/* Texto del botón. */}
    </button>
    {isActive && children} {/* Renderiza los componentes hijos solo si el botón está activo. */}
  </>
));

// Componente funcional principal para mostrar el mapa y las funcionalidades de ubicación.
const MapViewCapLoc = () => {
  const mapRef = useRef(null); // Referencia para acceder a la instancia del componente MapContainer de Leaflet.
  const tileLayerRef = useRef(null); // Referencia para acceder a la instancia del componente TileLayer de Leaflet.
  const layerGroupRef = useRef(null); // Referencia para agrupar capas vectoriales (como marcadores de ubicación).
  const [activeButton, setActiveButton] = useState<string | null>(null); // Estado para controlar qué botón de funcionalidad está activo.

  // Función useCallback para manejar los cambios en la vista del mapa (zoom, pan).
  const handleViewportChanged = useCallback(() => {
    const map = mapRef.current;
    if (map) {
      const center = map.getCenter(); // Obtiene las coordenadas del centro del mapa.
      const zoom = map.getZoom(); // Obtiene el nivel de zoom actual del mapa.
      console.log('Viewport changed:', { center, zoom }); // Registra en la consola los cambios de la vista.
    }
  }, []); // El array de dependencias vacío significa que esta función no se recreará en cada renderizado.

  // Efecto para adjuntar/desadjuntar un listener al evento 'moveend' del mapa.
  useEffect(() => {
    const map = mapRef.current;
    if (map) {
      map.on('moveend', handleViewportChanged); // Adjunta el listener al evento 'moveend' (cuando el movimiento del mapa termina).
      return () => map.off('moveend', handleViewportChanged); // Función de limpieza para remover el listener al desmontar el componente.
    }
  }, [handleViewportChanged]); // El efecto se ejecuta cuando la función 'handleViewportChanged' cambia.

  // Función useCallback para manejar el cambio de estado de los botones de funcionalidad.
  const handleToggleButton = useCallback(
    (buttonId: string) => {
      setActiveButton(prev => (prev === buttonId ? null : buttonId)); // Si el botón ya está activo, lo desactiva; si no, lo activa.
    },
    [] // El array de dependencias vacío significa que esta función no se recreará en cada renderizado.
  );

  return (
    <div className="map-container">
      {/* Componente principal de react-leaflet que contiene el mapa. */}
      <MapContainer {...mapOptions} ref={mapRef} style={mapStyles}>
        {/* Componente para mostrar la información del zoom actual. */}
        <ZoomDisplay />
        {/* Componente para controlar las capas base del mapa. */}
        <LayersControl position="topright">
          {/* Capa base de OpenStreetMap. */}
          <LayersControl.BaseLayer name="OpenStreetMap" checked>
            {/* Componente para renderizar los tiles del mapa. */}
            <TileLayer ref={tileLayerRef} {...tileLayerProps} />
          </LayersControl.BaseLayer>
        </LayersControl>
        {/* Grupo de capas para elementos vectoriales que se añaden al mapa (ej. marcadores). */}
        <LayerGroup ref={layerGroupRef}>
          {/* Componente para capturar clics en el mapa y realizar alguna acción. */}
          <CapturaClick layerGroupRef={layerGroupRef} />
        </LayerGroup>
      </MapContainer>

      {/* Botones para las diferentes funcionalidades de ubicación. */}
      <ToggleButton label="Ubicar DD" top={10} left={490} isActive={activeButton === 'dd'} onClick={() => handleToggleButton('dd')}>
        <UbicarCoordenadaDD layerGroupRef={layerGroupRef} mapRef={mapRef} />
      </ToggleButton>
      <ToggleButton label="Ubicar DMS" top={10} left={670} isActive={activeButton === 'dms'} onClick={() => handleToggleButton('dms')}>
        <UbicarCoordenadadms layerGroupRef={layerGroupRef} mapRef={mapRef} />
      </ToggleButton>
      <ToggleButton label="Ubicar DM" top={50} left={390} isActive={activeButton === 'dm'} onClick={() => handleToggleButton('dm')}>
        <Ubicardm layerGroupRef={layerGroupRef} mapRef={mapRef} />
      </ToggleButton>
      <ToggleButton label="Ubicar MGRS" top={50} left={490} isActive={activeButton === 'mgrs'} onClick={() => handleToggleButton('mgrs')}>
        <UbicarMGRS layerGroupRef={layerGroupRef} mapRef={mapRef} />
      </ToggleButton>
      <ToggleButton label="Ubicar Métricas" top={50} left={670} isActive={activeButton === 'metricas'} onClick={() => handleToggleButton('metricas')}>
        <UbicarMetrica layerGroupRef={layerGroupRef} mapRef={mapRef} />
      </ToggleButton>
    </div>
  );
};

// 🔹 Estilos globales aplicados dinámicamente al head del documento.
if (typeof document !== 'undefined') {
  const style = document.createElement('style');
  style.textContent = `
    .map-container {
      width: 100vw;
      height: 100vh;
      position: relative;
      background: #f0f0f0;
      overflow: hidden;
      isolation: isolate; /* Crea un nuevo contexto de apilamiento para evitar problemas con elementos externos. */
    }
    .error-fallback {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background: rgba(255, 255, 255, 0.9);
      z-index: 1000;
    }
  `;
  document.head.appendChild(style); // Añade el elemento <style> al <head> del documento.
}

export default memo(MapViewCapLoc); // Envuelve el componente con 'memo' para optimizarlo, evitando re-renderizados innecesarios si las props no cambian.

siguiente componente :


import { useState, useEffect } from 'react';
import { useMapEvent, useMap } from 'react-leaflet';

// Componente funcional de React para mostrar el nivel de zoom y la escala del mapa.
const ZoomDisplay = () => {
    // Estado para almacenar el nivel de zoom actual del mapa. Inicializado a null.
    const [zoom, setZoom] = useState<number | null>(null);
    // Estado para almacenar la escala actual del mapa como una cadena de texto. Inicializado a null.
    const [scale, setScale] = useState<string | null>(null);
    // Hook para acceder a la instancia del mapa de Leaflet.
    const map = useMap();

    // Estilos en línea para el contenedor que mostrará el zoom y la escala.
    const containerStyle = {
        position: 'absolute', // Para posicionarlo sobre el mapa.
        bottom: '20px',     // Ubicado en la parte inferior.
        left: '50%',       // Centrado horizontalmente.
        transform: 'translateX(-50%)', // Ajuste para centrado preciso.
        backgroundColor: 'rgba(255, 255, 255, 0.7)', // Fondo blanco semitransparente.
        padding: '5px 10px', // Espaciado interno.
        borderRadius: '5px', // Bordes redondeados.
        border: '2px solid rgba(0, 0, 0, 0.2)', // Borde sutil.
        zIndex: 1000,       // Asegura que esté sobre otros elementos del mapa.
        boxShadow: '0 0 10px rgba(0, 0, 0, 0.2)', // Sombra suave.
        fontFamily: 'Arial, sans-serif', // Fuente.
        fontSize: '14px',   // Tamaño de la fuente.
        fontWeight: 'bold', // Texto en negrita.
        color: '#333',      // Color del texto.
        display: 'flex',     // Para alinear los elementos internos.
        gap: '15px'         // Espacio entre el icono y el texto.
    };

    // Función para calcular la escala del mapa basada en el nivel de zoom y la latitud central.
    const calculateScale = (currentZoom: number) => {
        // Obtiene la latitud del centro actual del mapa.
        const latitude = map.getCenter().lat;
        // Circunferencia de la Tierra en metros.
        const earthCircumference = 40075017;
        // Resolución de la pantalla en píxeles por tile (para tiles de 256x256).
        const screenResolution = 256;
        // Cálculo del denominador de la escala. La escala varía con la latitud debido a la forma de la Tierra.
        const scaleDenominator = (earthCircumference * Math.cos((latitude * Math.PI) / 180)) /
                                    (screenResolution * Math.pow(2, currentZoom));

        // Formatea el denominador de la escala para una mejor visualización.
        return formatScale(scaleDenominator);
    };

    // Función para formatear el valor de la escala a un formato legible (ej: 1M, 10K).
    const formatScale = (rawScale: number): string => {
        if (rawScale >= 1000000) {
            return `${(rawScale / 1000000).toFixed(1)}M`; // Millones.
        } else if (rawScale >= 1000) {
            return `${(rawScale / 1000).toFixed(0)}K`;    // Miles.
        }
        return rawScale.toFixed(0); // Valor numérico sin sufijo para escalas más pequeñas.
    };

    // Hook useEffect para realizar acciones una vez que el componente se ha montado.
    useEffect(() => {
        // Obtiene el nivel de zoom inicial del mapa.
        const initialZoom = map.getZoom();
        // Establece el estado del zoom inicial.
        setZoom(initialZoom);
        // Calcula y establece la escala inicial basada en el zoom inicial.
        setScale(calculateScale(initialZoom));
        // La dependencia [map] asegura que este efecto se ejecute si la instancia del mapa cambia (raro en este contexto).
    }, [map]);

    // Hook useMapEvent para escuchar el evento 'zoomend' del mapa (cuando el zoom ha terminado).
    useMapEvent('zoomend', (event) => {
        // Obtiene el nuevo nivel de zoom del evento.
        const newZoom = event.target.getZoom();
        // Actualiza el estado del zoom.
        setZoom(newZoom);
        // Recalcula y actualiza el estado de la escala basada en el nuevo zoom.
        setScale(calculateScale(newZoom));
    });

    // Hook useMapEvent para escuchar el evento 'moveend' del mapa (cuando el mapa ha terminado de moverse).
    useMapEvent('moveend', () => {
        // Verifica que el valor del zoom no sea null antes de calcular la escala.
        if (zoom !== null) {
            // Recalcula y actualiza la escala, ya que la escala depende de la latitud central.
            setScale(calculateScale(zoom));
        }
    });

    // Renderiza el componente.
    return (
        <div style={containerStyle}>
            {/* Muestra la información del zoom si el estado 'zoom' no es null. */}
            {zoom !== null && (
                <div>
                    {/* Icono de lupa para indicar el zoom. */}
                    <span style={{ marginRight: '5px' }}>🔍</span>
                    {/* Muestra el nivel de zoom con un decimal. */}
                    Zoom: {zoom.toFixed(1)}
                </div>
            )}
            {/* Muestra la información de la escala si el estado 'scale' no es null. */}
            {scale !== null && (
                <div>
                    {/* Icono de regla para indicar la escala. */}
                    <span style={{ marginRight: '5px' }}>📏</span>
                    {/* Muestra la escala en formato 1:valor. */}
                    Escala: 1:{scale}
                </div>
            )}
        </div>
    );
};

// Exporta el componente para que pueda ser utilizado en otras partes de la aplicación.
export default ZoomDisplay;

siguiente componente :

import React, { useState } from "react";
// Importa el hook personalizado useDibujarMarker desde el archivo './DibujarMarker'.
// Este hook probablemente se encarga de dibujar un marcador en el mapa cuando las coordenadas cambian.
import useDibujarMarker from './DibujarMarker';

// Componente funcional UbicarCoordenadaDD que permite al usuario ingresar coordenadas decimales (DD)
// y ubicar un marcador en el mapa.
const UbicarCoordenadaDD = ({ layerGroupRef, mapRef }) => {
    // Estado para almacenar el valor de la longitud ingresada por el usuario. Inicializado como una cadena vacía.
    const [lng, setLng] = useState("");
    // Estado para almacenar el valor de la latitud ingresada por el usuario. Inicializado como una cadena vacía.
    const [lat, setLat] = useState("");
    // Estado para almacenar un array con las coordenadas [latitud, longitud] para dibujar el marcador.
    // Inicializado como null hasta que se ingresan y validan las coordenadas.
    const [coordenadas, setCoordenadas] = useState(null);
    // Estado para almacenar mensajes para el usuario, como errores de validación o confirmaciones.
    const [mensaje, setMensaje] = useState("");

    // Llama al hook personalizado useDibujarMarker.
    // Este hook recibe las coordenadas actuales, la referencia al layerGroup (donde se dibujará el marker)
    // y la referencia al objeto del mapa de Leaflet.
    // El hook se volverá a ejecutar cada vez que el valor de 'coordenadas' cambie.
    useDibujarMarker(coordenadas, layerGroupRef, mapRef);


    // Función que se ejecuta cuando el usuario hace clic en el botón "Ubicar DD".
    const handleUbicar = () => {
        // Limpia cualquier mensaje de error o éxito previo.
        setMensaje("");

        // Verifica si los campos de longitud o latitud están vacíos.
        if (lng === "" || lat === "") {
            // Si alguno está vacío, establece un mensaje de error y detiene la ejecución.
            setMensaje("Por favor, ingrese valores para latitud y longitud.");
            return;
        }

        // Intenta convertir las cadenas de longitud y latitud a números de punto flotante.
        let parsedLat = parseFloat(lat);
        let parsedLng = parseFloat(lng);

        // Verifica si la conversión a número falló (si el valor no es un número válido).
        if (isNaN(parsedLat) || isNaN(parsedLng)) {
            // Si la conversión falla, establece un mensaje de error y detiene la ejecución.
            setMensaje("Por favor, ingrese valores numéricos válidos.");
            return;
        }

        // Sugerencia al usuario si la latitud es negativa (podría indicar inversión de coordenadas).
        if (parsedLat < 0) {
            setMensaje("Si la latitud es negativa, probablemente estén invertidas las coordenadas.");
        return;
        }
        // Sugerencia al usuario si la longitud es positiva (podría indicar inversión de coordenadas).
        if (parsedLng > 0) {
            setMensaje("Si la longitud es positiva, probablemente estén invertidas las coordenadas.");
        return;
        }

        // Valida que la latitud esté dentro del rango válido (-90 a 90 grados).
        if (parsedLat < -90 || parsedLat > 90) {
            setMensaje("La latitud debe estar entre -90 y 90 grados.");
            return;
        }

        // Valida que la longitud esté dentro del rango válido (-180 a 180 grados).
        if (parsedLng < -180 || parsedLng > 180) {
            setMensaje("La longitud debe estar entre -180 y 180 grados.");
            return;
        }

        // Si todas las validaciones pasan, actualiza el estado 'coordenadas' con los valores parseados.
        // Esto activará la ejecución del hook useDibujarMarker y dibujará el marcador en el mapa.
        setCoordenadas([parsedLat, parsedLng]);
        // Limpia cualquier mensaje anterior de éxito (si hubiera).
        setMensaje("");
    };

    // Función de orden superior que devuelve un manejador de eventos para los cambios en los inputs.
    // Recibe una función 'setter' (como setLng o setLat) para actualizar el estado correspondiente.
    const handleInputChange = (setter) => (e) => {
        // Obtiene el valor actual del input.
        const value = e.target.value;
        // Utiliza una expresión regular para validar que solo se ingresen números (positivos o negativos)
        // y un único punto decimal.
        if (/^-?\d*\.?\d*$/.test(value)) {
            // Si el valor cumple con el patrón, actualiza el estado utilizando la función 'setter' proporcionada.
            setter(value);
        }
        // Si el valor no cumple con el patrón (contiene caracteres no válidos), el estado no se actualiza,
        // impidiendo la entrada de caracteres no numéricos.
    };

    return (
        // Contenedor principal del componente con estilos en línea para su posicionamiento y apariencia.
        <div
            style={{
                position: "absolute", // Para poder posicionarlo sobre el mapa.
                top: "160px",       // Posición vertical desde la parte superior.
                left: "60px",      // Posición horizontal desde la izquierda.
                zIndex: 1000,      // Asegura que esté por encima de otros elementos del mapa.
                background: "white", // Fondo blanco.
                padding: "10px",    // Espaciado interno.
                borderRadius: "8px", // Bordes redondeados.
                boxShadow: "0px 4px 6px rgba(0, 0, 0, 0.1)", // Sombra suave.
            }}
        >
            {/* Etiqueta para el campo de entrada de la longitud. */}
            <label>Longitud (x):</label>
            {/* Campo de entrada para la longitud. */}
            <input
                type="number" // Aunque el tipo sea 'number', la validación se hace con la regex en handleInputChange.
                value={lng}   // Valor controlado por el estado 'lng'.
                onChange={handleInputChange(setLng)} // Asigna el manejador de cambios para actualizar el estado 'lng'.
                placeholder="Ej: -00.00000000" // Texto de ejemplo para el usuario.
            />

            {/* Etiqueta para el campo de entrada de la latitud. */}
            <label>Latitud (y):</label>
            {/* Campo de entrada para la latitud. */}
            <input
                type="number" // Aunque el tipo sea 'number', la validación se hace con la regex en handleInputChange.
                value={lat}   // Valor controlado por el estado 'lat'.
                onChange={handleInputChange(setLat)} // Asigna el manejador de cambios para actualizar el estado 'lat'.
                placeholder="Ej: 00.00000000" // Texto de ejemplo para el usuario.
            />
            {/* Botón para activar la función de ubicación de coordenadas. */}
            <button onClick={handleUbicar}>Ubicar DD</button>
            {/* Sección para mostrar mensajes al usuario. */}
            {mensaje && (
                <div
                    style={{
                        color: mensaje.includes("fuera") || mensaje.includes("Por favor") ? 'red' : 'green',
                        marginTop: '10px',
                    }}
                >
                    {mensaje}
                </div>
            )}
        </div>
    );
};

// Exporta el componente para que pueda ser utilizado en otras partes de la aplicación.
export default UbicarCoordenadaDD;


suguiente componente :


import React, { useState, ChangeEvent } from 'react';
// Importa el hook personalizado useDibujarMarker desde el archivo './DibujarMarker'.
// Este hook probablemente se encarga de dibujar un marcador en el mapa cuando las coordenadas cambian.
import useDibujarMarker from './DibujarMarker';
// Importa la librería Leaflet para trabajar con mapas. Aunque aquí solo se usa en la definición de tipos.
import L from 'leaflet';

// Define la interfaz Props para este componente funcional.
interface Props {
    // layerGroupRef es una referencia mutable a un LayerGroup de Leaflet.
    // Se utiliza para agregar o eliminar capas (como marcadores) del mapa.
    layerGroupRef: React.RefObject<L.LayerGroup>;
    // mapRef es una referencia mutable a la instancia del mapa de Leaflet.
    // Permite interactuar directamente con el objeto del mapa.
    mapRef: React.RefObject<L.Map>;
}

// Define la interfaz DMS (Grados, Minutos, Segundos) para representar las coordenadas.
interface DMS {
    degrees: string;
    minutes: string;
    seconds: string;
}

// Componente funcional React UbicarCoordenadadms que permite al usuario ingresar coordenadas en formato
// Grados, Minutos y Segundos (DMS) y ubicar un marcador en el mapa.
const UbicarCoordenadadms: React.FC<Props> = ({ layerGroupRef, mapRef }) => {
    // Estado para almacenar los valores de latitud en formato DMS. Inicializado con cadenas vacías.
    const [latDms, setLatDms] = useState<DMS>({ degrees: '', minutes: '', seconds: '' });
    // Estado para almacenar los valores de longitud en formato DMS. Inicializado con cadenas vacías.
    const [lngDms, setLngDms] = useState<DMS>({ degrees: '', minutes: '', seconds: '' });
    // Estado para almacenar las coordenadas convertidas a formato decimal [latitud, longitud].
    // Inicializado como null hasta que se convierten las coordenadas DMS.
    const [coordenadas, setCoordenadas] = useState<[number, number] | null>(null);
    // Estado para almacenar mensajes de error para el usuario, por ejemplo, validaciones fallidas.
    const [error, setError] = useState<string>('');

    // Llama al hook personalizado useDibujarMarker.
    // Este hook recibe las coordenadas decimales actuales, la referencia al LayerGroup y la referencia al mapa.
    // Se ejecutará cada vez que el valor de 'coordenadas' cambie, actualizando el marcador en el mapa.
    useDibujarMarker(coordenadas, layerGroupRef, mapRef);

    // Función para convertir grados, minutos y segundos a formato decimal.
    // Recibe los valores numéricos de grados, minutos y segundos.
    const dmsToDecimal = (degrees: number, minutes: number, seconds: number): number => {
        // Calcula el valor decimal: grados + (minutos / 60) + (segundos / 3600).
        // Se utiliza Math.abs para asegurar que el cálculo sea siempre positivo,
        // el signo se aplicará posteriormente basado en el signo de los grados.
        return Math.abs(degrees) + (minutes / 60) + (seconds / 3600);
    };

    // Manejador para los cambios en los campos de entrada (grados, minutos, segundos).
    // Recibe el evento de cambio del input y el tipo de coordenada ('lat' o 'lng').
    const handleInputChange = (e: ChangeEvent<HTMLInputElement>, type: 'lat' | 'lng') => {
        // Extrae el nombre del input (degrees, minutes, seconds) y su valor.
        const { name, value } = e.target;

        // Actualiza el estado correspondiente (latDms o lngDms) basado en el tipo de coordenada.
        if (type === 'lat') {
            // Utiliza la función de actualización de estado para mantener los valores anteriores
            // y solo actualizar la propiedad del input que cambió.
            setLatDms(prev => ({ ...prev, [name]: value }));
        } else {
            // Similar a la latitud, actualiza el estado de la longitud.
            setLngDms(prev => ({ ...prev, [name]: value }));
        }

        // Limpia cualquier mensaje de error que pudiera estar visible al editar los campos.
        setError('');
    };

    // Función para convertir los valores DMS ingresados a coordenadas decimales.
    const convertDmsToDecimal = () => {
        // Extrae los valores de grados, minutos y segundos de los estados de latitud y longitud.
        const { degrees: degLat, minutes: minLat, seconds: secLat } = latDms;
        const { degrees: degLng, minutes: minLng, seconds: secLng } = lngDms;

        // Verifica si alguno de los campos está vacío.
        if (
            degLat === '' || minLat === '' || secLat === '' ||
            degLng === '' || minLng === '' || secLng === ''
        ) {
            // Si algún campo está vacío, establece un mensaje de error y detiene la conversión.
            setError('Por favor complete todos los campos');
            return;
        }

        try {
            // Intenta convertir los valores de cadena a números.
            const latDeg = Number(degLat);
            const latMin = Number(minLat);
            const latSec = Number(secLat);
            const lngDeg = Number(degLng);
            const lngMin = Number(minLng);
            const lngSec = Number(secLng);

            // Validar rangos de latitud (grados: -90 a 90, minutos/segundos: 0 a 59).
            if (latDeg < -90 || latDeg > 90 || latMin < 0 || latMin >= 60 || latSec < 0 || latSec >= 60) {
                setError('Valores de latitud fuera de rango');
                return;
            }

            // Validar rangos de longitud (grados: -180 a 180, minutos/segundos: 0 a 59).
            if (lngDeg < -180 || lngDeg > 180 || lngMin < 0 || lngMin >= 60 || lngSec < 0 || lngSec >= 60) {
                setError('Valores de longitud fuera de rango');
                return;
            }

            // Sugerencia al usuario si la latitud es negativa (podría indicar inversión de coordenadas).
            if (latDeg < 0) {
                setError("Si la latitud es negativa, probablemente estén invertidas las coordenadas.");
                return;
            }
            // Sugerencia al usuario si la longitud es positiva (podría indicar inversión de coordenadas).
            if (lngDeg > 0) {
                setError("Si la longitud es positiva, probablemente estén invertidas las coordenadas.");
                return;
            }

            // Convierte los valores DMS a decimales, manteniendo el signo de los grados.
            const lat = dmsToDecimal(latDeg, latMin, latSec) * (latDeg >= 0 ? 1 : -1);
            const lng = dmsToDecimal(lngDeg, lngMin, lngSec) * (lngDeg >= 0 ? 1 : -1);

            // Actualiza el estado 'coordenadas' con los valores decimales convertidos.
            // Esto activará el hook useDibujarMarker.
            setCoordenadas([lat, lng]);
            // Limpia cualquier mensaje de error después de una conversión exitosa.
            setError('');
        } catch (err) {
            // Captura cualquier error que ocurra durante la conversión (por ejemplo, si Number() falla).
            setError('Error al convertir las coordenadas');
        }
    };

    return (
        <>
            {/* Contenedor para los campos de entrada de la longitud en formato DMS. */}
            <div style={styles.panel(220)}>
                <label>(longitud)</label>
                {/* Input para los grados de longitud. */}
                <input
                    type="number"
                    name="degrees"
                    value={lngDms.degrees}
                    onChange={(e) => handleInputChange(e, 'lng')}
                    placeholder="Grados °"
                    min="-180"
                    max="180"
                    step="0.000001"
                />
                {/* Input para los minutos de longitud. */}
                <input
                    type="number"
                    name="minutes"
                    value={lngDms.minutes}
                    onChange={(e) => handleInputChange(e, 'lng')}
                    placeholder="Minutos '"
                    min="0"
                    max="59"
                    step="0.000001"
                />
                {/* Input para los segundos de longitud. */}
                <input
                    type="number"
                    name="seconds"
                    value={lngDms.seconds}
                    onChange={(e) => handleInputChange(e, 'lng')}
                    placeholder='Segundos "'
                    min="0"
                    max="59"
                    step="0.000001"
                />
                {/* Etiqueta indicando la dirección (Oeste). */}
                <label> W </label>
                {/* Botón para iniciar la conversión de DMS a decimal y ubicar el marcador. */}
                <button onClick={convertDmsToDecimal}>Ubicar DMS</button>
            </div>

            {/* Contenedor para los campos de entrada de la latitud en formato DMS. */}
            <div style={styles.panel(300)}>
                <label>(latitud)</label>
                {/* Input para los grados de latitud. */}
                <input
                    type="number"
                    name="degrees"
                    value={latDms.degrees}
                    onChange={(e) => handleInputChange(e, 'lat')}
                    placeholder="Grados °"
                    min="-90"
                    max="90"
                    step="0.000001"
                />
                {/* Input para los minutos de latitud. */}
                <input
                    type="number"
                    name="minutes"
                    value={latDms.minutes}
                    onChange={(e) => handleInputChange(e, 'lat')}
                    placeholder="Minutos '"
                    min="0"
                    max="59"
                    step="0.000001"
                />
                {/* Input para los segundos de latitud. */}
                <input
                    type="number"
                    name="seconds"
                    value={latDms.seconds}
                    onChange={(e) => handleInputChange(e, 'lat')}
                    placeholder='Segundos "'
                    min="0"
                    max="59"
                    step="0.000001"
                />
                {/* Etiqueta indicando la dirección (Norte). */}
                <label> N </label>
            </div>
            {/* Muestra el mensaje de error si existe. */}
            {error && <div style={{ color: 'red', position: 'absolute', top: '350px', left: '60px' }}>{error}</div>}
        </>
    );
};

// Define un objeto de estilos para los contenedores de los inputs.
const styles = {
    panel: (top: number) => ({
        position: 'absolute' as const, // Posicionamiento absoluto para ubicarlo sobre el mapa.
        top: `${top}px`,                // Posición vertical desde la parte superior.
        left: '60px',               // Posición horizontal desde la izquierda.
        zIndex: 1000,               // Asegura que esté por encima de otros elementos del mapa.
        background: "white",        // Fondo blanco para mejor legibilidad.
        padding: "10px",           // Espaciado interno.
        borderRadius: "8px",        // Bordes redondeados para una apariencia más suave.
        boxShadow: "0px 4px 6px rgba(0, 0, 0, 0.1)", // Sombra suave para destacar el panel.
        display: 'flex',           // Utiliza flexbox para alinear los elementos internos.
        alignItems: 'center',        // Alinea los elementos verticalmente al centro.
        gap: '5px'                // Espacio entre los elementos internos.
    }),
};

export default UbicarCoordenadadms;


suguiente componente :

import React, { useState } from 'react';
// Importa el hook personalizado useDibujarMarker desde el archivo './DibujarMarker'.
// Este hook probablemente se encarga de dibujar un marcador en el mapa cuando las coordenadas cambian.
import useDibujarMarker from './DibujarMarker';

// Componente funcional Ubicardm que permite al usuario ingresar coordenadas en formato
// Grados y Minutos decimales (DM) y ubicar un marcador en el mapa.
const Ubicardm = ({ layerGroupRef, mapRef }) => {
    // Estado para almacenar las coordenadas decimales [latitud, longitud] para dibujar el marcador.
    // Inicializado como null hasta que se convierten las coordenadas DM.
    const [coordenadas, setCoordenadas] = useState<[number, number] | null>(null);
    // Estado para almacenar los valores de grados y minutos para latitud ('degrees') y longitud ('degreesw').
    // Inicializado con cadenas vacías.
    const [dms, setDms] = useState({ degrees: '', degreesw: '' });
    // Estado para almacenar mensajes de error para el usuario, por ejemplo, validaciones fallidas.
    const [error, setError] = useState<string>('');

    // Llama al hook personalizado useDibujarMarker.
    // Este hook recibe las coordenadas decimales actuales, la referencia al LayerGroup y la referencia al mapa.
    // Se ejecutará cada vez que el valor de 'coordenadas' cambie, actualizando el marcador en el mapa.
    useDibujarMarker(coordenadas, layerGroupRef, mapRef);

    // Manejador para los cambios en los campos de entrada (latitud y longitud).
    // Recibe el evento de cambio del input.
    const handleChange = (e) => {
        // Extrae el nombre del input (degrees o degreesw) y su valor.
        const { name, value } = e.target;
        // Actualiza el estado 'dms' manteniendo los valores anteriores y actualizando solo la propiedad del input que cambió.
        setDms((prev) => ({
            ...prev,
            [name]: value
        }));
    };

    // Función para convertir grados y minutos decimales a formato decimal.
    // Recibe el valor de grados y minutos como un número o cadena.
    const dmToDecimal = (degrees) => {
        // Verifica si el valor de grados es nulo, indefinido o no es un número.
        if (!degrees || isNaN(degrees)) {
            return null; // Retorna null si el valor no es válido.
        }

        // Asegura que el valor de grados sea positivo para el cálculo. El signo se manejará por separado.
        degrees = Math.abs(parseFloat(degrees));
        let grados, minutos;

        // Determina si el formato es GGMM (grados enteros seguidos de minutos enteros) o GG.MM (grados enteros seguidos de minutos decimales).
        if (degrees >= 100) { // Formato GGMM
            grados = Math.floor(degrees / 100); // Obtiene la parte entera de los grados.
            minutos = degrees % 100;         // Obtiene la parte de los minutos.
        } else { // Formato GG.MM
            grados = Math.floor(degrees);       // Obtiene la parte entera de los grados.
            minutos = (degrees - grados) * 60; // Obtiene la parte decimal de los minutos y la convierte a minutos.
        }

        // Valida que los minutos no sean mayores o iguales a 60.
        if (minutos >= 60) {
            return null; // Retorna null si los minutos no son válidos.
        }

        // Retorna el valor decimal de la coordenada: grados + (minutos / 60).
        return grados + (minutos / 60);
    };

    // Función para convertir los valores DM ingresados a coordenadas decimales.
    const convertDmToDecimal = () => {
        // Extrae los valores de latitud y longitud del estado 'dms'.
        const { degrees, degreesw } = dms;

        // Verifica si alguno de los campos está vacío.
        if (degrees === '' || degreesw === '') {
            setError('Por favor ingrese valores para latitud y longitud');
            return;
        }

        // Convierte la latitud y la longitud a formato decimal utilizando la función dmToDecimal.
        const lat = dmToDecimal(degrees);
        const lng = dmToDecimal(degreesw);

        // Verifica si la conversión de latitud o longitud resultó en null (formato incorrecto).
        if (lat === null || lng === null) {
            setError('Formato de grados incorrecto');
            return;
        }

        // Valida que la latitud esté dentro del rango válido (0 a 90 grados Norte).
        if (lat < 0 || lat > 90) {
            setError('La latitud debe estar entre 0° y 90°');
            return;
        }

        // Valida que la longitud esté dentro del rango válido (0 a 180 grados Oeste).
        if (lng < 0 || lng > 180) {
            setError('La longitud debe estar entre 0° y 180°');
            return;
        }

        // Convierte la longitud a negativo porque se asume que es Oeste (W).
        setCoordenadas([lat, -lng]);
        // Limpia cualquier mensaje de error después de una conversión exitosa.
        setError('');
    };

    return (
        <>
            {/* Contenedor para el input de la longitud (Oeste). */}
            <div style={{
                position: 'absolute',
                top: '315px',
                left: '60px',
                zIndex: 1000,
                background: "white",
                padding: "10px",
                borderRadius: "8px",
                boxShadow: "0px 4px 6px rgba(0, 0, 0, 0.1)",
            }}>
                {/* Input para ingresar la longitud en formato GGMM o GG.MM. */}
                <input
                    type="number"
                    name="degreesw"
                    value={dms.degreesw}
                    placeholder="Longitud (GGMM o GG.MM)"
                    step="0.000001"
                    onChange={handleChange}
                />
                {/* Etiqueta para indicar que este campo es para la Longitud Oeste. */}
                <label> W (Longitud) </label>
                {/* Botón para iniciar la conversión de DM a decimal y ubicar el marcador. */}
                <button onClick={convertDmToDecimal}>Ubicar DM</button>
            </div>

            {/* Contenedor para el input de la latitud (Norte). */}
            <div style={{
                position: 'absolute',
                top: '385px',
                left: '60px',
                zIndex: 1000,
                background: "white",
                padding: "10px",
                borderRadius: "8px",
                boxShadow: "0px 4px 6px rgba(0, 0, 0, 0.1)",
            }}>
                {/* Input para ingresar la latitud en formato GGMM o GG.MM. */}
                <input
                    type="number"
                    name="degrees"
                    value={dms.degrees}
                    placeholder="Latitud (GGMM o GG.MM)"
                    step="0.000001"
                    onChange={handleChange}
                />
                {/* Etiqueta para indicar que este campo es para la Latitud Norte. */}
                <label> N (Latitud)</label>
            </div>

            {/* Sección para mostrar mensajes de error al usuario. */}
            {error && (
                <div style={{
                    position: 'absolute',
                    top: '455px',
                    left: '60px',
                    zIndex: 1000,
                    background: "white",
                    padding: "8px",
                    borderRadius: "8px",
                    boxShadow: "0px 4px 6px rgba(0, 0, 0, 0.1)",
                    color: 'red'
                }}>
                    {error}
                </div>
            )}
        </>
    );
};

export default Ubicardm;


siguiente componente :

import React, { useState } from "react";
// Importa la función 'inverse' con el alias 'toLatLon' desde la librería 'mgrs'.
// Esta función se utiliza para convertir coordenadas MGRS (Military Grid Reference System) a Latitud y Longitud.
import { inverse as toLatLon } from "mgrs";
// Importa el hook personalizado useDibujarMarker desde el archivo './DibujarMarker'.
// Este hook probablemente se encarga de dibujar un marcador en el mapa cuando las coordenadas cambian.
import useDibujarMarker from './DibujarMarker';

// Componente funcional UbicarMGRS que permite al usuario ingresar una coordenada MGRS
// y ubicar un marcador en el mapa correspondiente a esa ubicación.
export const UbicarMGRS = ({ layerGroupRef, mapRef }) => {
    // Estado para almacenar la cadena de texto ingresada por el usuario como coordenada MGRS a buscar.
    const [coordenadasBuscar, setCoordenadasBuscar] = useState("");
    // Estado para almacenar las coordenadas [longitud, latitud] resultantes de la conversión de MGRS.
    // Inicializado como null hasta que se realiza una conversión exitosa.
    const [coordenadas, setCoordenadas] = useState<[number, number] | null>(null);
    // Estado para almacenar mensajes de error para el usuario, por ejemplo, si el formato MGRS es inválido
    // o si ocurre un error durante la conversión.
    const [error, setError] = useState("");

    // Llama al hook personalizado useDibujarMarker.
    // Este hook recibe las coordenadas [longitud, latitud] actuales, la referencia al LayerGroup (donde se dibujará el marker)
    // y la referencia al objeto del mapa de Leaflet.
    // El hook se volverá a ejecutar cada vez que el valor de 'coordenadas' cambie.
    useDibujarMarker(coordenadas, layerGroupRef, mapRef);

    // Función para validar si una cadena de texto dada tiene un formato MGRS válido.
    const isValidMGRS = (mgrs) => {
        // Si la cadena es nula, indefinida o no es un string, no es un MGRS válido.
        if (!mgrs || typeof mgrs !== 'string') return false;

        // Elimina espacios en blanco al inicio y al final y convierte la cadena a mayúsculas para la validación.
        const trimmed = mgrs.trim().toUpperCase();

        // Un MGRS mínimo debe tener 7 caracteres: zona (1-2) + banda (1) + cuadrícula (2) + al menos 2 dígitos de precisión.
        if (trimmed.length < 7) return false;

        // Verifica si la cadena contiene solo letras mayúsculas y números.
        if (!/^[A-Z0-9]+$/.test(trimmed)) return false;

        // Expresión regular para el formato típico de MGRS:
        // - (\d{1,2}): Una o dos cifras para la zona.
        // - ([C-HJ-NP-X]): Una letra para la banda (omite I y O).
        // - ([A-HJ-NP-Z]{2}): Dos letras para la designación del cuadrado de 100 km.
        // - (\d{2,10})?: Opcionalmente, un par de números (o más pares) para la precisión de las coordenadas.
        const mgrsPattern = /^(\d{1,2})([C-HJ-NP-X])([A-HJ-NP-Z]{2})(\d{2,10})?$/;
        // Retorna el resultado de la prueba de la expresión regular contra la cadena MGRS.
        return mgrsPattern.test(trimmed);
    };

    // Función que se ejecuta cuando el usuario hace clic en el botón "Ubicar MGRS".
    const handleUbicar = () => {
        // Obtiene la coordenada MGRS del estado, elimina espacios y la convierte a mayúsculas.
        const mgrs = coordenadasBuscar.trim().toUpperCase();

        // Si el campo de entrada está vacío, establece un mensaje de error y detiene la ejecución.
        if (mgrs === "") {
            setError("Por favor ingrese una coordenada MGRS.");
            return;
        }

        // Valida el formato de la coordenada MGRS utilizando la función isValidMGRS.
        if (!isValidMGRS(mgrs)) {
            setError("Formato MGRS inválido. Ejemplo válido: 33UXP04.");
            return;
        }

        try {
            // Intenta convertir la coordenada MGRS a latitud y longitud utilizando la función toLatLon.
            const [lat, lon] = toLatLon(mgrs);
            // Verifica si la conversión resultó en números válidos para latitud y longitud.
            if (typeof lat !== 'number' || typeof lon !== 'number') {
                throw new Error("Conversión inválida");
            }

            // Si la conversión es exitosa, actualiza el estado 'coordenadas' con [longitud, latitud]
            // (el orden es importante para Leaflet) y limpia cualquier mensaje de error previo.
            setCoordenadas([lon, lat]);
            setError("");

        } catch (error) {
            // Si ocurre un error durante la conversión (por ejemplo, si la coordenada MGRS es válida en formato
            // pero no representa una ubicación válida), registra el error en la consola y establece un mensaje de error para el usuario.
            console.error("Error al convertir MGRS:", error);
            setError("Error al convertir la coordenada MGRS. Verifica que sea válida.");
        }
    };

    // Manejador para los cambios en el campo de entrada de la coordenada MGRS.
    const handleChange = (e) => {
        // Obtiene el valor del input y lo convierte a mayúsculas.
        const value = e.target.value.toUpperCase();
        // Permite solo la entrada de letras mayúsculas y números, o una cadena vacía.
        if (/^[A-Z0-9]*$/.test(value) || value === "") {
            // Actualiza el estado 'coordenadasBuscar' con el nuevo valor.
            setCoordenadasBuscar(value);
            // Limpia cualquier mensaje de error que pudiera estar visible al editar el campo.
            setError("");
        }
    };

    return (
        <div>
            {/* Contenedor para el input de la coordenada MGRS y el botón de ubicación. */}
            <div
                style={{
                    position: "absolute",
                    top: "400px",
                    left: "60px",
                    zIndex: 1000,
                    background: "white",
                    padding: "10px",
                    borderRadius: "8px",
                    boxShadow: "0px 4px 6px rgba(0, 0, 0, 0.1)",
                }}
            >
                {/* Campo de entrada de texto para que el usuario ingrese la coordenada MGRS. */}
                <input
                    type="text"
                    value={coordenadasBuscar}
                    onChange={handleChange}
                    placeholder="Coordenadas MGRS (ej. 33UXP04)"
                    className="border p-2 rounded w-64" // Clases de Tailwind CSS para estilos básicos.
                />
                {/* Botón para activar la función de ubicación de la coordenada MGRS. */}
                <button
                    onClick={handleUbicar}
                    className="ml-2 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition" // Clases de Tailwind CSS para estilos básicos.
                >
                    Ubicar MGRS
                </button>
            </div>
            {/* Sección para mostrar mensajes de error al usuario si el estado 'error' tiene un valor. */}
            {error && (
                <div style={{
                    position: "absolute",
                    top: "480px",
                    left: "60px",
                    zIndex: 1000,
                    color: 'red',
                    background: "white",
                    padding: "8px",
                    borderRadius: "8px",
                    boxShadow: "0px 4px 6px rgba(0, 0, 0, 0.1)",
                }}>
                    {error}
                </div>
            )}
        </div>
    );
};

export default UbicarMGRS;


suguiente componente :

import React, { useState, useEffect } from "react";
// Importa la librería proj4, utilizada para realizar transformaciones de coordenadas entre diferentes sistemas de referencia espacial.
import proj4 from "proj4";
// Importa el hook personalizado useDibujarMarker desde el archivo './DibujarMarker'.
// Este hook probablemente se encarga de dibujar un marcador en el mapa cuando las coordenadas cambian.
import useDibujarMarker from "./DibujarMarker";

// Definir un objeto que mapea las zonas UTM (Universal Transverse Mercator) específicas para México
// con sus correspondientes códigos EPSG (European Petroleum Survey Group).
const utmZones = {
    11: "EPSG:32611", // Baja California (Zona 11 Norte)
    12: "EPSG:32612", // Sonora, Chihuahua (Zona 12 Norte)
    13: "EPSG:32613", // Durango, Zacatecas, Nayarit (Zona 13 Norte)
    14: "EPSG:32614", // Ciudad de México, Estado de México, Puebla (Zona 14 Norte)
    15: "EPSG:32615", // Veracruz, Tabasco, Campeche (Zona 15 Norte)
    16: "EPSG:32616", // Quintana Roo, Yucatán (Zona 16 Norte)
};

// Registrar las definiciones de las proyecciones UTM en la librería proj4.
// Esto permite a proj4 entender y realizar transformaciones con estos sistemas de coordenadas.
Object.entries(utmZones).forEach(([zoneNum, epsg]) => {
    // Define cada proyección UTM utilizando el número de zona, el datum WGS84 (World Geodetic System 1984),
    // las unidades en metros, la ausencia de definiciones predeterminadas adicionales y la indicación de que es una zona norte.
    proj4.defs(epsg, `+proj=utm +zone=${zoneNum} +datum=WGS84 +units=m +no_defs +north`);
});

// Función para determinar la zona UTM aproximada en la que se encuentra una coordenada Este (X) en México.
// Se basa en rangos típicos de valores Este para las diferentes zonas UTM del país.
const determinarZonaUTM = (xEste) => {
    // Si el valor Este está fuera de un rango general esperado para México, retorna null.
    if (xEste < 200000 || xEste > 900000) return null;
    // Asigna la zona UTM basándose en rangos del valor Este. Estos rangos son aproximados y pueden variar.
    if (xEste < 300000) return utmZones[11]; // Zona 11
    if (xEste < 400000) return utmZones[12]; // Zona 12
    if (xEste < 500000) return utmZones[13]; // Zona 13
    if (xEste < 600000) return utmZones[14]; // Zona 14
    if (xEste < 700000) return utmZones[15]; // Zona 15
    return utmZones[16];                     // Zona 16 (para valores Este más altos)
};

// Componente funcional UbicarMetrica que permite al usuario ingresar coordenadas UTM (Este y Norte)
// y ubicar un marcador en el mapa correspondiente a esa ubicación geográfica en México.
const UbicarMetrica = ({ layerGroupRef, mapRef }) => {
    // Estado para almacenar el valor de la coordenada Este (X) ingresada por el usuario. Inicializado como una cadena vacía.
    const [xEste, setXEste] = useState("");
    // Estado para almacenar el valor de la coordenada Norte (Y) ingresada por el usuario. Inicializado como una cadena vacía.
    const [yNorte, setYNorte] = useState("");
    // Estado para almacenar un array de coordenadas [latitud, longitud] convertidas desde UTM.
    // Se utiliza un array para potencialmente almacenar múltiples ubicaciones, aunque en la lógica actual solo se usa la última.
    const [coordenadas, setCoordenadas] = useState<[number, number][]>([]);
    // Estado para almacenar mensajes de error para el usuario, por ejemplo, si los valores ingresados no son válidos
    // o si ocurre un error durante la conversión de coordenadas.
    const [error, setError] = useState("");

    // Hook useEffect que se ejecuta cuando la lista de 'coordenadas' cambia.
    // Su propósito es dibujar un nuevo marcador en el mapa cada vez que se agrega una nueva coordenada convertida.
    useEffect(() => {
        // Verifica si hay al menos una coordenada en el array.
        if (coordenadas.length > 0) {
            // Obtiene la última coordenada agregada al array.
            const ultima = coordenadas[coordenadas.length - 1];
            // Llama al hook useDibujarMarker para dibujar un marcador en el mapa en la ubicación de la última coordenada.
            useDibujarMarker(ultima, layerGroupRef, mapRef);
        }
        // Las dependencias del useEffect son 'coordenadas', 'layerGroupRef' y 'mapRef'.
        // El efecto se re-ejecutará si alguno de estos valores cambia.
    }, [coordenadas, layerGroupRef, mapRef]);

    // Función para validar si un valor ingresado es un número positivo válido (puede ser entero o decimal).
    const esNumeroValido = (valor) => /^(\d+(\.\d+)?)$/.test(valor);

    // Función que se ejecuta cuando el usuario hace clic en el botón "Ubicar".
    const handleUbicar = () => {
        // Limpia cualquier mensaje de error previo.
        setError("");

        // Verifica si alguno de los campos de Este o Norte está vacío después de eliminar espacios en blanco.
        if (!xEste.trim() || !yNorte.trim()) {
            setError("Ambos campos son obligatorios.");
            return;
        }

        // Valida si los valores ingresados para Este y Norte son números positivos válidos.
        if (!esNumeroValido(xEste) || !esNumeroValido(yNorte)) {
            setError("Solo se permiten números positivos válidos.");
            return;
        }

        // Convierte los valores de Este y Norte a números de punto flotante.
        const parsedX = parseFloat(xEste);
        const parsedY = parseFloat(yNorte);

        // Valida si las coordenadas UTM ingresadas se encuentran dentro de un rango aproximado válido para México.
        // Estos rangos son generales y pueden no cubrir todas las posibles coordenadas UTM en el país.
        if (parsedX < 200000 || parsedX > 900000 || parsedY < 1000000 || parsedY > 4000000) {
            setError("Coordenadas fuera del rango válido para México.");
            return;
        }

        // Determina la zona UTM aproximada basándose en el valor de la coordenada Este.
        const zonaEPSG = determinarZonaUTM(parsedX);

        // Si no se pudo determinar una zona UTM válida, establece un mensaje de error.
        if (!zonaEPSG) {
            setError("No se pudo determinar la zona UTM.");
            return;
        }

        try {
            // Realiza la transformación de coordenadas desde el sistema UTM determinado (zonaEPSG)
            // al sistema de coordenadas geográficas WGS84 (EPSG:4326), que utiliza latitud y longitud.
            // proj4 espera la coordenada Este primero y luego la Norte. Retorna un array [longitud, latitud].
            const [lng, lat] = proj4(zonaEPSG, "EPSG:4326", [parsedX, parsedY]);
            // Actualiza el estado 'coordenadas' agregando la nueva coordenada [latitud, longitud] al array.
            setCoordenadas((prev) => [...prev, [lat, lng]]);
            // Limpia los campos de entrada después de una conversión exitosa.
            setXEste("");
            setYNorte("");
        } catch (err) {
            // Si ocurre un error durante la conversión de coordenadas, registra el error en la consola
            // y establece un mensaje de error para el usuario.
            console.error("Error en conversión:", err);
            setError("Error al convertir las coordenadas.");
        }
    };

    return (
        <div
            style={{
                position: "absolute",
                top: "450px",
                left: "60px",
                zIndex: 1000,
                background: "white",
                padding: "12px",
                borderRadius: "8px",
                boxShadow: "0 4px 8px rgba(0, 0, 0, 0.15)",
                width: "260px"
            }}
        >
            {/* Contenedor para el campo de entrada de la coordenada Este (X). */}
            <div style={{ marginBottom: "8px" }}>
                <label>X (Este): </label>
                <input
                    type="text"
                    value={xEste}
                    onChange={(e) => setXEste(e.target.value)}
                    placeholder="Ej. 400000"
                    className="border p-1 rounded w-full" // Clases de Tailwind CSS para estilos básicos.
                />
            </div>
            {/* Contenedor para el campo de entrada de la coordenada Norte (Y). */}
            <div style={{ marginBottom: "8px" }}>
                <label>Y (Norte): </label>
                <input
                    type="text"
                    value={yNorte}
                    onChange={(e) => setYNorte(e.target.value)}
                    placeholder="Ej. 2144730"
                    className="border p-1 rounded w-full" // Clases de Tailwind CSS para estilos básicos.
                />
            </div>
            {/* Botón para activar la función de ubicación de coordenadas UTM. */}
            <button
                onClick={handleUbicar}
                className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 w-full" // Clases de Tailwind CSS para estilos básicos.
            >
                Ubicar
            </button>
            {/* Sección para mostrar mensajes de error al usuario si el estado 'error' tiene un valor. */}
            {error && (
                <div
                    style={{
                        marginTop: "10px",
                        color: "red",
                        backgroundColor: "#ffe6e6",
                        padding: "8px",
                        borderRadius: "6px",
                    }}
                >
                    {error}
                </div>
            )}
        </div>
    );
};

export default UbicarMetrica;


suguiente componente : 

import React, { useState, useEffect, useMemo } from "react";
// Importa el hook useMapEvent de react-leaflet para acceder a los eventos del mapa.
import { useMapEvent } from "react-leaflet";
// Importa la librería Leaflet y la interfaz LatLng para trabajar con coordenadas.
import L, { LatLng } from "leaflet";
// Importa la librería proj4 para realizar transformaciones de coordenadas entre diferentes sistemas de referencia espacial.
import proj4 from "proj4";
// Importa la librería mgrs para convertir entre coordenadas geográficas y el sistema MGRS.
import * as mgrs from "mgrs";
// Importa el icono personalizado desde el archivo './icon'.
import icon from "./icon";

// Define la interfaz Coordenadas para almacenar varios formatos de una misma ubicación.
interface Coordenadas {
    lat: number; // Latitud en grados decimales.
    lng: number; // Longitud en grados decimales.
    xEste: number | null; // Coordenada Este en UTM (metros).
    yNorte: number | null; // Coordenada Norte en UTM (metros).
    zonaUTM: string | null; // Código EPSG de la zona UTM.
    mgrsCoord: string; // Coordenada en formato MGRS.
    latDMS: string; // Latitud en formato Grados, Minutos y Segundos.
    lngDMS: string; // Longitud en formato Grados, Minutos y Segundos.
    latDM: string; // Latitud en formato Grados y Minutos decimales.
    lngDM: string; // Longitud en formato Grados y Minutos decimales.
}

// Define la interfaz CapturaClickProps para las propiedades del componente.
interface CapturaClickProps {
    layerGroupRef: React.RefObject<L.LayerGroup>; // Referencia mutable a un LayerGroup de Leaflet para agregar capas.
}

// 🔹 Definición de las proyecciones UTM (Universal Transverse Mercator) para algunas zonas.
// Record<number, string> asegura que las claves son números y los valores son strings.
const utmZones: Record<number, string> = {
    11: "EPSG:32611", 12: "EPSG:32612", 13: "EPSG:32613",
    14: "EPSG:32614", 15: "EPSG:32615", 16: "EPSG:32616",
};

// Registra las definiciones de las proyecciones UTM en la librería proj4.
Object.entries(utmZones).forEach(([zone, epsg]) => {
    // Define cada proyección UTM utilizando el número de zona, el datum WGS84, las unidades en metros,
    // la ausencia de definiciones predeterminadas adicionales y la indicación de que es una zona norte.
    proj4.defs(epsg, `+proj=utm +zone=${zone} +datum=WGS84 +units=m +no_defs +north`);
});

// Función para determinar la zona UTM aproximada basada en la longitud.
const determinarZonaUTM = (lng: number): string | null => {
    // Calcula la zona UTM dividiendo la longitud + 180 por 6 y tomando la parte entera + 1.
    const zona = Math.floor((lng + 180) / 6) + 1;
    // Retorna el código EPSG de la zona UTM si existe en el objeto utmZones, de lo contrario retorna null.
    return utmZones[zona] || null;
};

// 🔹 Funciones de conversión de coordenadas.

// Función para convertir grados decimales a Grados y Minutos decimales (DM).
const decimalToDM = (decimal: number, isLat: boolean): string => {
    const absolute = Math.abs(decimal); // Obtiene el valor absoluto.
    const degrees = Math.floor(absolute); // Obtiene la parte entera (grados).
    const minutes = ((absolute - degrees) * 60).toFixed(4); // Calcula los minutos decimales.
    const direction = isLat ? (decimal >= 0 ? "N" : "S") : (decimal >= 0 ? "E" : "W"); // Determina la dirección (N/S para latitud, E/W para longitud).
    return `${degrees}° ${minutes}' ${direction}`; // Formatea la salida.
};

// Función para convertir grados decimales a Grados, Minutos y Segundos (DMS).
const decimalToDMS = (decimal: number, isLat: boolean): string => {
    const absolute = Math.abs(decimal); // Obtiene el valor absoluto.
    const degrees = Math.floor(absolute); // Obtiene la parte entera (grados).
    const minutesFloat = (absolute - degrees) * 60; // Calcula los minutos con decimales.
    const minutes = Math.floor(minutesFloat); // Obtiene la parte entera de los minutos.
    const seconds = ((minutesFloat - minutes) * 60).toFixed(2); // Calcula los segundos decimales.
    const direction = isLat ? (decimal >= 0 ? "N" : "S") : (decimal >= 0 ? "E" : "W"); // Determina la dirección.
    return `${degrees}° ${minutes}' ${seconds}" ${direction}`; // Formatea la salida.
};

// Función para validar si una latitud y longitud están dentro de los rangos válidos.
const validarCoordenadas = (lat: number, lng: number): boolean =>
    lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180;

// Componente funcional CapturaClick que permite capturar las coordenadas del mouse al moverse sobre el mapa
// y mostrar un marcador con información detallada al hacer clic.
const CapturaClick: React.FC<CapturaClickProps> = ({ layerGroupRef }) => {
    // Estado para almacenar las coordenadas en varios formatos al mover el mouse.
    const [coordenadas, setCoordenadas] = useState<Coordenadas | null>(null);
    // Estado para almacenar las coordenadas del último clic del usuario.
    const [click, setClick] = useState<Coordenadas | null>(null);

    // Función para abrir las coordenadas en Google Maps en una nueva ventana.
    const openInGoogleMaps = (lat: number, lng: number) =>
        window.open(`https://www.google.com/maps?q=&layer=c&cbll=${lat},${lng}`, "_blank");

    // Función para abrir las coordenadas en Google Earth en una nueva ventana.
    const openInGoogleEarth = (lat: number, lng: number) =>
        window.open(`https://earth.google.com/web/@${lat},${lng},5a,5000d,10y,0h,0t,0r`, "_blank");

    // 🔸 Evento de movimiento del mouse sobre el mapa.
    useMapEvent("mousemove", ({ latlng }) => {
        const { lat, lng } = latlng; // Obtiene la latitud y longitud del evento.

        // Si las coordenadas no son válidas, no hace nada.
        if (!validarCoordenadas(lat, lng)) return;

        // Determina la zona UTM basada en la longitud.
        const zonaUTM = determinarZonaUTM(lng);
        // Convierte las coordenadas a MGRS si la latitud está dentro del rango válido (-80 a 84 grados).
        const mgrsCoord = lat <= 84 && lat >= -80 ? mgrs.forward([lng, lat], 8) : "Fuera de rango";

        // Convierte las coordenadas a UTM si se pudo determinar la zona UTM.
        const [xEste, yNorte] = zonaUTM
            ? proj4("EPSG:4326", zonaUTM, [lng, lat])
            : [null, null];

        // Actualiza el estado 'coordenadas' con todos los formatos calculados.
        setCoordenadas({
            lat, lng, xEste, yNorte, zonaUTM,
            mgrsCoord,
            latDMS: decimalToDMS(lat, true),
            lngDMS: decimalToDMS(lng, false),
            latDM: decimalToDM(lat, true),
            lngDM: decimalToDM(lng, false),
        });
    });

    // 🔸 Evento de clic del usuario en el mapa.
    useMapEvent("click", () => {
        // Al hacer clic, guarda las coordenadas actuales del mouse en el estado 'click'.
        if (coordenadas) setClick(coordenadas);
        // Limpia cualquier marcador anterior del LayerGroup.
        layerGroupRef.current?.clearLayers();
    });

    // Hook useEffect que se ejecuta cuando el estado 'click' cambia.
    useEffect(() => {
        // Si no hay un clic o la referencia al LayerGroup no está definida, no hace nada.
        if (!click || !layerGroupRef.current) return;

        // Extrae la información de coordenadas del estado 'click'.
        const {
            lat, lng, xEste, yNorte, zonaUTM,
            mgrsCoord, latDMS, lngDMS, latDM, lngDM
        } = click;

        // Define el contenido HTML para el popup del marcador.
        const popupContent = `
            📌 Coordenadas Decimales :<br>
            Longitud: <b>${lng?.toFixed(13) || "N/A"}</b><br>
            Latitud : <b>${lat?.toFixed(13) || "N/A"}</b><br><br>

            📌 Coordenadas Métricas (UTM - ${zonaUTM || "N/A"}):<br>
            X: <b>${xEste?.toFixed(4) || "N/A"}</b><br>
            Y: <b>${yNorte?.toFixed(4) || "N/A"}</b><br><br>

            📌 MGRS: <b>${mgrsCoord}</b><br><br>

            📌 DMS:<br>
            Longitud: <b>${lngDMS}</b><br>
            Latitud : <b>${latDMS}</b><br><br>

            📌 DM:<br>
            Longitus: <b>${lngDM}</b><br>
            Latitud: <b>${latDM}</b><br><br>

            <button id="googleMapsBtn">Ir a Google</button>
            <button id="googleEarthBtn">Ir a Earth</button>
        `;

        // Crea un marcador en la ubicación del clic con el icono personalizado y el popup.
        const marker = L.marker([lat, lng], { icon }).bindPopup(popupContent);

        // Agrega un listener para el evento 'popupopen' del marcador.
        marker.on("popupopen", () => {
            // Agrega listeners a los botones dentro del popup para abrir Google Maps y Google Earth.
            document.getElementById("googleMapsBtn")?.addEventListener("click", () =>
                openInGoogleMaps(lat, lng)
            );
            document.getElementById("googleEarthBtn")?.addEventListener("click", () =>
                openInGoogleEarth(lat, lng)
            );
        });

        // Agrega el marcador al LayerGroup, haciéndolo visible en el mapa.
        layerGroupRef.current.addLayer(marker);
    }, [click, layerGroupRef]); // El efecto se ejecuta cuando 'click' o 'layerGroupRef' cambian.

    // El componente no renderiza nada visualmente por sí mismo, solo interactúa con el mapa a través de eventos.
    return null;
};

export default CapturaClick;


siguiente componente :

import L from 'leaflet';
// Importa la librería Leaflet, esencial para trabajar con mapas interactivos.
import icon from "./icon";
// Importa el icono personalizado desde el archivo './icon'. Este icono se utilizará para los marcadores.

// Función para abrir la ubicación en Google Maps Street View en una nueva ventana del navegador.
// Recibe la latitud (lat) y la longitud (lng) como argumentos.
const openInGoogleMapsStreetView = (lat, lng) => {
    // Construye la URL de Google Maps con las coordenadas proporcionadas.
    // El formato '0{lat},{lng}' es un formato común para pasar coordenadas a Google Maps.
    const url = `https://www.google.com/maps?q=&layer=c&cbll=${lat},${lng}`;
    // Abre una nueva ventana o pestaña en el navegador con la URL de Google Maps.
    window.open(url, '_blank');
};

// Función para abrir la ubicación en Google Earth Web en una nueva ventana del navegador.
// Recibe la latitud (lat) y la longitud (lng) como argumentos.
const openInGoogleEarthStreetView = (lat, lng) => {
    // Construye la URL de Google Earth Web con las coordenadas proporcionadas.
    // El formato incluye la latitud, longitud y algunos parámetros de vista (altitud, inclinación, etc.).
    const url = `https://earth.google.com/web/@${lat},${lng},5a,5000d,10y,0h,0t,0r`;
    // Abre una nueva ventana o pestaña en el navegador con la URL de Google Earth Web.
    window.open(url, '_blank');
};

// Hook personalizado de React para dibujar un marcador en un mapa de Leaflet.
// Recibe tres argumentos:
// - punto: Un array que contiene la latitud y la longitud de la ubicación a marcar ([lat, lng]).
// - layerGroupRef: Una referencia mutable (Ref) a un LayerGroup de Leaflet donde se agregará el marcador.
// - mapRef: Una referencia mutable (Ref) a la instancia del objeto del mapa de Leaflet.
const useDibujarMarker = (punto, layerGroupRef, mapRef) => {
    // Si 'punto' es null o undefined (no se proporciona una ubicación), la función se detiene sin hacer nada.
    if (!punto) return;

    // Limpia todas las capas existentes dentro del LayerGroup referenciado.
    // Esto asegura que solo se muestre el marcador más reciente.
    layerGroupRef.current?.clearLayers();

    // Genera un ID único para el popup del marcador basado en la marca de tiempo actual.
    const popupId = `popup-${Date.now()}`;
    // Define el contenido HTML que se mostrará dentro del popup del marcador.
    // Incluye las coordenadas de la ubicación y dos botones para abrir en Google Maps y Google Earth.
    const popupContent = `
        <div id="${popupId}">
            <p><b>Ubicación:</b> ${punto[0]}, ${punto[1]}</p><br>
            <button class="googleMapsBtn">Ir a Google Maps</button>
            <button class="googleEarthBtn">Ir a Google Earth</button>
        </div>
    `;

    // Crea un nuevo marcador de Leaflet en las coordenadas proporcionadas ('punto').
    // Utiliza el 'icon' importado para la apariencia del marcador.
    // Asocia el 'popupContent' al marcador para que se muestre al hacer clic en él.
    const marker = L.marker([punto[0], punto[1]], { icon }).bindPopup(popupContent);

    // Agrega un listener para el evento 'popupopen' del marcador.
    // Este evento se dispara cuando el popup del marcador se abre.
    marker.on("popupopen", () => {
        // Obtiene el contenedor del popup utilizando el ID único generado.
        const container = document.getElementById(popupId);
        // Busca el botón con la clase 'googleMapsBtn' dentro del contenedor del popup
        // y agrega un event listener para abrir Google Maps al hacer clic.
        container?.querySelector(".googleMapsBtn")?.addEventListener("click", () =>
            openInGoogleMapsStreetView(punto[0], punto[1])
        );
        // Busca el botón con la clase 'googleEarthBtn' dentro del contenedor del popup
        // y agrega un event listener para abrir Google Earth al hacer clic.
        container?.querySelector(".googleEarthBtn")?.addEventListener("click", () =>
            openInGoogleEarthStreetView(punto[0], punto[1])
        );
    });

    // Agrega el marcador recién creado al LayerGroup referenciado, haciéndolo visible en el mapa.
    layerGroupRef.current?.addLayer(marker);
    // Centra el mapa en las coordenadas proporcionadas ('punto') y ajusta el nivel de zoom actual del mapa.
    // 'flyTo' proporciona una animación suave al moverse al nuevo centro y zoom.
    mapRef.current?.flyTo(punto, mapRef.current.getZoom());
};

// Exporta el hook personalizado 'useDibujarMarker' para que pueda ser utilizado en otros componentes.
export default useDibujarMarker;


siguiente componente :

import L from "leaflet";
// Importa la librería Leaflet, esencial para trabajar con mapas interactivos.

// Crea una instancia de un icono personalizado de Leaflet.
// Este objeto define la apariencia y el comportamiento del marcador que se mostrará en el mapa.
const Icon = L.icon({
    // Define el tamaño del icono del marcador en píxeles [ancho, alto].
    iconSize: [30, 41],
    // Define el punto del icono que corresponderá a la ubicación real de la coordenada [x, y].
    // En este caso, el punto inferior central del icono se alineará con la latitud y longitud.
    iconAnchor: [15, 41],
    // Define el punto del icono donde se anclará la parte superior del popup cuando se abra [x, y].
    // Un valor positivo en x desplaza el popup a la derecha, y un valor negativo en y lo desplaza hacia arriba.
    popupAnchor: [2, -40],
    // Especifica la URL de la imagen que se utilizará como icono del marcador.
    iconUrl: "/icons/map_marker.png",
    // Define el tamaño de la imagen de la sombra del marcador en píxeles [ancho, alto].
    shadowSize: [45, 45],
    // Define el punto de la imagen de la sombra que se alineará con el 'iconAnchor' [x, y].
    shadowAnchor: [10, 40],
    // Especifica la URL de la imagen que se utilizará como sombra del marcador.
    shadowUrl: "/icons/marker-shadow.png"
});

// Exporta la instancia del icono personalizado para que pueda ser utilizada en otros componentes
// al crear marcadores en el mapa de Leaflet.
export default Icon;