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 / Align
Toggle 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} />