Edit

GeoZarr

zarr4 geozarr4 sentinel-23

A GeoZarr source displaying Sentinel-2 imagery.

The GeoZarr source can be used to read groups of multi-band data from Zarr files following the GeoZarr specification. GeoZarrs following the Multiscales convention are supported. Legacy multiscales GeoZarrs with an OGC TileMatrixSet definition also work.

In this example, a true-color composite is rendered from Sentinel-2 L2A bands B02 (blue), B03 (green), and B04 (red) reflectance values. Reflectance values from 0 to 0.5 are stretched to values between 0 and 255 for the RGB color channels. A gamma correction is also made to brighten the image.

To try different scenes, browse to the Sentinel-2 Level-2A catalog of the EOPF Explorer, find a Zarr store, and paste the url including the group path (e.g. store.zarr/measurements/reflectance) after selecting "Custom..." from the dropdown above. Note that only GeoZarrs with bands 'b02', 'b03', and 'b04' will work without changes to the source code.

main.js
import Map from 'ol/Map.js';
import {
  getView,
  withExtentCenter,
  withHigherResolutions,
  withLowerResolutions,
  withZoom,
} from 'ol/View.js';
import TileLayer from 'ol/layer/WebGLTile.js';
import GeoZarr from 'ol/source/GeoZarr.js';
import OSM from 'ol/source/OSM.js';

const select = document.getElementById('url-select');
const input = document.getElementById('custom-url');
const button = document.getElementById('load-url');

function getUrl() {
  return select.value === 'custom' ? input.value : select.value;
}

select.addEventListener('change', () => {
  if (select.value === 'custom') {
    input.style.display = '';
    input.focus();
  } else {
    input.style.display = 'none';
  }
});

let map;

function render() {
  const url = getUrl();
  if (!url) {
    return;
  }

  const source = new GeoZarr({
    url: url,
    bands: ['b04', 'b03', 'b02'],
  });

  if (map) {
    map.setTarget(null);
  }

  map = new Map({
    layers: [
      new TileLayer({
        source: new OSM(),
      }),
      new TileLayer({
        style: {
          gamma: 1.5,
          color: [
            'color',
            ['interpolate', ['linear'], ['band', 1], 0, 0, 0.5, 255],
            ['interpolate', ['linear'], ['band', 2], 0, 0, 0.5, 255],
            ['interpolate', ['linear'], ['band', 3], 0, 0, 0.5, 255],
          ],
        },
        source,
      }),
    ],
    target: 'map',
    view: getView(
      source,
      withLowerResolutions(1),
      withHigherResolutions(2),
      withExtentCenter(),
      withZoom(2),
    ),
  });
}

button.addEventListener('click', render);

render();
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>GeoZarr</title>
    <link rel="stylesheet" href="node_modules/ol/ol.css">
    <style>
      .map {
        width: 100%;
        height: 400px;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map"></div>
    <div class="row mt-2">
      <div class="col-auto">
        <div class="input-group">
          <label class="input-group-text" for="url-select">URL</label>
          <select class="form-select" id="url-select">
            <option value="https://s3.explorer.eopf.copernicus.eu/esa-zarr-sentinel-explorer-fra/tests-output/sentinel-2-l2a/S2C_MSIL2A_20260120T084301_N0511_R064_T36UXA_20260120T122910.zarr/measurements/reflectance">Sentinel-2 L2A (Krasnokuts'k)</option>
            <option value="https://s3.explorer.eopf.copernicus.eu/esa-zarr-sentinel-explorer-fra/tests-output/sentinel-2-l2a/S2B_MSIL2A_20260120T125339_N0511_R138_T27VWL_20260120T131151.zarr/measurements/reflectance">Sentinel-2 L2A (Hvolsvöllur)</option>
            <option value="custom">Custom...</option>
          </select>
          <input type="text" class="form-control" id="custom-url" placeholder="Enter Zarr URL" style="display: none; width: 300px;">
          <button class="btn btn-outline-secondary" id="load-url" type="button">Load</button>
        </div>
      </div>
    </div>

    <script type="module" src="main.js"></script>
  </body>
</html>
package.json
{
  "name": "geozarr",
  "dependencies": {
    "ol": "10.9.0"
  },
  "devDependencies": {
    "vite": "^3.2.3"
  },
  "scripts": {
    "start": "vite",
    "build": "vite build"
  }
}