Skip to content

Single-file no-build-step example #589

Description

@kylebarron

See https://github.com/microsoft/PlanetaryComputerDataCatalog/pull/530/changes#diff-75f8c328e1b62b6317e072b79dea671d5638af89f2c75bbe1b7f29491bf559f7

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Planetary Computer · deck.gl-raster (no build)</title>
  <link href="https://esm.sh/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
  <style>
    html, body { margin: 0; height: 100%; font-family: system-ui, sans-serif; }
    #map { position: absolute; inset: 0; }
    #panel {
      position: absolute; top: 16px; left: 16px; z-index: 2; width: 240px;
      background: #ffffff; border-radius: 10px; padding: 16px 18px;
      box-shadow: 0 2px 14px #0003; font-size: 13px; color: #1a1a1a;
    }
    #panel h1 { font-size: 15px; margin: 0 0 6px; }
    #panel .muted { color: #666; line-height: 1.4; }
    #panel label { display: block; margin-top: 14px; font-weight: 600; }
    #panel input[type=range] { width: 100%; }
    #scene { font-family: ui-monospace, monospace; font-size: 11px; word-break: break-all; }
  </style>

  <!-- No build step: an import map resolves every dependency from a CDN.
       All @deck.gl/* and @luma.gl/core MUST share one version (mismatched
       patch versions throw "deck.gl - multiple versions detected"). The
       deck.gl-geotiff entry marks deck/luma/geotiff as `external` so they
       resolve to the singletons above instead of being bundled again. -->
  <script type="importmap">
  {
    "imports": {
      "maplibre-gl": "https://esm.sh/maplibre-gl@4.7.1",
      "@deck.gl/core": "https://esm.sh/@deck.gl/core@9.3.2",
      "@deck.gl/layers": "https://esm.sh/@deck.gl/layers@9.3.2",
      "@deck.gl/geo-layers": "https://esm.sh/@deck.gl/geo-layers@9.3.2",
      "@deck.gl/mesh-layers": "https://esm.sh/@deck.gl/mesh-layers@9.3.2",
      "@deck.gl/mapbox": "https://esm.sh/@deck.gl/mapbox@9.3.2?external=@deck.gl/core,maplibre-gl",
      "@luma.gl/core": "https://esm.sh/@luma.gl/core@9.3.2",
      "@developmentseed/geotiff": "https://esm.sh/@developmentseed/geotiff@0.7.0",
      "@developmentseed/deck.gl-geotiff": "https://esm.sh/@developmentseed/deck.gl-geotiff@0.7.0?external=@deck.gl/core,@deck.gl/layers,@deck.gl/geo-layers,@deck.gl/mesh-layers,@luma.gl/core,@developmentseed/geotiff"
    }
  }
  </script>
</head>
<body>
  <div id="map"></div>
  <div id="panel">
    <h1>NAIP over Portland</h1>
    <div class="muted">A Cloud Optimized GeoTIFF, decoded and reprojected in your browser with <b>deck.gl-raster</b>. No tile server.</div>
    <label>Imagery opacity</label>
    <input id="opacity" type="range" min="0" max="100" value="100" />
    <label>Scene</label>
    <div id="scene" class="muted">searching…</div>
  </div>

  <script type="module">
    import maplibregl from "maplibre-gl";
    import { MapboxOverlay } from "@deck.gl/mapbox";
    import { COGLayer } from "@developmentseed/deck.gl-geotiff";
    import { DecoderPool } from "@developmentseed/geotiff";

    const STAC = "https://planetarycomputer.microsoft.com/api/stac/v1";
    const SIGN = "https://planetarycomputer.microsoft.com/api/sas/v1/sign?href=";

    const map = new maplibregl.Map({
      container: "map",
      style: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
      center: [-122.62, 45.52],
      zoom: 11,
    });

    // size: 0 keeps decoding on the main thread. The default pool spawns a
    // Web Worker from the package URL, which browsers block when that URL is
    // cross-origin (a CDN). Main-thread decoding sidesteps that for a
    // single-file app; add a same-origin worker if you need the throughput.
    const pool = new DecoderPool({ size: 0 });
    const overlay = new MapboxOverlay({ interleaved: true, layers: [] });
    map.addControl(overlay);

    let opacity = 1;
    let geotiff = null;
    let beforeId = null; // draw imagery beneath the basemap's labels
    let styleReady = false;
    let fitted = false;

    function render() {
      if (!geotiff || !styleReady) return;
      overlay.setProps({
        layers: [
          new COGLayer({
            id: "naip",
            geotiff,
            pool,
            opacity,
            beforeId,
            // Frame the map to the COG's own bounds once it has loaded.
            onGeoTIFFLoad: (tiff, { geographicBounds }) => {
              if (fitted) return;
              fitted = true;
              const { west, south, east, north } = geographicBounds;
              map.fitBounds([[west, south], [east, north]], { padding: 40, duration: 0 });
            },
          }),
        ],
      });
    }

    document.getElementById("opacity").addEventListener("input", (e) => {
      opacity = e.target.value / 100;
      render();
    });

    map.on("load", () => {
      // Insert deck layers before the first label layer so basemap text
      // (place names, roads) stays legible on top of the imagery.
      beforeId = map.getStyle().layers.find((l) => l.type === "symbol")?.id;
      styleReady = true;
      render();
    });

    // Find a NAIP scene over Portland, then sign its asset href. The Planetary
    // Computer signing endpoint is public. No key, no backend.
    const search = await fetch(`${STAC}/search`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        collections: ["naip"],
        bbox: [-122.70, 45.50, -122.55, 45.57],
        datetime: "2022-01-01/2023-01-01",
        limit: 1,
      }),
    }).then((r) => r.json());

    const item = search.features[0];
    const signed = await fetch(SIGN + encodeURIComponent(item.assets.image.href)).then((r) => r.json());
    geotiff = signed.href;
    document.getElementById("scene").textContent = item.id;
    render();
  </script>
</body>
</html>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions