protoai-dashboard / static /swipe_map.html
dcarreradigm's picture
Upload folder using huggingface_hub
93e5f2d verified
Raw
History Blame Contribute Delete
11.5 kB
<!-- static/swipe_map.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<title>Swipe Map</title>
<link href="https://js.arcgis.com/4.33/@arcgis/core/assets/esri/themes/light/main.css" rel="stylesheet" />
<!-- Load Calcite Design System -->
<script type="module" src="https://js.arcgis.com/calcite-components/3.3.3/calcite.esm.js"></script>
<!-- Load the JavaScript Maps SDK core API -->
<script src="https://js.arcgis.com/4.34/"></script>
<!-- Load the JavaScript Maps SDK Map components or other component packages -->
<script type="module" src="https://js.arcgis.com/4.34/map-components/"></script>
<style>
html,
body,
#viewDiv {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<style>
/* 🔘 Estilos del control flotante */
.layer-toggle {
position: absolute;
top: 15px;
left: 15px;
z-index: 99;
background: rgba(255, 255, 255, 0.9);
border-radius: 8px;
padding: 10px 14px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
font-family: sans-serif;
}
.layer-toggle label {
display: block;
margin-bottom: 5px;
font-size: 14px;
cursor: pointer;
}
.map-titles {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
pointer-events: none;
/* permite arrastrar el swipe */
font-family: "Segoe UI", sans-serif;
}
.map-titles .title-after {
position: absolute;
top: 0px;
right: 0px;
background: rgba(255, 255, 255, 0.85);
padding: 8px 12px;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
white-space: pre-line;
}
.map-titles .title-before {
position: absolute;
bottom: 6px;
right: 0px;
background: rgba(255, 255, 255, 0.85);
padding: 8px 12px;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}
</style>
<div class="layer-toggle">
<label><input type="radio" name="mode" value="swipe" checked> Comparar antes/después</label>
<label><input type="radio" name="mode" value="change"> Mostrar cambio de temperatura</label>
</div>
<div id="viewDiv" style="width:100%;height:100vh; position: relative;">
</div>
<script type="module">
import Map from "https://js.arcgis.com/4.33/@arcgis/core/Map.js";
import MapView from "https://js.arcgis.com/4.33/@arcgis/core/views/MapView.js";
import WebMap from "https://js.arcgis.com/4.33/@arcgis/core/WebMap.js";
import GeoJSONLayer from "https://js.arcgis.com/4.33/@arcgis/core/layers/GeoJSONLayer.js";
import GraphicsLayer from "https://js.arcgis.com/4.33/@arcgis/core/layers/GraphicsLayer.js";
import Graphic from "https://js.arcgis.com/4.33/@arcgis/core/Graphic.js";
import Polygon from "https://js.arcgis.com/4.33/@arcgis/core/geometry/Polygon.js";
import Swipe from "https://js.arcgis.com/4.33/@arcgis/core/widgets/Swipe.js";
import Legend from "https://js.arcgis.com/4.33/@arcgis/core/widgets/Legend.js";
import * as projection from "https://js.arcgis.com/4.33/@arcgis/core/geometry/projection.js";
// 🧩 Leer parámetros
const params = new URLSearchParams(window.location.search);
const before_url = params.get("before");
const after_url = params.get("after");
const change_url = params.get("change");
const split_url = params.get("split");
// 🎨 Gradientes de color
const tempStops = [
{ value: 15, color: [44, 123, 182] },
{ value: 25, color: [255, 255, 191] },
{ value: 35, color: [215, 25, 28] }
];
const changeStops = [
{ value: -2.0, color: [8, 48, 107] },
{ value: -1.0, color: [107, 174, 214] },
{ value: 0.0, color: [247, 251, 255] }
];
// 🗺️ Capas GeoJSON base
const beforeLayer = new GeoJSONLayer({ url: before_url, opacity: 0 });
const afterLayer = new GeoJSONLayer({ url: after_url, opacity: 0 });
const changeLayer = new GeoJSONLayer({ url: change_url, opacity: 0 });
// 🧱 Layers de buffers
const beforeBufferLayer = new GraphicsLayer({ title: "Antes (cuadrados)" });
const afterBufferLayer = new GraphicsLayer({ title: "Después (cuadrados)" });
const changeBufferLayer = new GraphicsLayer({ title: "Cambio (cuadrados)" });
const webmap = new WebMap({
portalItem: {
id: "9f45f081b3e5416790b5652b5b47e90e"
},
layers: [afterBufferLayer, beforeBufferLayer, changeBufferLayer]
});
const view = new MapView({
container: "viewDiv",
map: webmap,
center: [-90.493862, 14.61925],
zoom: 11
});
if (split_url) {
// Renderizar como X personalizadas
renderSplitGraphics(view, split_url, false);
}
// 🔀 Swipe
const swipe = new Swipe({
view,
leadingLayers: [beforeBufferLayer],
trailingLayers: [afterBufferLayer],
position: 50,
direction: "vertical",
});
view.ui.add(swipe);
// 🧭 Leyenda
const legend = new Legend({
view,
layerInfos: [
{ layer: beforeBufferLayer, title: "Antes" },
{ layer: afterBufferLayer, title: "Después" },
{ layer: changeBufferLayer, title: "Cambio" }
]
});
view.ui.add(legend, "bottom-left");
// 🧮 Función genérica para crear buffers cuadrados (usando color precalculado)
async function createSquaresFromLayer(geoLayer, targetGraphicsLayer, valueField = "value") {
await projection.load();
await geoLayer.load();
const features = await geoLayer.queryFeatures();
const half = 50;
const graphics = [];
if (features.features.length === 0) {
console.warn(`⚠️ ${geoLayer.title} no devolvió features.`);
return;
}
console.log(`🔍 Revisando campos de ${geoLayer.title}`);
console.log(features.features[0].attributes);
for (const f of features.features) {
const attrs = f.attributes;
const geom = f.geometry;
const value =
attrs[valueField] ??
attrs[valueField.toLowerCase()] ??
attrs[valueField.toUpperCase()] ??
attrs["value"];
// 🟩 ahora usamos directamente el color precalculado
const colorHex =
attrs["color"] ??
attrs["Color"] ??
attrs["COLOR"];
if (value === undefined || colorHex === undefined) continue;
// Convierte el color HEX a [r, g, b] array
const colorRGB = hexToRGB(colorHex, 0.9);
const utm = projection.project(geom, { wkid: 32615 });
if (!utm) continue;
const { x, y } = utm;
const squareUTM = new Polygon({
spatialReference: { wkid: 32615 },
rings: [[
[x - half, y - half],
[x + half, y - half],
[x + half, y + half],
[x - half, y + half],
[x - half, y - half]
]]
});
const squareWGS = projection.project(squareUTM, { wkid: 4326 });
graphics.push(new Graphic({
geometry: squareWGS,
attributes: { value, color: colorHex },
symbol: {
type: "simple-fill",
color: colorRGB,
outline: { color: [40, 40, 40, 0], width: 0.5 }
}
}));
}
targetGraphicsLayer.addMany(graphics);
console.log(`✅ ${graphics.length} cuadrados renderizados en ${geoLayer.title}`);
}
// 🔧 Conversión HEX → [r,g,b,a]
function hexToRGB(hex, alpha = 1) {
const bigint = parseInt(hex.replace("#", ""), 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return [r, g, b, alpha];
}
// 🚀 Generar buffers
await createSquaresFromLayer(beforeLayer, beforeBufferLayer, "value", tempStops);
await createSquaresFromLayer(afterLayer, afterBufferLayer, "value", tempStops);
await createSquaresFromLayer(changeLayer, changeBufferLayer, "temp_change", changeStops);
// 🔘 Configurar visibilidad inicial
changeBufferLayer.visible = false;
afterLayer.when(() => view.goTo(afterLayer.fullExtent));
// 🎚️ Toggle de modo
const radios = document.querySelectorAll('input[name="mode"]');
radios.forEach(r => {
r.addEventListener("change", (e) => {
const mode = e.target.value;
if (mode === "swipe") {
swipe.visible = true;
beforeBufferLayer.visible = true;
afterBufferLayer.visible = true;
changeBufferLayer.visible = false;
} else if (mode === "change") {
swipe.visible = false;
beforeBufferLayer.visible = false;
afterBufferLayer.visible = false;
changeBufferLayer.visible = true;
}
});
});
async function renderSplitGraphics(view, split_url, useBuffer = false) {
if (!split_url) {
console.warn("⚠️ split_url no especificado");
return;
}
console.log("🗂️ Cargando capa split:", split_url);
const splitGraphicsLayer = new GraphicsLayer({ title: "Split Graphics" });
view.map.add(splitGraphicsLayer);
const geoLayer = new GeoJSONLayer({ url: split_url });
await geoLayer.load();
const features = await geoLayer.queryFeatures();
console.log(`📦 ${features.features.length} features cargadas desde split_url`);
await projection.load();
const graphics = [];
for (const f of features.features) {
const geom = f.geometry;
const attrs = f.attributes;
const colorHex = attrs.color ?? "#d2691e";
const colorRGB = hexToRGB(colorHex, 0.90);
if (useBuffer) {
const utm = projection.project(geom, { wkid: 32615 });
if (!utm) continue;
const { x, y } = utm;
const half = 50;
const squareUTM = new Polygon({
spatialReference: { wkid: 32615 },
rings: [[
[x - half, y - half],
[x + half, y - half],
[x + half, y + half],
[x - half, y + half],
[x - half, y - half]
]]
});
const squareWGS = projection.project(squareUTM, { wkid: 4326 });
graphics.push(new Graphic({
geometry: squareWGS,
attributes: attrs,
symbol: {
type: "simple-fill",
color: colorRGB,
outline: { color: [40, 40, 40, 0.2], width: 0.5 }
}
}));
} else {
graphics.push(new Graphic({
geometry: geom,
attributes: attrs,
symbol: {
type: "simple-marker",
path: "M -1 -1 L 1 1 M -1 1 L 1 -1",
color: colorRGB,
size: 9, // más grande
outline: {
color: colorRGB,
width: 6 // más gruesa
}
}
}));
}
}
splitGraphicsLayer.addMany(graphics);
console.log(`✅ Renderizadas ${graphics.length} features en ${useBuffer ? "modo buffer" : "modo X"}`);
// 🔹 Ajuste dinámico inverso de tamaño (zoom menor → ícono más grande)
return splitGraphicsLayer;
}
</script>
</body>
</html>