protoai-dashboard / static /linked_map.js
dcarreradigm's picture
Upload folder using huggingface_hub
acd2185 verified
Raw
History Blame Contribute Delete
15.7 kB
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();
}
});