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