Spaces:
Sleeping
Sleeping
| <!-- static/swipe_map.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> |