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 * as reactiveUtils from "https://js.arcgis.com/4.33/@arcgis/core/core/reactiveUtils.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 Home from "https://js.arcgis.com/4.33/@arcgis/core/widgets/Home.js"; import Expand from "https://js.arcgis.com/4.33/@arcgis/core/widgets/Expand.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"); const temp_change = params.get("slider_value"); const sim_type = params.get("mode"); console.log("mode", sim_type) console.log("temp_change", temp_change) console.log("before_url", before_url) const STORAGE_KEY = "linked_map_selected_zona"; function getStoredZonaId() { const raw = window.localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed = Number(raw); return Number.isFinite(parsed) ? parsed : null; } function setStoredZonaId(zonaId) { console.log("setStoredZonaId", zonaId) if (zonaId == null) return; window.localStorage.setItem(STORAGE_KEY, String(zonaId)); } // ??? Capas base const zonasLayer = new GeoJSONLayer({ id: "zonasLayer", url: "layers/Zonas.geojson", outFields: ["*"], opacity: 0.6, renderer: { type: "simple", symbol: { type: "simple-fill", color: [127, 154, 99, 0.6], outline: { color: [155, 255, 0, 1], width: 2 } } } }); 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 }); let splitGeoLayer = null; if (split_url) splitGeoLayer = new GeoJSONLayer({ url: split_url }); // ?? Layers gr ficos const beforeBufferLayer = new GraphicsLayer({ title: "Antes (cuadrados)" }); const beforeBufferLayer2 = new GraphicsLayer({ title: "Antes (cuadrados)" }); const afterBufferLayer = new GraphicsLayer({ title: "Después (cuadrados)" }); const changeBufferLayer = new GraphicsLayer({ title: "Cambio (cuadrados)" }); await zonasLayer.load(); await beforeLayer.load(); // Obtén su extent const extent = zonasLayer.fullExtent; // Calcula el punto central del extent const center = extent.center; // ?? Mapa compartido (solo before/after) const map = new Map({ basemap: "satellite", layers: [ zonasLayer,beforeBufferLayer2,] }); const map2 = new Map({ basemap: "satellite", layers: [beforeBufferLayer, afterBufferLayer] }); const homeViewpoint = { targetGeometry: center, scale: 50000 }; // ?? View 1 (izquierda) const view1 = new MapView({ container: "view1Div", map, center: [-90.493862, 14.61925], zoom: 11, constraints: { snapToZoom: false }, viewpoint: homeViewpoint }); // ?? View 2 (derecha) const view2 = new MapView({ container: "view2Div", map: map2, center: [-90.493862, 14.61925], zoom: 11, constraints: { snapToZoom: false }, viewpoint: homeViewpoint }); const modeControls = document.getElementById("modeControls"); // Crear widget Expand con ese contenido const expandWidget = new Expand({ view: view2, content: modeControls, collapseIcon: "sliders", expandIcon: "sliders", expanded: true, expandTooltip: "Modo de visualizaci¢n", mode: "floating" }); const temperature = document.getElementById("temperature"); const tempWidget = new Expand({ view: view2, content: temperature, collapseIcon: "sliders", expandIcon: "spike-data-visualization-outlined-gradient", expanded: false, expandTooltip: "Modo de visualizaci¢n", mode: "floating" }); view2.ui.add(expandWidget, "top-right"); view2.ui.add(tempWidget, "bottom-right"); const labelEstimated = document.getElementById("labelEstimated"); const labelChange = document.getElementById("labelChange"); const card2 = document.createElement("calcite-card"); const title2 = document.createElement("span"); title2.slot = "title"; const card = document.createElement("calcite-card"); const title = document.createElement("span"); title.slot = "title"; title.textContent = `${sim_type} actual`; function updateLabels() { console.log("updateLabels", sim_type); if (!labelEstimated || !labelChange) return; console.log("entro?"); title.textContent = `${sim_type} actual`; if (sim_type === "Cobertura") { labelEstimated.textContent = "Cobertura estimada"; labelChange.textContent = "Cambio de cobertura"; title2.textContent = `Cobertura estimada después de la disminución\nde la temperatura ${temp_change}`; } else { labelEstimated.textContent = "Temperatura estimada"; labelChange.textContent = "Disminución de temperatura"; title2.textContent = `Temperatura estimada después del aumento\nde cobertura arbórea ${temp_change}`; } } updateLabels(); card2.appendChild(title2); card.appendChild(title); view1.ui.add(card, "bottom-left"); view2.ui.add(card2, "bottom-left"); view2.ui.move("zoom", "top-left"); view1.ui.move("zoom", "top-left"); // ? Capa "Cambio" solo en view2 view2.map.add(changeBufferLayer); changeBufferLayer.visible = false; // ? Capa "Split" solo en view2 const splitGraphicsLayer = new GraphicsLayer({ title: "Split Graphics" }); view2.map.add(splitGraphicsLayer); splitGraphicsLayer.visible = false; // ?? Sincronizaci¢n entre vistas const views = [view1, view2]; let active; function sync(source) { if (!active || !active.viewpoint || active !== source) return; for (const view of views) { if (view !== active) view.viewpoint = active.viewpoint.clone(); } } for (const view of views) { reactiveUtils.watch(() => [view.interacting, view.viewpoint], ([interacting, viewpoint]) => { if (interacting) { active = view; sync(active); } if (viewpoint) { sync(view); } }); } async function applyZonaSelection(zonaFeature, { persist = true } = {}) { const zonaGeom = zonaFeature?.geometry; console.log("zonaGeom", zonaGeom); if (!zonaGeom) return; if (persist) { const zonaId = zonaFeature?.attributes?.properties?.zona ?? zonaFeature?.attributes?.zona; setStoredZonaId(zonaId); } const q = beforeLayer.createQuery(); q.geometry = zonaGeom; q.spatialRelationship = "intersects"; // o "contains" q.outFields = ["*"]; q.returnGeometry = true; console.log("q", q); const result = await beforeLayer.queryFeatures(q); const resultAfter = await afterLayer.queryFeatures(q); const resultChange = await changeLayer.queryFeatures(q); console.log("result", result.features.length); let targetExtent = result?.extent || zonaGeom?.extent; if (targetExtent) { const expanded = targetExtent.clone().expand(1.3); // Mueve ambos mapas al mismo tiempo await Promise.allSettled([ view1.goTo(expanded, { animate: true, duration: 700 }), view2.goTo(expanded, { animate: true, duration: 700 }) ]); } beforeBufferLayer.removeAll(); beforeBufferLayer2.removeAll(); afterBufferLayer.removeAll(); changeBufferLayer.removeAll(); await createSquaresFromFeatures(result.features, beforeBufferLayer2); await createSquaresFromFeatures(result.features, beforeBufferLayer); await createSquaresFromFeatures(resultAfter.features, afterBufferLayer); await createSquaresFromFeatures(resultChange.features, changeBufferLayer); if (splitGeoLayer) { const resultSplit = await splitGeoLayer.queryFeatures(q); splitGraphicsLayer.removeAll(); await createSquaresFromFeatures(resultSplit.features, splitGraphicsLayer, false); } } async function restoreZonaSelection() { const zonaId = getStoredZonaId(); if (zonaId == null) return; const q = zonasLayer.createQuery(); q.where = `zona = ${zonaId}`; q.outFields = ["*"]; q.returnGeometry = true; const result = await zonasLayer.queryFeatures(q); const zonaFeature = result.features[0]; if (!zonaFeature) return; await applyZonaSelection(zonaFeature, { persist: false }); } view1.on("click", async (event) => { const hit = await view1.hitTest(event); console.log("click", hit.results) const zonaHit = hit.results.find(r => r.graphic?.layer?.id === zonasLayer.id); if (!zonaHit) return; console.log("zona hit ", zonaHit) console.log("attributes", zonaHit.graphic.attributes) const zonaGraphic = zonaHit.graphic; await applyZonaSelection(zonaGraphic); }) let hoverGraphic = null; view1.on("pointer-move", async (event) => { const hit = await view1.hitTest(event); const zonaHit = hit.results.find( r => r.graphic?.layer?.id === zonasLayer.id ); // quitar hover anterior if (hoverGraphic) { view1.graphics.remove(hoverGraphic); hoverGraphic = null; } if (!zonaHit) return; hoverGraphic = zonaHit.graphic.clone(); hoverGraphic.symbol = { type: "simple-fill", color: [127, 154, 99, 0.6], outline: { color: [155, 255, 0, 1], width: 2.5 } }; view1.graphics.add(hoverGraphic); }); async function createSquaresFromFeatures(features, targetGraphicsLayer, useBuffer = true) { await projection.load(); const half = 50; // metros en UTM const graphics = []; for (const f of features) { const attrs = f.attributes; const geom = f.geometry; if (!geom) continue; const colorHex = attrs.color ?? "#999"; const colorRGB = hexToRGB(colorHex, 0.9); if (useBuffer){ 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, symbol: { type: "simple-fill", color: colorRGB, outline: { color: [40, 40, 40, 0.2], width: 0 }, }, })); } 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, outline: { color: colorRGB, width: 6, }, }, }) ); } } targetGraphicsLayer.addMany(graphics); } // ?? Crear buffers async function createSquaresFromLayer(geoLayer, targetGraphicsLayer, valueField = "value") { await projection.load(); await geoLayer.load(); const features = await geoLayer.queryFeatures(); const half = 50; const graphics = []; for (const f of features.features) { const attrs = f.attributes; const geom = f.geometry; const colorHex = attrs.color ?? "#999"; 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, symbol: { type: "simple-fill", color: colorRGB, outline: { color: [40, 40, 40, 0.2], width: 0 }, }, })); } targetGraphicsLayer.addMany(graphics); } function hexToRGB(hex, alpha = 1) { const bigint = parseInt(hex.replace("#", ""), 16); return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255, alpha]; } // ?? Render buffers // await createSquaresFromLayer(beforeLayer, beforeBufferLayer); // await createSquaresFromLayer(beforeLayer, beforeBufferLayer2); // await createSquaresFromLayer(afterLayer, afterBufferLayer); // await createSquaresFromLayer(changeLayer, changeBufferLayer); // ?? Swipe en view2 const swipe = new Swipe({ view: view2, trailingLayers: [beforeBufferLayer], leadingLayers: [afterBufferLayer, changeBufferLayer], position: 50, direction: "vertical", }); view2.ui.add(swipe); // ?? Leyenda en view2 const legend = new Legend({ view: view2, layerInfos: [ { layer: beforeBufferLayer, title: "Antes" }, { layer: afterBufferLayer, title: "Despu‚s" }, { layer: changeBufferLayer, title: "Cambio" }, { layer: splitGraphicsLayer, title: "Split" }, ], }); //view2.ui.add(legend, "bottom-left"); // ??? Radio buttons - controlan solo view2 const home = new Home({ view: view1 }); const home2 = new Home({ view: view2 }); view1.ui.add(home, "top-left"); view2.ui.add(home2, "top-left"); // ?? Interceptar cuando el usuario presiona Home home.go = async function () { console.log("entro") // Llama a la animaci¢n normal de la vista principal //await view1.goTo(homeViewpoint, { animate: true }); await view1.goTo(zonasLayer.fullExtent, { animate: true }); console.log("zonaslayer2", zonasLayer) // Sincroniza la otra vista tambi‚n //await view2.goTo(homeViewpoint, { animate: true }); }; await restoreZonaSelection(); const vm = document.getElementById("viewModeGroup"); vm.selectedItem = "swipe" const img = document.querySelector("#modeImage"); vm.addEventListener("calciteRadioButtonGroupChange", (event) => { const selected = event.target.selectedItem; const mode = selected?.value; console.log("?? Cambio detectado:", mode); if (mode === "swipe") { //swipe.visible = true; beforeBufferLayer.visible = true; afterBufferLayer.visible = true; changeBufferLayer.visible = false; img.src = "data/before_after_colorbar_horizontal.png"; img.alt = "Vista Swipe"; title2.textContent = `Temperatura estimada después del aumento\nde cobertura arbórea ${temp_change}` } else if (mode === "change") { //swipe.visible = false; //beforeBufferLayer.visible = false; afterBufferLayer.visible = false; changeBufferLayer.visible = true; img.src = "data/change_colorbar_horizontal.png"; img.alt = "Vista de cambio"; title2.textContent = `Disminución de la temperatura después del aumento\nde cobertura arbórea ${temp_change}` } }); const sw = document.getElementById("filtroSwitch"); sw.addEventListener("calciteSwitchChange", (event) => { const isOn = event.target.checked; console.log("Switch cambi¢:", isOn); if (isOn) { console.log("?? Activar capa de  rboles"); splitGraphicsLayer.visible = true; } else { console.log("?? Desactivar capa de  rboles"); splitGraphicsLayer.visible = false; } }); window.addEventListener("message", async (event) => { const msg = event.data; //console.log("Mensaje recibido en linked_map.js:", msg); if (msg?.type === "setMode") { sim_type = msg.mode; temp_change = msg.value; updateLabels(); } });