LayerViewer
Toggleable, opacity-controlled georeferenced overlays draped on a satellite base map — plus click-to-measure, point/line/polygon annotation, a two-layer compare swipe, and a manual GPS-drift alignment nudge.
Compare / AlignToggle Compare or Align to try them
Usage
import { LayerViewer, LayerControl, CompareSwipe, AlignControl } from "@/components/LayerViewer";
import type { LayerDef, LayerViewerHandle } from "@/components/LayerViewer";
const viewerRef = useRef<LayerViewerHandle>(null);
const [layers, setLayers] = useState<LayerDef[]>([]);
const [isFullscreen, setIsFullscreen] = useState(false);
<LayerViewer
ref={viewerRef}
manifest="/layer-viewer-samples/2806-prado/layers.json"
onLayersChange={setLayers}
onFullscreenChange={setIsFullscreen}
/>
{/* Measure + Annotate are built into <LayerViewer> itself — their own
floating panels (bottom-left / top-left) render automatically, no
extra wiring needed. They're mutually exclusive: activating one
turns the other off (see docs/components/layer-viewer.md's
"Phase 2 tools" section for the click-ownership integration check
behind that). */}
<LayerControl
layers={layers}
onToggle={(id, toggle) => viewerRef.current?.toggleLayer(id, toggle)}
onOpacityChange={(id, opacity) => viewerRef.current?.setOpacity(id, opacity)}
/>
<button onClick={() => viewerRef.current?.toggleFullscreen()}>
{isFullscreen ? "Exit fullscreen" : "Fullscreen"}
</button>
{/* Compare and Align are separate siblings — mount them only when a
consumer wants that overlay, passing the SAME viewerRef + layers. */}
<CompareSwipe viewerRef={viewerRef} layers={layers} layerA="ortho" layerB="hillshade" />
<AlignControl viewerRef={viewerRef} layers={layers} />