Un mapa de calor convierte una nube de puntos de coordenadas en una superficie de densidad instantaneamente legible. Los colores calidos marcan donde se agrupan los eventos, los colores frios marcan donde se adelgazan. Los usuarios captan el patron antes de leer una sola etiqueta. Por eso los mapas de calor son la opcion predeterminada para analisis de trafico peatonal, pronostico de demanda de entrega, herramientas de informes de crimenes e investigacion de precios de propiedades.
Este tutorial construye un mapa de calor interactivo desde cero utilizando el SDK de MapAtlas. Estructuraras datos de puntos como GeoJSON ponderado, representaras una capa de densidad con un degradado de color personalizado, conectaras un control deslizante de intensidad y terminaras con un ejemplo real de demanda de entrega. Al final tendras un patron reutilizable que puedes insertar en cualquier proyecto de JavaScript o React.
Si eres nuevo en el SDK de MapAtlas, lee primero Como anadir mapas interactivos a tu sitio web. Cubre instalacion, inicializacion de mapa y marcadores. Este tutorial continua donde se detiene el otro.
Cuando usar un mapa de calor
Los mapas de calor son la herramienta correcta cuando tienes un conjunto grande de ubicaciones de puntos individuales y quieres comunicar densidad, no identidad. Los marcadores individuales pierden significado despues de algunos cientos de puntos; un mapa de calor revela estructura que ningun monto de marcadores podria.
Casos de uso comunes:
- Analisis de trafico peatonal: seleccion de sitios minoristas, planificacion urbana, modelado de multitudes de eventos. Suelta un ping de GPS para cada cliente que paso por una puerta y el mapa de calor muestra que areas de una ciudad, centro comercial o lugar atraen a mas personas.
- Demanda de entrega: agrega origenes de ordenes por codigo postal o coordenada sin procesar para mostrar a los despachadores donde se concentra la demanda. Esto se alimenta directamente en la planificacion de zonas y asignacion de conductores.
- Datos de crimenes e incidentes: paneles de analisis policial, mapas de riesgo de seguros y herramientas de informes de seguridad publica todos usan mapas de calor de densidad para comunicar riesgo espacial sin abrumar a los usuarios con marcadores individuales.
- Gradientes de precios de propiedades: cuando se combina con valores ponderados, un mapa de calor puede mostrar donde los precios son mas altos en toda una ciudad. Consulta el tutorial de mapa de propiedades inmobiliarias para mas informacion sobre construccion de herramientas de mapa enfocadas en propiedades.
Si tu pregunta es "donde suceden mas las cosas", un mapa de calor es tu respuesta.
Formato de datos: puntos GeoJSON ponderados
La capa de mapa de calor de MapAtlas lee una FeatureCollection de Point GeoJSON estandar. Cada caracteristica puede llevar una propiedad weight que escala su contribucion a la superficie de densidad. Un pedido de entrega de 10 articulos contribuye mas calor que un pedido de un articulo; un centro de transito importante genera mas trafico peatonal que una calle lateral.
const demandData = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8952, 52.3702] },
properties: { weight: 8, zone: "centrum" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9123, 52.3601] },
properties: { weight: 3, zone: "oost" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8801, 52.3780] },
properties: { weight: 12, zone: "west" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9041, 52.3540] },
properties: { weight: 1, zone: "south" }
}
]
};
El valor weight es adimensional. Lo normalizas en relacion con el rango de tu conjunto de datos. Si tu zona de mayor demanda genera 500 ordenes y tu mas tranquila genera 10, mapea esos a una escala de 1 a 10 antes de pasarlos. Esto mantiene el mapa de calor visualmente significativo en conjuntos de datos con conteos absolutos muy diferentes.
Sin una propiedad weight, cada punto contribuye por igual y el mapa de calor refleja pura densidad de conteo.
Prerequisites
Before you start:
- A MapAtlas API key (sign up free, no credit card required)
- Node.js 18+ for npm-based projects, or a plain HTML page if you prefer the CDN
Step 1: Install and Initialise the Map
Install the SDK:
npm install @mapmetrics/mapmetrics-gl
Or load it via CDN in a plain HTML file:
<link
rel="stylesheet"
href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css"
/>
<script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
Add a container with a defined height:
<div id="map" style="width: 100%; height: 600px;"></div>
Initialise the map. The Dark style makes heatmap colour gradients pop against the background, which is why it is the preferred base for density visualisations:
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const map = new mapmetricsgl.Map({
container: 'map',
style: 'https://tiles.mapatlas.eu/styles/dark/style.json?key=YOUR_API_KEY',
center: [4.9041, 52.3676], // Amsterdam
zoom: 12,
});
Step 2: Add the Heatmap Layer
Register your GeoJSON as a source, then add a heatmap layer that reads from it. The map.on('load', ...) callback ensures the style has finished loading before you modify it.
map.on('load', () => {
map.addSource('demand', {
type: 'geojson',
data: demandData,
});
map.addLayer({
id: 'demand-heatmap',
type: 'heatmap',
source: 'demand',
paint: {
// Weight each point by its 'weight' property (defaults to 1 if absent)
'heatmap-weight': [
'interpolate', ['linear'],
['get', 'weight'],
0, 0,
12, 1
],
// Radius in pixels; larger = smoother but less precise
'heatmap-radius': 30,
// Overall opacity of the heatmap layer
'heatmap-opacity': 0.85,
},
});
});
At this point you have a working heatmap. Blue-green areas are low density; the default gradient shifts toward yellow and red at high density. The next step replaces the default palette with a deliberate colour scheme.
Paso 3: personalizacion del degradado de color
La propiedad heatmap-color mapea valores de densidad (0 a 1) a colores utilizando la misma expresion interpolate utilizada en el resto del sistema de pintura de MapAtlas. La densidad 0 es transparente para que la capa base del mapa se muestre en areas vacias.
map.addLayer({
id: 'demand-heatmap',
type: 'heatmap',
source: 'demand',
paint: {
'heatmap-weight': [
'interpolate', ['linear'],
['get', 'weight'],
0, 0,
12, 1
],
'heatmap-radius': 30,
'heatmap-opacity': 0.85,
'heatmap-color': [
'interpolate', ['linear'],
['heatmap-density'],
0, 'rgba(0, 0, 255, 0)', // transparent at zero density
0.2, 'rgba(0, 128, 255, 0.6)',
0.4, 'rgba(0, 230, 200, 0.7)',
0.6, 'rgba(100, 230, 0, 0.8)',
0.8, 'rgba(255, 200, 0, 0.9)',
1.0, 'rgba(255, 50, 0, 1)' // bright red at peak density
],
},
});
Este degradado corre azul (disperso) pasando por verde y amarillo a rojo (denso), coincidiendo con el modelo mental que la mayoria de los usuarios traen de mapas de radar meteorologico e imagenes termicas. Si tu aplicacion tiene una paleta de colores de marca, reemplaza los valores RGB con tus propias paradas; la interpolacion maneja transiciones suaves automaticamente.
Consejo de diseno: Para paneles profesionales y herramientas de analiticamente, mantén el extremo de baja densidad casi transparente. Preserva la legibilidad del mapa base en areas tranquilas y atrae la vista naturalmente a las zonas calientes.
Paso 4: annadiendo un control deslizante de radio para control interactivo
El radio del mapa de calor controla cuanto "irradia" influencia cada punto. Un radio pequeno (10 a 15px) muestra clusters de grano fino; un radio grande (50 a 80px) produce una superficie mas amplia y suave. Diferentes casos de uso requieren diferentes predeterminados: el trafico peatonal en un centro urbano denso necesita un radio pequeno, la demanda de entrega nacional en todo un pais necesita uno grande.
Dale a los usuarios un control deslizante para que puedan ajustar sobre la marcha:
<div id="controls" style="position: absolute; top: 16px; left: 16px; z-index: 1;
background: white; padding: 12px 16px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.2);">
<label style="font-size: 14px; font-weight: 600;">
Radius: <span id="radius-value">30</span>px
</label>
<br />
<input id="radius-slider" type="range" min="5" max="80" value="30" style="width: 180px; margin-top: 6px;" />
</div>
Wire the slider to setPaintProperty, which updates the layer without re-adding it:
const slider = document.getElementById('radius-slider');
const radiusLabel = document.getElementById('radius-value');
slider.addEventListener('input', () => {
const radius = Number(slider.value);
radiusLabel.textContent = radius;
map.setPaintProperty('demand-heatmap', 'heatmap-radius', radius);
});
setPaintProperty is a live update with no flicker. The heatmap re-renders on the GPU in the same frame. This pattern works for any paint property: opacity, intensity, colour stops.
Paso 5: escalado de intensidad por nivel de zoom
En niveles de zoom bajos (vista de ciudad), los puntos cercanos se superponen mucho y el mapa de calor puede parecer uniformemente saturado. En zoom alto (nivel de calle), los mismos puntos se dispersan y la superficie de densidad se ve dispersa. La compensacion de intensidad vinculada al zoom mantiene la visualizacion legible en cada zoom.
paint: {
// ...other paint properties...
'heatmap-intensity': [
'interpolate', ['linear'],
['zoom'],
8, 1, // low zoom: normal intensity
14, 3 // high zoom: boost intensity to compensate for point spread
],
'heatmap-radius': [
'interpolate', ['linear'],
['zoom'],
8, 20, // small radius at city scale
14, 50 // larger radius at street scale
],
},
Both properties use the same interpolate over zoom expression. The values between the stops are interpolated linearly, so the transition is smooth as the user zooms.
Ejemplo del mundo real: mapa de calor de demanda de entrega
Aqui hay una implementacion completa y autonoma para un panel de demanda de entrega. Los pedidos llegan de una API como una FeatureCollection GeoJSON. El mapa de calor se actualiza cada vez que el usuario cambia el filtro de tiempo.
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const API_KEY = 'YOUR_API_KEY';
const map = new mapmetricsgl.Map({
container: 'map',
style: `https://tiles.mapatlas.eu/styles/dark/style.json?key=${API_KEY}`,
center: [4.9041, 52.3676],
zoom: 11,
});
async function loadOrders(hourFrom, hourTo) {
const res = await fetch(
`/api/orders/geojson?hour_from=${hourFrom}&hour_to=${hourTo}`
);
return res.json(); // returns a GeoJSON FeatureCollection
}
map.on('load', async () => {
const orders = await loadOrders(8, 12); // morning peak
map.addSource('orders', { type: 'geojson', data: orders });
map.addLayer({
id: 'order-heatmap',
type: 'heatmap',
source: 'orders',
paint: {
'heatmap-weight': [
'interpolate', ['linear'], ['get', 'items'],
0, 0, 20, 1
],
'heatmap-color': [
'interpolate', ['linear'], ['heatmap-density'],
0, 'rgba(0, 0, 255, 0)',
0.2, 'rgba(0, 128, 255, 0.5)',
0.5, 'rgba(0, 230, 150, 0.8)',
0.8, 'rgba(255, 200, 0, 0.9)',
1.0, 'rgba(255, 50, 0, 1)'
],
'heatmap-radius': [
'interpolate', ['linear'], ['zoom'],
8, 20, 14, 50
],
'heatmap-intensity': [
'interpolate', ['linear'], ['zoom'],
8, 1, 14, 3
],
'heatmap-opacity': 0.9,
},
});
// Time-of-day filter buttons
document.querySelectorAll('[data-hour-range]').forEach((btn) => {
btn.addEventListener('click', async () => {
const [from, to] = btn.dataset.hourRange.split('-').map(Number);
const newOrders = await loadOrders(from, to);
map.getSource('orders').setData(newOrders);
});
});
});
La llamada setData en una fuente existente reemplaza el GeoJSON sin re-anadir la capa. El mapa de calor se vuelve a renderizar automaticamente. Este patron se adapta a cualquier filtro basado en tiempo: hora del dia, dia de la semana, condicion del clima.
Para la capa de planificacion de ruta que tipicamente acompana un mapa de calor de demanda de entrega, consulta el tutorial de API de optimizacion de rutas. Superponer una ruta de entrega optimizada sobre un mapa de calor de demanda proporciona a los despachadores una imagen operativa completa en una sola vista.
Consejos de rendimiento para conjuntos de datos grandes
La capa de mapa de calor de MapAtlas utiliza WebGL y se renderiza rapidamente, pero el canal de datos que la alimenta puede convertirse en un cuello de botella a escala.
Preagregar en el servidor para conjuntos de datos muy grandes. Si tienes millones de pings de GPS, no los envies todos al navegador. Ejecuta una agregacion espacial del lado del servidor (cuadricula hexagonal H3, arbol cuaternario, o un redondeado de cuadricula simple) que reduzca tus 1 millon de puntos sin procesar a 10,000 celdas de cuadricula con un campo count y weight. El mapa de calor se vera identico para el usuario y se cargara en una fraccion del tiempo.
Transmite actualizaciones de forma incremental. Para datos en vivo (trafico peatonal en tiempo real, colocacion de pedidos en vivo), usa setData con una ventana rodante de puntos recientes en lugar de acumular un objeto GeoJSON cada vez mas grande. Mantén la fuente en un recuento de puntos maximo fijo y desaloja registros antiguos.
Usa un maxzoom en la fuente. Anadir maxzoom: 14 a tu llamada addSource le dice al SDK que deje de solicitar datos de tiles por encima del zoom 14. Para mapas de calor esto raramente importa ya que las capas de mapa de calor leen una fuente GeoJSON plana unica en lugar de datos en mosaico, pero evita re-procesamiento innecesario en niveles de zoom alto.
Reduce la complejidad de la propiedad de pintura. Cada parada interpolate adicional en una expresion de pintura anade costo de evaluacion de GPU por fotograma. Para aplicaciones dirigidas a dispositivos moviles, simplifica el degradado de color a tres o cuatro paradas y suelta el escalado de radio/intensidad vinculado al zoom en vistas de menor prioridad.
Inicializa perezosamente el mapa. Envuelve toda la inicializacion del mapa en una devolucion de llamada IntersectionObserver para que solo se ejecute cuando el contenedor del mapa se desplaza hacia la vista. Esto difiere el paquete SDK de la carga de pagina inicial y es especialmente valioso en paginas de marketing donde el mapa esta debajo del pliegue.
Para un analisis mas profundo de patrones de rendimiento de mapas, incluyendo carga perezosa y agrupamiento, la lista de verificacion de produccion en Como anadir mapas interactivos a tu sitio web cubre la lista completa.
Integracion de React
Envolver el mapa de calor en un componente de React sigue el mismo patron que cualquier mapa de MapAtlas: inicializar en useEffect, exponer estado para el control deslizante y filtro via useState y limpiar al desmontar.
import { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
export function DeliveryHeatmap({ geojson, apiKey }) {
const containerRef = useRef(null);
const mapRef = useRef(null);
const [radius, setRadius] = useState(30);
useEffect(() => {
const map = new mapmetricsgl.Map({
container: containerRef.current,
style: `https://tiles.mapatlas.eu/styles/dark/style.json?key=${apiKey}`,
center: [4.9041, 52.3676],
zoom: 11,
});
mapRef.current = map;
map.on('load', () => {
map.addSource('orders', { type: 'geojson', data: geojson });
map.addLayer({
id: 'order-heatmap',
type: 'heatmap',
source: 'orders',
paint: {
'heatmap-radius': radius,
'heatmap-opacity': 0.9,
},
});
});
return () => map.remove();
}, [apiKey]);
// Update radius without remounting the map
useEffect(() => {
const map = mapRef.current;
if (!map || !map.getLayer('order-heatmap')) return;
map.setPaintProperty('order-heatmap', 'heatmap-radius', radius);
}, [radius]);
return (
<div>
<div style={{ padding: '8px 0' }}>
<label>
Radius: {radius}px
<input
type="range" min={5} max={80} value={radius}
onChange={e => setRadius(Number(e.target.value))}
style={{ marginLeft: 8, width: 160 }}
/>
</label>
</div>
<div ref={containerRef} style={{ width: '100%', height: '600px' }} />
</div>
);
}
En Next.js, importa este componente con dynamic(() => import('./DeliveryHeatmap'), { ssr: false }) para evitar errores de renderizado del lado del servidor del SDK solo del navegador.
Que construir a continuacion
Ahora tienes un mapa de calor interactivo que funciona con datos ponderados, un degradado de color personalizado, un control deslizante de radio e intensidad vinculada al zoom. Aqui es donde llevarlo:
- Superpón una capa de enrutamiento sobre el mapa de calor para mostrar rutas de entrega planificadas contra la superficie de demanda. El tutorial de API de optimizacion de rutas camina a traves de la implementacion completa.
- Anada una animacion de tiempo: cicla a traves de instantaneas horarias con un bucle
setIntervalllamandosetDataen la fuente. Esto convierte un mapa de densidad estatico en un lapso de tiempo de como la demanda se mueve durante el dia. - Combina con datos de precios de propiedades para mostrar gradientes de precio por vecindario. El tutorial de mapa de propiedades inmobiliarias cubre patrones de datos ponderados para plataformas de propiedades.
- Verifica la pagina de precios de MapAtlas para encontrar el plan correcto para tu trafico de produccion.
La referencia completa del SDK y ejemplos adicionales estan disponibles en docs.mapatlas.xyz.
Frequently Asked Questions
What is the difference between a heatmap and a choropleth map?
A heatmap visualises point density by blending nearby points into a continuous colour gradient, ideal for raw coordinate data like GPS pings or event locations. A choropleth map colours pre-defined geographic areas (countries, postcodes, census tracts) by a statistical value. Use a heatmap when you have many individual points; use a choropleth when your data is already aggregated by region.
How many data points can a JavaScript heatmap handle?
The MapAtlas heatmap layer renders on the GPU via WebGL, so it handles tens of thousands of points without frame drops at normal zoom levels. Above roughly 500,000 points, pre-aggregating your data server-side into a lower-resolution grid and switching to a GeoJSON fill-extrusion or circle layer gives better performance on low-end devices.
Can I use MapAtlas heatmaps for free?
Yes. MapAtlas has a free tier that includes map tile rendering, GeoJSON layer support, and heatmap layers. The free plan covers development and low-volume production use. See mapatlas.eu/pricing for full plan details.
Do heatmaps work on mobile browsers?
Yes. The MapAtlas SDK uses WebGL for rendering, which is supported in all modern mobile browsers including Safari on iOS and Chrome on Android. For very large datasets on low-end mobile hardware, reducing the point count or increasing the heatmap radius keeps frame rates smooth.
Related reading:

