Librería: Leaflet

Descripción: El usuario agrega manualmente el archivo geojson que contenga campos numericos por los cuales generar el mapa de calor y el sistema lo muestra en el mapa con los datos de calor.

image.png

Archivo CSS

#map {
    position: absolute;
    width: 100%;
    height: 100%;
}

.control-panel {
  position: absolute;
  top: 10px;
  right: 10px;
  background: white;
  padding: 10px;
  z-index: 1000;
  border-radius: 5px;
  box-shadow: 0 0 8px rgba(0,0,0,0.3);
  font-family: Arial, sans-serif;
}
.control-panel label,
.control-panel select,
.control-panel input {
  display: block;
  margin-bottom: 8px;
}

Archivo HMTL:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Heatmap Interactivo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link 
    rel="stylesheet" 
    href="<https://unpkg.com/leaflet@1.9.4/dist/leaflet.css>"
  />
  <link rel="stylesheet" href="mapa.css" />
</head>
<body>

<div id="map"></div>

<div class="control-panel">
  <label for="geojson-upload">📂 Cargar GeoJSON:</label>
  <input type="file" id="geojson-upload" accept=".geojson,.json">

  <label for="campo-intensidad">📊 Campo de Intensidad:</label>
  <select id="campo-intensidad" disabled></select>
</div>

<script src="<https://unpkg.com/leaflet@1.9.4/dist/leaflet.js>"></script>
<script src="<https://unpkg.com/leaflet.heat/dist/leaflet-heat.js>"></script>
<script src="mapa.js"></script>

</body>
</html>

Archivo JavaScript:

// Inicializar el mapa en las coordenadas y zoom requeridos
const map = L.map('map').setView([6.245760960399575, -75.59087996503669], 13);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap'
}).addTo(map);

let geojsonDataGlobal = null;
let heatLayer = null;
const campoSelect = document.getElementById('campo-intensidad');

function getPolygonCentroid(coords) {
  try {
    let area = 0.0, x = 0.0, y = 0.0;
    const points = coords[0];
    for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
      const [x0, y0] = points[j];
      const [x1, y1] = points[i];
      const a = x0 * y1 - x1 * y0;
      area += a;
      x += (x0 + x1) * a;
      y += (y0 + y1) * a;
    }
    area *= 0.5;
    const factor = 1 / (6 * area);
    return [y * factor, x * factor]; // [lat, lon]
  } catch (e) {
    console.error("Error calculando centroide de polígono:", e);
    return null;
  }
}

function getLineCentroid(coords) {
  let lat = 0, lng = 0;
  coords.forEach(([lon, latVal]) => {
    lng += lon;
    lat += latVal;
  });
  return [lat / coords.length, lng / coords.length];
}

function obtenerCamposNumericos(properties) {
  const campos = [];
  for (const key in properties) {
    if (typeof properties[key] === 'number') {
      campos.push(key);
    }
  }
  return campos;
}

function construirHeatmap(geojson, campo) {
  const heatData = [];

  geojson.features.forEach(feature => {
    let coords;
    const geom = feature.geometry;
    if (!geom || !feature.properties) return;

    if (geom.type === 'Point') {
      coords = [geom.coordinates[1], geom.coordinates[0]];
    } else if (geom.type === 'Polygon') {
      coords = getPolygonCentroid(geom.coordinates);
    } else if (geom.type === 'LineString') {
      coords = getLineCentroid(geom.coordinates);
    } else if (geom.type === 'MultiPolygon') {
      // Usamos el primer polígono del MultiPolygon
      coords = getPolygonCentroid(geom.coordinates[0]);
    } else {
      console.warn("Tipo de geometría no soportado:", geom.type);
      return;
    }

    const valor = parseFloat(feature.properties[campo]);
    if (!isNaN(valor) && coords) {
      heatData.push([coords[0], coords[1], valor]);
    }
  });

  console.log("Datos heatmap generados:", heatData);

  if (heatData.length === 0) {
    alert("No se generó ningún punto válido para el mapa de calor.");
    return;
  }

  if (heatLayer) {
    map.removeLayer(heatLayer);
  }

  heatLayer = L.heatLayer(heatData, {
    radius: 30,
    blur: 20,
    maxZoom: 17
  }).addTo(map);
}

document.getElementById('geojson-upload').addEventListener('change', function (e) {
  const file = e.target.files[0];
  if (!file) return;

  const reader = new FileReader();
  reader.onload = function (event) {
    const geojson = JSON.parse(event.target.result);
    geojsonDataGlobal = geojson;

    console.log("GeoJSON cargado:", geojson);

    let camposNumericos = [];
    for (let f of geojson.features) {
      if (f.properties) {
        camposNumericos = obtenerCamposNumericos(f.properties);
        if (camposNumericos.length > 0) break;
      }
    }

    if (camposNumericos.length === 0) {
      alert("No se encontraron campos numéricos.");
      return;
    }

    campoSelect.innerHTML = '';
    camposNumericos.forEach(campo => {
      const opt = document.createElement('option');
      opt.value = campo;
      opt.textContent = campo;
      campoSelect.appendChild(opt);
    });
    campoSelect.disabled = false;

    construirHeatmap(geojsonDataGlobal, camposNumericos[0]);

    const layer = L.geoJSON(geojson);
    /*try {
      map.fitBounds(layer.getBounds());
    } catch (error) {
      console.warn("No se pudo ajustar el zoom al archivo:", error);
    }*/
  };
  reader.readAsText(file);
});

campoSelect.addEventListener('change', function () {
  const campo = campoSelect.value;
  if (geojsonDataGlobal && campo) {
    construirHeatmap(geojsonDataGlobal, campo);
  }
});

Ver el paso a paso:

https://youtu.be/bMPmZYwN_xI