npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@datosgeo-atdt/geo-herramientas

v1.0.0

Published

Herramientas de análisis geoespacial para mapas MapLibre GL JS

Readme

@datosgeo-atdt/geo-herramientas

Herramientas de análisis geoespacial para React + MapLibre GL JS. Incluye hooks y componentes listos para usar: rutas óptimas, isócronas de tiempo, edificios 3D, terreno 3D y selector de estilos de mapa.

Instalación

npm install @datosgeo-atdt/geo-herramientas

Dependencias requeridas (peer dependencies)

Este paquete requiere que tu proyecto ya tenga instalado:

npm install maplibre-gl react react-dom

Importar estilos CSS

Importante: debes importar el CSS del paquete una sola vez en tu proyecto (por ejemplo en tu archivo main.tsx o App.tsx):

import '@datosgeo-atdt/geo-herramientas/dist/index.css';

Sin esta importación los botones aparecerán sin estilo (negros y sin posicionamiento).


Uso rápido

Todos los hooks y componentes reciben un mapRef — la referencia al objeto Map de MapLibre que obtienes de tu instancia del mapa.

import { useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@datosgeo-atdt/geo-herramientas/dist/index.css';

const mapRef = useRef<maplibregl.Map>(null);

Componentes

<MapToolbar />

Barra de herramientas completa que integra todos los controles en un solo componente.

import { MapToolbar, useOptimalRoute, useIsochrone, useBuildings3D } from '@datosgeo-atdt/geo-herramientas';

function MiMapa() {
  const mapRef = useRef<maplibregl.Map>(null);
  const [currentStyle, setCurrentStyle] = useState('https://www.mapabase.atdt.gob.mx/style.json');

  const { routeMode, setRouteMode, clearRoute } = useOptimalRoute({ mapRef });
  const { isochroneMode, setIsochroneMode, isLoading, isComplete, clearIsochrone } = useIsochrone({
    mapRef,
    orsApiKey: 'TU_API_KEY_ORS',
  });
  const { buildings3DEnabled, toggleBuildings3D } = useBuildings3D({ mapRef });

  return (
    <div style={{ position: 'relative', width: '100%', height: '100vh' }}>
      {/* Tu mapa */}
      <div ref={/* tu container ref */} style={{ width: '100%', height: '100%' }} />

      <MapToolbar
        mapRef={mapRef}
        routeMode={routeMode}
        onToggleRouteMode={() => setRouteMode(!routeMode)}
        isochroneMode={isochroneMode}
        onToggleIsochroneMode={() => setIsochroneMode(!isochroneMode)}
        isochroneLoading={isLoading}
        isochroneComplete={isComplete}
        buildings3DEnabled={buildings3DEnabled}
        onToggleBuildings3D={toggleBuildings3D}
        currentStyle={currentStyle}
        onStyleChange={setCurrentStyle}
        onClear={() => { clearRoute(); clearIsochrone(); }}
        activeColor="#611232"
      />
    </div>
  );
}

Props de MapToolbar

| Prop | Tipo | Requerido | Descripción | |------|------|-----------|-------------| | mapRef | RefObject<maplibregl.Map> | ✅ | Referencia al mapa | | routeMode | boolean | ✅ | Estado del modo ruta | | onToggleRouteMode | () => void | ✅ | Activa/desactiva modo ruta | | isochroneMode | boolean | ✅ | Estado del modo isocrona | | onToggleIsochroneMode | () => void | ✅ | Activa/desactiva modo isocrona | | isochroneLoading | boolean | — | Muestra spinner de carga | | isochroneComplete | boolean | — | Muestra indicador de éxito | | buildings3DEnabled | boolean | ✅ | Estado de edificios 3D | | onToggleBuildings3D | () => void | ✅ | Activa/desactiva edificios 3D | | terrainEnabled | boolean | — | Estado del terreno 3D (controlado externo) | | setTerrainEnabled | Dispatch<SetStateAction<boolean>> | — | Setter para terreno controlado externo | | currentStyle | string | ✅ | URL del estilo de mapa activo | | onStyleChange | (url: string) => void | ✅ | Callback al cambiar estilo | | mapStyles | MapStyle[] | — | Lista personalizada de estilos | | onClear | () => void | ✅ | Limpia todas las capas | | activeColor | string | — | Color de acento (default #611232) | | className | string | — | Clase CSS extra para la barra |


<MapStyleSelector />

Selector flotante de estilos de mapa base (blanco, color, oscuro, satélite).

import { MapStyleSelector } from '@datosgeo-atdt/geo-herramientas';

const [currentStyle, setCurrentStyle] = useState('https://www.mapabase.atdt.gob.mx/style.json');

<MapStyleSelector
  currentStyle={currentStyle}
  onStyleChange={setCurrentStyle}
/>

Para usar estilos personalizados:

const misEstilos = [
  { id: 'claro', label: 'Claro', url: 'https://mi-servidor/style-light.json', icon: 'map' },
  { id: 'oscuro', label: 'Oscuro', url: 'https://mi-servidor/style-dark.json', icon: 'map-dark' },
  { id: 'satelite', label: 'Satélite', url: 'https://mi-servidor/style-sat.json', icon: 'satellite' },
];

<MapStyleSelector
  currentStyle={currentStyle}
  onStyleChange={setCurrentStyle}
  styles={misEstilos}
  activeColor="#1a73e8"
/>

Props de MapStyleSelector

| Prop | Tipo | Requerido | Descripción | |------|------|-----------|-------------| | currentStyle | string | ✅ | URL del estilo activo | | onStyleChange | (url: string) => void | ✅ | Callback al seleccionar un estilo | | styles | MapStyle[] | — | Estilos personalizados (usa los de ATDT por defecto) | | activeColor | string | — | Color del botón activo |


<TerrainToggle />

Botón para activar/desactivar el terreno 3D con hillshading.

import { TerrainToggle } from '@datosgeo-atdt/geo-herramientas';

// Modo no controlado (maneja su propio estado)
<TerrainToggle mapRef={mapRef} />

// Modo controlado (el estado lo manejas tú)
const [terrainEnabled, setTerrainEnabled] = useState(false);

<TerrainToggle
  mapRef={mapRef}
  terrainEnabled={terrainEnabled}
  setTerrainEnabled={setTerrainEnabled}
  exaggeration={2}
  activePitch={60}
  activeBearing={-45}
/>

Props de TerrainToggle

| Prop | Tipo | Requerido | Descripción | |------|------|-----------|-------------| | mapRef | RefObject<maplibregl.Map> | ✅ | Referencia al mapa | | terrainEnabled | boolean | — | Estado controlado externamente | | setTerrainEnabled | Dispatch<SetStateAction<boolean>> | — | Setter controlado externamente | | exaggeration | number | — | Exageración del terreno (default 2) | | activePitch | number | — | Inclinación al activar (default 60) | | activeBearing | number | — | Rotación al activar (default -45) |


Hooks

useOptimalRoute

Calcula la ruta óptima entre múltiples puntos usando OSRM. El usuario hace clic en el mapa para agregar nodos.

import { useOptimalRoute } from '@datosgeo-atdt/geo-herramientas';

const {
  routeMode,
  setRouteMode,
  selectedNodes,
  setSelectedNodes,
  routeData,
  distanceText,
  clearRoute,
  isCalculating,
} = useOptimalRoute({
  mapRef,
  lineColor: '#007cbf',   // color de la línea de ruta
  lineWidth: 4,            // grosor en px
  osrmUrl: 'https://router.project-osrm.org',  // servidor OSRM
  profile: 'driving',      // 'driving' | 'walking' | 'cycling'
});

// Activar modo: el usuario hace clic en el mapa para agregar puntos
<button onClick={() => setRouteMode(!routeMode)}>
  {routeMode ? 'Cancelar ruta' : 'Trazar ruta'}
</button>

{distanceText && <p>Distancia total: {distanceText}</p>}

<button onClick={clearRoute}>Limpiar</button>

Opciones de useOptimalRoute

| Parámetro | Tipo | Default | Descripción | |-----------|------|---------|-------------| | mapRef | RefObject<maplibregl.Map> | — | Referencia al mapa | | lineColor | string | '#007cbf' | Color de la línea de ruta | | lineWidth | number | 4 | Grosor de la línea en px | | osrmUrl | string | 'https://router.project-osrm.org' | URL del servidor OSRM | | profile | string | 'driving' | Perfil de transporte |

Valores retornados

| Valor | Tipo | Descripción | |-------|------|-------------| | routeMode | boolean | Si el modo ruta está activo | | setRouteMode | (active: boolean) => void | Activa/desactiva el modo | | selectedNodes | SelectedNode[] | Nodos seleccionados por el usuario | | setSelectedNodes | Dispatch | Setter de nodos (para eliminar puntos manualmente) | | routeData | number[][] | Coordenadas de la ruta calculada | | distanceText | string \| null | Distancia formateada (ej. "2.34 km") | | clearRoute | () => void | Elimina ruta y puntos del mapa | | isCalculating | boolean | Indica si hay un cálculo en curso |


useIsochrone

Genera isócronas de tiempo (áreas alcanzables) usando OpenRouteService. El usuario hace clic en el mapa para generar el análisis.

import { useIsochrone } from '@datosgeo-atdt/geo-herramientas';

const {
  isochroneMode,
  setIsochroneMode,
  isLoading,
  isComplete,
  clearIsochrone,
  timeRange,
  setTimeRange,
} = useIsochrone({
  mapRef,
  orsApiKey: 'TU_API_KEY',           // API key de openrouteservice.org
  ranges: [900, 1800, 3600],          // rangos en segundos: 15 min, 30 min, 1 hora
  profile: 'driving-car',             // perfil de movilidad
  colors: ['#D79BB3', '#A24D7B', '#4A0D4D'],  // un color por rango
});

// Activa el modo: el siguiente clic en el mapa genera las isócronas
<button onClick={() => setIsochroneMode(!isochroneMode)}>
  {isochroneMode ? 'Cancelar' : 'Generar isócronas'}
</button>

// Control de visibilidad por tiempo (en minutos)
<input
  type="range"
  min={15}
  max={60}
  value={timeRange}
  onChange={e => setTimeRange(Number(e.target.value))}
/>
<span>Mostrar hasta {timeRange} min</span>

Obtén tu API key gratuita en openrouteservice.org

Opciones de useIsochrone

| Parámetro | Tipo | Default | Descripción | |-----------|------|---------|-------------| | mapRef | RefObject<maplibregl.Map> | — | Referencia al mapa | | orsApiKey | string | — | API key de OpenRouteService | | ranges | number[] | [900, 1800, 3600] | Rangos en segundos | | profile | string | 'driving-car' | Perfil ORS ('driving-car', 'foot-walking', 'cycling-regular') | | colors | string[] | colores morados | Un color por rango, del más pequeño al más grande |

Valores retornados

| Valor | Tipo | Descripción | |-------|------|-------------| | isochroneMode | boolean | Si el modo está activo | | setIsochroneMode | (active: boolean) => void | Activa/desactiva el modo | | isLoading | boolean | Calculando isócronas | | isComplete | boolean | Cálculo completado (se resetea en 2.5 s) | | clearIsochrone | () => void | Elimina las capas del mapa | | timeRange | number | Minutos visibles actualmente | | setTimeRange | Dispatch | Controla qué rangos se muestran |


useBuildings3D

Renderiza edificios en 3D con extrusión. Soporta tres modos de fuente: GeoJSON, vector tiles propios, o reutilizar una fuente que ya viene en el estilo del mapa.

Modo 1 — GeoJSON (más simple)

import { useBuildings3D } from '@datosgeo-atdt/geo-herramientas';

const { buildings3DEnabled, toggleBuildings3D, isLoading } = useBuildings3D({
  mapRef,
  footprintsUrl: 'https://mi-servidor/edificios.geojson',
  heightProperty: 'altura',
  defaultHeight: 10,
  buildingColor: '#c0b8b0',
  baseColor: '#a09890',
});

<button onClick={toggleBuildings3D} disabled={isLoading}>
  {buildings3DEnabled ? 'Ocultar edificios 3D' : 'Ver edificios 3D'}
</button>

Modo 2 — Vector tiles propios

Cuando tienes un servidor de vector tiles propio que sí tiene CORS configurado para tu dominio:

const { buildings3DEnabled, toggleBuildings3D, isLoading } = useBuildings3D({
  mapRef,
  sourceType: 'vector',
  footprintsUrl: 'https://mi-servidor/api/edificios',  // endpoint TileJSON
  // sourceLayer no es necesario si el layer se llama 'footprints' (default automático)
  sourceLayer: 'edificios',   // especificar solo si el layer tiene otro nombre
  heightProperty: 'height_mean',
  defaultHeight: 10,
});

Modo 3 — Reutilizar fuente del estilo (recomendado para CORS)

Si tu estilo de mapa ya incluye la fuente de edificios, úsala directamente en vez de cargarla de nuevo. Esto evita peticiones adicionales que pueden fallar por CORS cuando el servidor de tiles no permite tu dominio de desarrollo:

// El estilo base ya tiene definida la fuente "lotes" con los tiles de edificios.
// Pasamos existingSourceId para reutilizarla sin hacer ninguna petición extra.
// sourceLayer se omite: cuando hay existingSourceId se usa 'footprints' automáticamente.
const { buildings3DEnabled, toggleBuildings3D, isLoading } = useBuildings3D({
  mapRef,
  existingSourceId: 'lotes',    // nombre exacto de la fuente en el estilo
  heightProperty: 'height_mean',
  buildingColor: '#DDDDDD',     // opcional: color personalizado
  buildingOpacity: 0.7,         // opcional: opacidad entre 0 y 1
});

¿Cómo sé el nombre de la fuente? Está definido en el JSON de tu estilo de mapa, dentro de la clave "sources". Por ejemplo: "sources": { "lotes": { "type": "vector", ... } }. ¿Y el sourceLayer? Por defecto es 'footprints' cuando usas existingSourceId o sourceType: 'vector'. Solo pásalo explícitamente si tu tileset usa un nombre diferente.

Opciones de useBuildings3D

| Parámetro | Tipo | Default | Descripción | |-----------|------|---------|-------------| | mapRef | RefObject<maplibregl.Map> | — | Referencia al mapa | | existingSourceId | string | — | ID de una fuente ya cargada por el estilo. Si se pasa, footprintsUrl se ignora y no se hace ninguna petición extra | | footprintsUrl | string | URL ATDT | URL del GeoJSON o TileJSON. Se usa solo si existingSourceId no se especifica | | sourceType | 'geojson' \| 'vector' | 'geojson' | Tipo de fuente cuando se crea una nueva (ignorado con existingSourceId) | | sourceLayer | string | 'footprints'* | Nombre del layer dentro del tileset. *Se auto-asigna 'footprints' cuando se usa existingSourceId o sourceType: 'vector'; solo pásalo si tu tileset usa otro nombre | | heightProperty | string | 'altura' | Propiedad del feature que contiene la altura en metros | | defaultHeight | number | 10 | Altura en metros cuando el feature no tiene la propiedad | | buildingColor | string | '#c0b8b0' | Color de la extrusión | | buildingOpacity | number | 0.85 | Opacidad de la extrusión (0–1) | | baseColor | string | '#a09890' | Color del piso del edificio |


Ejemplo completo

El patrón recomendado separa la lógica de los hooks en un componente hijo que solo se monta después de que el mapa está listo (mapLoaded). Esto evita que los hooks intenten acceder al mapa antes de que exista.

import { useRef, useState, useEffect } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import {
  MapToolbar,
  useOptimalRoute,
  useIsochrone,
  useBuildings3D,
} from '@datosgeo-atdt/geo-herramientas';

const STYLE_INICIAL = 'https://www.mapabase.atdt.gob.mx/style.json';
const ORS_API_KEY = 'TU_API_KEY_ORS';

// ── Componente hijo: solo se monta cuando el mapa ya está listo ──────────────
function MapControls({
  mapRef,
  currentStyle,
  setCurrentStyle,
}: {
  mapRef: React.RefObject<maplibregl.Map>;
  currentStyle: string;
  setCurrentStyle: (s: string) => void;
}) {
  const route = useOptimalRoute({ mapRef });
  const isochrone = useIsochrone({ mapRef, orsApiKey: ORS_API_KEY });

  // Si el estilo del mapa ya incluye la fuente "lotes", usa existingSourceId
  // para evitar una segunda petición que puede fallar por CORS en desarrollo.
  // sourceLayer se omite: con existingSourceId se usa 'footprints' automáticamente.
  const buildings = useBuildings3D({
    mapRef,
    existingSourceId: 'lotes',   // fuente ya cargada por el estilo base
    heightProperty: 'height_mean',
  });

  return (
    <MapToolbar
      mapRef={mapRef}
      routeMode={route.routeMode}
      onToggleRouteMode={() => route.setRouteMode(!route.routeMode)}
      isochroneMode={isochrone.isochroneMode}
      onToggleIsochroneMode={() => isochrone.setIsochroneMode(!isochrone.isochroneMode)}
      isochroneLoading={isochrone.isLoading}
      isochroneComplete={isochrone.isComplete}
      buildings3DEnabled={buildings.buildings3DEnabled}
      onToggleBuildings3D={buildings.toggleBuildings3D}
      currentStyle={currentStyle}
      onStyleChange={setCurrentStyle}
      onClear={() => { route.clearRoute(); isochrone.clearIsochrone(); }}
    />
  );
}

// ── Componente principal del mapa ────────────────────────────────────────────
export default function MapView() {
  const containerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<maplibregl.Map>(null);
  const [mapLoaded, setMapLoaded] = useState(false);
  const [currentStyle, setCurrentStyle] = useState(STYLE_INICIAL);
  const isFirstStyle = useRef(true);

  // Inicializar el mapa
  useEffect(() => {
    if (!containerRef.current) return;
    const map = new maplibregl.Map({
      container: containerRef.current,
      style: STYLE_INICIAL,
      center: [-99.133, 19.432],
      zoom: 11,
    });
    (mapRef as React.MutableRefObject<maplibregl.Map>).current = map;
    map.addControl(new maplibregl.NavigationControl());
    map.on('style.load', () => setMapLoaded(true));
    return () => { map.remove(); setMapLoaded(false); };
  }, []);

  // Cambiar estilo de mapa base
  useEffect(() => {
    if (isFirstStyle.current) { isFirstStyle.current = false; return; }
    setMapLoaded(false);
    mapRef.current?.setStyle(currentStyle);
  }, [currentStyle]);

  return (
    // ⚠️ position: relative es obligatorio para que los botones
    // se posicionen correctamente sobre el mapa
    <div style={{ position: 'relative', width: '100%', height: '100vh' }}>
      <div ref={containerRef} style={{ width: '100%', height: '100%' }} />
      {mapLoaded && (
        <MapControls
          mapRef={mapRef as React.RefObject<maplibregl.Map>}
          currentStyle={currentStyle}
          setCurrentStyle={setCurrentStyle}
        />
      )}
    </div>
  );
}

Tipos exportados

import type {
  MapRef,
  SelectedNode,
  MapStyle,
  UseOptimalRouteOptions,
  UseOptimalRouteReturn,
  UseIsochroneOptions,
  UseIsochroneReturn,
  UseBuildings3DOptions,
  UseBuildings3DReturn,
  TerrainToggleProps,
  MapStyleSelectorProps,
  MapToolbarProps,
} from '@datosgeo-atdt/geo-herramientas';

Licencia

MIT © DatosGeo ATDT