Skip to main content
Heatmap JavaScript: tutorial per una mappa di densità interattiva
Tutorials

Heatmap JavaScript: tutorial per una mappa di densità interattiva

Heatmap JavaScript: tutorial per costruire una mappa di densità interattiva con MapAtlas. Formattazione dati, intensity weighting, gradienti di colore e casi d'uso reali.

Brent van der Heiden15 min read
#javascript heatmap#heatmap javascript tutorial#interactive heatmap web#data visualization#maps api#geospatial#javascript#density map

Una heatmap trasforma una nuvola di coordinate in una superficie di densità leggibile a colpo d'occhio. I colori caldi indicano dove gli eventi si concentrano, quelli freddi dove si diradano. Gli utenti colgono lo schema prima ancora di leggere un'etichetta. Per questo le heatmap sono la scelta standard per analizzare il traffico pedonale, prevedere la domanda di consegne, creare strumenti di segnalazione dei reati e studiare i prezzi degli immobili.

In questo tutorial costruisci da zero una heatmap interattiva con l'SDK di MapAtlas. Strutturerai i dati dei punti come GeoJSON pesato, disegnerai un layer di densità con un gradiente di colore personalizzato, collegherai uno slider interattivo e chiuderai con un esempio reale sulla domanda di consegne. Alla fine avrai uno schema riutilizzabile da inserire in qualsiasi progetto JavaScript o React.

Se non conosci ancora l'SDK di MapAtlas, leggi prima Come aggiungere mappe interattive al tuo sito web. L'articolo spiega installazione, inizializzazione della mappa e marker. Questo tutorial riparte da dove si ferma quello.

Quando usare una heatmap

Le heatmap sono lo strumento giusto quando hai un grande insieme di posizioni puntuali e vuoi comunicare la densità, non l'identità dei singoli punti. Oltre qualche centinaio di punti i marker singoli perdono significato; una heatmap rivela una struttura che nessuna quantità di segnaposto potrebbe mostrare.

Casi d'uso comuni:

  • Analisi del traffico pedonale: scelta della location per il retail, pianificazione urbana, modellazione della folla agli eventi. Registra un ping GPS per ogni cliente passato da un ingresso e la heatmap mostra quali zone di una città, di un centro commerciale o di una venue attirano più persone.
  • Domanda di consegne: aggrega le origini degli ordini per codice postale o per coordinate grezze e mostra ai dispatcher dove si concentra la domanda. Il risultato alimenta direttamente la pianificazione delle zone e l'assegnazione dei corrieri.
  • Dati su reati e incidenti: dashboard di analisi per le forze dell'ordine, mappe del rischio assicurativo e strumenti di segnalazione per la sicurezza pubblica usano tutti heatmap di densità per comunicare il rischio spaziale senza sommergere gli utenti di marker.
  • Gradienti dei prezzi immobiliari: combinata con valori pesati, una heatmap può mostrare dove i prezzi sono più alti all'interno di una città. Consulta il tutorial sulla mappa immobiliare per saperne di più sulla creazione di strumenti di mappa per il settore immobiliare.

Se la tua domanda è «dove succedono più cose?», la risposta è una heatmap.

Formato dei dati: punti GeoJSON pesati

Il layer heatmap di MapAtlas legge una FeatureCollection GeoJSON standard composta da feature Point. Ogni feature può avere una proprietà weight che scala il suo contributo alla superficie di densità. Un ordine da 10 articoli genera più calore di un ordine da un solo articolo; un grande nodo di trasporto genera più traffico pedonale di una via secondaria.

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" }
    }
  ]
};

Il valore weight è adimensionale e va normalizzato rispetto all'intervallo del tuo dataset. Se la zona con più domanda genera 500 ordini e quella più tranquilla 10, riportali su una scala da 1 a 10 prima di passarli al layer. Così la heatmap resta leggibile anche con dataset dai conteggi assoluti molto diversi.

Senza la proprietà weight, ogni punto contribuisce in modo uguale e la heatmap riflette la pura densità dei conteggi.

Prerequisiti

Prima di iniziare ti servono:

  • Una chiave API MapAtlas (registrati gratis, senza carta di credito)
  • Node.js 18+ per i progetti basati su npm, oppure una semplice pagina HTML se preferisci la CDN

Passo 1: installa e inizializza la mappa

Installa l'SDK:

npm install @mapmetrics/mapmetrics-gl

Oppure caricalo via CDN in un semplice file HTML:

<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>

Aggiungi un contenitore con un'altezza definita:

<div id="map" style="width: 100%; height: 600px;"></div>

Inizializza la mappa con uno stile scuro del tuo account. Una base scura fa risaltare i gradienti di colore della heatmap sullo sfondo, ed è per questo la base preferita per le visualizzazioni di densità:

import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const map = new mapmetricsgl.Map({
  container: 'map',
  style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=YOUR_API_KEY',
  center: [4.9041, 52.3676], // Amsterdam
  zoom: 12,
});

Passo 2: aggiungi il layer heatmap

Registra il GeoJSON come source, poi aggiungi un layer heatmap che legge da quella source. La callback map.on('load', ...) garantisce che lo stile abbia finito di caricarsi prima che tu lo modifichi.

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,
    },
  });
});

A questo punto hai una heatmap funzionante. Le aree blu-verdi hanno bassa densità; con densità alta il gradiente predefinito vira verso il giallo e il rosso. Il passo successivo sostituisce la palette predefinita con uno schema di colori scelto da te.

Passo 3: personalizza il gradiente di colore

La proprietà heatmap-color associa i valori di densità (da 0 a 1) ai colori con la stessa espressione interpolate usata in tutto il sistema di paint di MapAtlas. La densità 0 è trasparente, così nelle aree vuote resta visibile la mappa di base.

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
    ],
  },
});

Questo gradiente passa dal blu (dati radi) al verde e al giallo, fino al rosso (dati densi), e rispecchia il modello mentale che la maggior parte degli utenti ha già grazie alle mappe radar meteo e alle immagini termiche. Se la tua app ha una palette di colori del brand, sostituisci i valori RGB con i tuoi stop: l'interpolazione gestisce automaticamente le transizioni.

Consiglio di design: nelle dashboard professionali e negli strumenti di analisi, mantieni quasi trasparente l'estremità a bassa densità. Così la mappa di base resta leggibile nelle aree tranquille e lo sguardo va in modo naturale verso le zone calde.

Passo 4: aggiungi uno slider per regolare il raggio

Il raggio della heatmap controlla fin dove ogni punto «irradia» la propria influenza. Un raggio piccolo (da 10 a 15 px) mostra cluster dettagliati; un raggio grande (da 50 a 80 px) produce una superficie più ampia e morbida. Casi d'uso diversi richiedono valori predefiniti diversi: il traffico pedonale in un centro città denso richiede un raggio piccolo, la domanda di consegne su scala nazionale ne richiede uno grande.

Dai agli utenti uno slider per regolarlo al volo:

<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>

Collega lo slider a setPaintProperty, che aggiorna il layer senza doverlo aggiungere di nuovo:

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 aggiorna il layer in tempo reale, senza sfarfallii. La heatmap viene ridisegnata sulla GPU nello stesso frame. Lo stesso schema funziona con qualsiasi proprietà di paint: opacità, intensità, stop di colore.

Passo 5: scala l'intensità in base al livello di zoom

Ai livelli di zoom bassi (vista sull'intera città), i punti vicini si sovrappongono molto e la heatmap può apparire uniformemente satura. Ai livelli alti (vista a livello di strada), gli stessi punti si allontanano e la superficie di densità appare rada. Compensare l'intensità in base allo zoom mantiene la visualizzazione leggibile a ogni livello.

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
  ],
},

Entrambe le proprietà usano la stessa espressione interpolate su zoom. I valori tra uno stop e l'altro vengono interpolati in modo lineare, quindi la transizione resta fluida mentre l'utente cambia zoom.

Esempio reale: heatmap della domanda di consegne

Ecco un'implementazione completa e autonoma per una dashboard della domanda di consegne. Gli ordini arrivano da un'API come FeatureCollection GeoJSON. La heatmap si aggiorna ogni volta che l'utente cambia il filtro orario.

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://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=${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 chiamata setData su una source esistente sostituisce il GeoJSON senza aggiungere di nuovo il layer, e la heatmap viene ridisegnata in automatico. Lo schema si applica a qualsiasi filtro temporale: ora del giorno, giorno della settimana, condizioni meteo.

Per il layer di pianificazione dei percorsi che di solito accompagna una heatmap della domanda di consegne, consulta il tutorial sull'API di ottimizzazione dei percorsi. Sovrapporre un percorso di consegna ottimizzato a una heatmap della domanda dà ai dispatcher un quadro operativo completo in un'unica vista.

Consigli sulle prestazioni con dataset di grandi dimensioni

Il layer heatmap di MapAtlas usa WebGL ed è veloce nel rendering, ma su larga scala la pipeline di dati che lo alimenta può diventare un collo di bottiglia.

Con dataset molto grandi, pre-aggrega i dati sul server. Se hai milioni di ping GPS, non inviarli tutti al browser. Esegui un'aggregazione spaziale lato server (griglia esagonale H3, quadtree o un semplice arrotondamento a griglia) che riduca 1 milione di punti grezzi a 10.000 celle con un campo count e un campo weight. Ai normali livelli di zoom la heatmap avrà un aspetto quasi identico, e si caricherà in una frazione del tempo.

Invia gli aggiornamenti in modo incrementale. Per i dati live (traffico pedonale in tempo reale, ordini che arrivano in diretta), usa setData con una finestra mobile di punti recenti invece di accumulare un oggetto GeoJSON che cresce senza limiti. Mantieni la source entro un numero massimo fisso di punti ed elimina i record più vecchi.

Imposta un maxzoom sulla source. Aggiungere maxzoom: 14 alla chiamata addSource indica all'SDK di smettere di richiedere dati dei tile oltre lo zoom 14. Per le heatmap conta poco, perché i layer heatmap leggono un'unica source GeoJSON piatta invece di dati a tile, ma evita rielaborazioni inutili ai livelli di zoom alti.

Mantieni semplici le espressioni di paint. Il gradiente di heatmap-color viene convertito una sola volta in una texture di rampa di colori quando lo imposti, quindi gli stop di colore aggiuntivi non costano quasi nulla a ogni frame. Su mobile i costi maggiori sono un heatmap-radius ampio e un numero elevato di punti, quindi ottimizza prima questi.

Inizializza la mappa in modo lazy. Racchiudi l'intera inizializzazione della mappa in una callback IntersectionObserver, così viene eseguita solo quando il contenitore della mappa entra nella viewport. In questo modo il bundle dell'SDK non pesa sul caricamento iniziale della pagina, un vantaggio concreto nelle pagine di marketing in cui la mappa si trova sotto la piega.

Per approfondire i pattern di performance delle mappe, tra cui lazy loading e clustering, la checklist per la produzione in Come aggiungere mappe interattive al tuo sito web riporta l'elenco completo.

Integrazione con React

Racchiudere la heatmap in un componente React segue lo stesso schema di qualsiasi mappa MapAtlas: inizializza la mappa in useEffect, gestisci lo stato dello slider e del filtro con useState e fai pulizia quando il componente viene smontato.

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://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=${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>
  );
}

In Next.js, importa questo componente con dynamic(() => import('./DeliveryHeatmap'), { ssr: false }) per evitare errori di server-side rendering dovuti a un SDK che funziona solo nel browser.

Cosa costruire dopo

Ora hai una heatmap interattiva funzionante con dati pesati, un gradiente di colore personalizzato, uno slider per il raggio e un'intensità legata allo zoom. Ecco come puoi andare avanti:

  • Sovrapponi un layer di routing alla heatmap per mostrare i percorsi di consegna pianificati sopra la superficie della domanda. Il tutorial sull'API di ottimizzazione dei percorsi spiega l'implementazione completa.
  • Aggiungi un'animazione temporale: scorri le istantanee orarie con un ciclo setInterval che chiama setData sulla source. Così una mappa di densità statica diventa un time-lapse di come si sposta la domanda nel corso della giornata.
  • Combina la heatmap con i dati sui prezzi degli immobili per mostrare i gradienti di prezzo per quartiere. Il tutorial sulla mappa immobiliare illustra gli schemi di dati pesati per i portali immobiliari.
  • Consulta la pagina dei prezzi di MapAtlas per trovare il piano adatto al tuo traffico in produzione.

Il riferimento completo dell'SDK e altri esempi sono disponibili su docs.mapatlas.xyz.

Domande frequenti

Qual è la differenza tra una heatmap e una mappa coropleta?

Una heatmap visualizza la densità dei punti fondendo i punti vicini in un gradiente di colore continuo, ideale per dati di coordinate grezzi come i ping GPS o le posizioni degli eventi. Una mappa coropleta colora aree geografiche predefinite (paesi, codici postali, sezioni di censimento) in base a un valore statistico. Usa una heatmap quando hai molti punti individuali; usa una coropleta quando i tuoi dati sono già aggregati per regione.

Quanti punti dati può gestire una heatmap JavaScript?

Il layer heatmap di MapAtlas esegue il rendering sulla GPU tramite WebGL, quindi gestisce in modo fluido decine di migliaia di punti su hardware tipico. Con dataset molto più grandi, dalle centinaia di migliaia di punti in su, conviene pre-aggregare i dati lato server in una griglia a risoluzione inferiore e passare a un layer GeoJSON fill-extrusion o circle, che offre prestazioni migliori sui dispositivi di fascia bassa.

Posso usare le heatmap di MapAtlas gratuitamente?

Sì. MapAtlas ha un piano gratuito che include il rendering dei tile della mappa, il supporto dei layer GeoJSON e i layer heatmap. Il piano gratuito copre lo sviluppo e l'uso in produzione a basso volume. Vedi mapatlas.eu/pricing per tutti i dettagli dei piani.

Le heatmap funzionano sui browser mobili?

Sì. L'SDK di MapAtlas usa WebGL per il rendering, supportato da tutti i browser mobili moderni, incluso Safari su iOS e Chrome su Android. Con dataset molto grandi su hardware mobile di fascia bassa, ridurre il numero di punti o aumentare il raggio della heatmap mantiene fluido il frame rate.


Letture correlate:

Domande frequenti

Qual è la differenza tra una heatmap e una mappa coropleta?

Una heatmap visualizza la densità dei punti fondendo i punti vicini in un gradiente di colore continuo, ideale per dati di coordinate grezzi come i ping GPS o le posizioni degli eventi. Una mappa coropleta colora aree geografiche predefinite (paesi, codici postali, sezioni di censimento) in base a un valore statistico. Usa una heatmap quando hai molti punti individuali; usa una coropleta quando i tuoi dati sono già aggregati per regione.

Quanti punti dati può gestire una heatmap JavaScript?

Il layer heatmap di MapAtlas esegue il rendering sulla GPU tramite WebGL, quindi gestisce in modo fluido decine di migliaia di punti su hardware tipico. Con dataset molto più grandi, dalle centinaia di migliaia di punti in su, conviene pre-aggregare i dati lato server in una griglia a risoluzione inferiore e passare a un layer GeoJSON fill-extrusion o circle, che offre prestazioni migliori sui dispositivi di fascia bassa.

Posso usare le heatmap di MapAtlas gratuitamente?

Sì. MapAtlas ha un piano gratuito che include il rendering dei tile della mappa, il supporto dei layer GeoJSON e i layer heatmap. Il piano gratuito copre lo sviluppo e l'uso in produzione a basso volume. Vedi mapatlas.eu/pricing per tutti i dettagli dei piani.

Le heatmap funzionano sui browser mobili?

Sì. L'SDK di MapAtlas usa WebGL per il rendering, supportato da tutti i browser mobili moderni, incluso Safari su iOS e Chrome su Android. Con dataset molto grandi su hardware mobile di fascia bassa, ridurre il numero di punti o aumentare il raggio della heatmap (che fonde visivamente più punti) mantiene fluido il frame rate.

L'hai trovato utile? Condividilo.

Sull'autore

Brent van der Heiden

Scritto da

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. Before that he co-founded MapMetrics, a community mapping and navigation app. He writes about geospatial infrastructure, map data, AI search visibility, and how location data powers the products people rely on every day.

Vedi tutti gli articoli →
Torna al blog