From acaa876b677c75d5b6f0b23bf6559cfd61c99796 Mon Sep 17 00:00:00 2001 From: amit-spatial Date: Tue, 21 Jul 2026 23:30:40 +0000 Subject: [PATCH 1/9] added GeoLibre v2.2 integration --- src/components/geolibre/GeoLibreFrame.jsx | 214 +++++ .../geolibre/GeoLibreFrame.test.jsx | 73 ++ src/components/geolibre/README.md | 143 ++++ src/components/geolibre/geolibreProject.js | 746 ++++++++++++++++++ .../geolibre/geolibreProject.test.js | 200 +++++ src/config/geolibre.config.js | 88 +++ src/config/geolibre.config.test.js | 48 ++ src/config/geolibreLayers.js | 330 ++++++++ src/pages/LE_homepage.jsx | 21 +- src/pages/LandscapeExplorer.jsx | 545 +++---------- 10 files changed, 1967 insertions(+), 441 deletions(-) create mode 100644 src/components/geolibre/GeoLibreFrame.jsx create mode 100644 src/components/geolibre/GeoLibreFrame.test.jsx create mode 100644 src/components/geolibre/README.md create mode 100644 src/components/geolibre/geolibreProject.js create mode 100644 src/components/geolibre/geolibreProject.test.js create mode 100644 src/config/geolibre.config.js create mode 100644 src/config/geolibre.config.test.js create mode 100644 src/config/geolibreLayers.js diff --git a/src/components/geolibre/GeoLibreFrame.jsx b/src/components/geolibre/GeoLibreFrame.jsx new file mode 100644 index 00000000..91447862 --- /dev/null +++ b/src/components/geolibre/GeoLibreFrame.jsx @@ -0,0 +1,214 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { + GEOLIBRE_CONFIG, + geoLibreVersionStatus, + resolveGeoLibreViewer, +} from "../../config/geolibre.config"; + +const GeoLibreFrame = ({ + project, + preparationMessage, + preparationError, + warning, + onRetry, +}) => { + const frameRef = useRef(null); + const fitTimerRef = useRef(null); + const sentProjectRef = useRef(null); + const sequenceRef = useRef(0); + const [viewerState, setViewerState] = useState("loading"); + const [viewerError, setViewerError] = useState(""); + const [readyGeneration, setReadyGeneration] = useState(0); + + const viewer = useMemo(() => { + try { + return { ...resolveGeoLibreViewer(), error: "" }; + } catch (error) { + return { + url: "", + origin: "", + error: + error instanceof Error + ? error.message + : "The configured GeoLibre URL is invalid.", + }; + } + }, []); + + useEffect(() => { + if (!viewer.url) return undefined; + const frame = frameRef.current; + if (!frame) return undefined; + + let handshakeTimer = window.setTimeout(() => { + setViewerError( + `GeoLibre ${GEOLIBRE_CONFIG.version} did not complete its iframe handshake. Check the configured viewer URL and browser console.` + ); + setViewerState("error"); + }, 90000); + + const handleMessage = (event) => { + if ( + event.origin !== viewer.origin || + event.source !== frame.contentWindow || + !event.data || + typeof event.data !== "object" + ) { + return; + } + + if (event.data.type === "geolibre:ready") { + window.clearTimeout(handshakeTimer); + handshakeTimer = null; + const status = geoLibreVersionStatus(event.data.version); + if (!status.compatible) { + setViewerError(status.message); + setViewerState("error"); + return; + } + sentProjectRef.current = null; + setViewerError(""); + setViewerState("ready"); + setReadyGeneration((generation) => generation + 1); + return; + } + + if (event.data.type === "geolibre:error") { + setViewerError( + event.data.message || "GeoLibre could not load the generated project." + ); + setViewerState("error"); + } + }; + + window.addEventListener("message", handleMessage); + return () => { + window.removeEventListener("message", handleMessage); + if (handshakeTimer !== null) window.clearTimeout(handshakeTimer); + }; + }, [viewer.origin, viewer.url]); + + useEffect( + () => () => { + if (fitTimerRef.current !== null) { + window.clearTimeout(fitTimerRef.current); + } + }, + [] + ); + + useEffect(() => { + const target = frameRef.current?.contentWindow; + if ( + !target || + !project || + !["ready", "loaded"].includes(viewerState) || + sentProjectRef.current === project + ) { + return; + } + + sequenceRef.current += 1; + const sequence = sequenceRef.current; + target.postMessage( + { + type: "geolibre:load-project", + project, + seq: sequence, + }, + viewer.origin + ); + sentProjectRef.current = project; + if (fitTimerRef.current !== null) { + window.clearTimeout(fitTimerRef.current); + } + const bounds = project.mapView?.bbox; + if (Array.isArray(bounds) && bounds.length === 4) { + fitTimerRef.current = window.setTimeout(() => { + target.postMessage( + { + type: "geolibre:command", + requestId: `kyl-fit-bounds-${sequence}`, + method: "fitBounds", + params: { bounds }, + }, + viewer.origin + ); + fitTimerRef.current = null; + }, 1500); + } + setViewerState("loaded"); + }, [project, readyGeneration, viewer.origin, viewerState]); + + const activeError = viewer.error || preparationError || viewerError; + const showProgress = !activeError && (!project || viewerState === "loading"); + + return ( +
+ {viewer.url && ( +