├── .github └── workflows │ └── quarto-publish.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE.txt ├── README.md ├── docs ├── .gitignore ├── _quarto.yml ├── custom.scss ├── examples │ ├── 00_download_data.ipynb │ ├── 01_CRS_in_auxiliary_variable.ipynb │ ├── 02_CRS_and_geotransform_in_auxiliary_variable.ipynb │ ├── 03_multiscales_as_WebMercatorQuad_ZarrV2.ipynb │ ├── 04_multiscales_as_WebMercatorQuad_ZarrV3.ipynb │ └── 05_WIP_multiscales_as_customTMS_ZarrV2.ipynb ├── index.qmd ├── slides │ ├── 2025-02.qmd │ ├── 2025-04.qmd │ └── _static │ │ ├── CDM-PR.png │ │ ├── CDM-issue.png │ │ ├── CDM.png │ │ ├── CNG-conference.png │ │ ├── EGU.png │ │ ├── OGC-TMS.png │ │ ├── Pangeo-NetCDF-limitations.png │ │ ├── UADM.png │ │ ├── UADM_example.png │ │ ├── conformance-class-meeting.png │ │ ├── flexible-coordinates.png │ │ ├── geozarr-activity.png │ │ ├── geozarr-examples-repo.png │ │ ├── geozarr-examples-request.png │ │ ├── geozarr-repo.png │ │ ├── pangeo-showcase.png │ │ ├── recurring-meeting.png │ │ ├── titiler-multidim-repo.png │ │ ├── virtualizarr.png │ │ ├── xproj-docs.png │ │ ├── xpublish-community.png │ │ ├── zarr-obstore.png │ │ ├── zarr-stac-issue.png │ │ └── zep9.png ├── spec-diagram.qmd ├── styles.css ├── theme-dark.scss ├── web-optimized-zarr.qmd └── woz-diagram.qmd ├── pixi.lock ├── pyproject.toml ├── src └── geozarr_examples │ └── __init__.py └── tests ├── __init__.py └── test_version.py /.github/workflows/quarto-publish.yaml: -------------------------------------------------------------------------------- 1 | # Publish Quarto Book 2 | # https://github.com/quarto-dev/quarto-actions/blob/v2.1.3/examples/example-01-basics.md 3 | name: Render and Publish 4 | 5 | # Only run for pushes to the main branch 6 | on: 7 | push: 8 | branches: main 9 | # Runs for pull requests should be disabled other than for testing purposes 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | build-deploy: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Set up Quarto 22 | uses: quarto-dev/quarto-actions/setup@v2 23 | 24 | - name: Publish to GitHub Pages (and render) 25 | uses: quarto-dev/quarto-actions/publish@v2 26 | with: 27 | target: gh-pages 28 | path: docs 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | .venv/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | docs/api 56 | docs/data 57 | data 58 | data.zip 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | # PyCharm 64 | .idea 65 | 66 | # Jupyter 67 | .ipynb_checkpoints/ 68 | 69 | # VCS versioning 70 | src/geozarr_examples/_version.py 71 | 72 | # emacs 73 | *~ 74 | 75 | # VSCode 76 | .vscode/ 77 | 78 | # test data 79 | 80 | .DS_Store 81 | tests/.hypothesis 82 | .hypothesis/ 83 | 84 | # pixi environments 85 | .pixi 86 | *.egg-info 87 | 88 | # Paired notebooks 89 | examples/*.py 90 | 91 | # WIP 92 | scratch-notebooks/* 93 | 94 | # data 95 | data/* 96 | output/* 97 | 98 | docs/data/* 99 | docs/output/* 100 | 101 | .gitattributes 102 | 103 | docs/_site/ 104 | docs/.quarto/ 105 | 106 | .mypy_cache/ 107 | .pytest_cache/ 108 | .ruff_cache/ 109 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_commit_msg: "chore: update pre-commit hooks" 3 | autoupdate_schedule: "monthly" 4 | autofix_commit_msg: "style: pre-commit fixes" 5 | autofix_prs: false 6 | default_stages: [pre-commit, pre-push] 7 | repos: 8 | - repo: https://github.com/astral-sh/ruff-pre-commit 9 | rev: v0.9.4 10 | hooks: 11 | - id: ruff 12 | args: ["--fix", "--show-fixes"] 13 | - id: ruff-format 14 | - repo: https://github.com/codespell-project/codespell 15 | rev: v2.4.1 16 | hooks: 17 | - id: codespell 18 | exclude: pixi.lock 19 | args: ["-L", "fo,ihs,kake,te", "-S", "fixture"] 20 | - repo: https://github.com/pre-commit/pre-commit-hooks 21 | rev: v5.0.0 22 | hooks: 23 | - id: check-yaml 24 | - id: trailing-whitespace 25 | - repo: https://github.com/pre-commit/mirrors-mypy 26 | rev: v1.14.1 27 | hooks: 28 | - id: mypy 29 | - repo: https://github.com/pre-commit/pygrep-hooks 30 | rev: v1.10.0 31 | hooks: 32 | - id: rst-directive-colons 33 | - id: rst-inline-touching-normal 34 | - repo: https://github.com/nbQA-dev/nbQA 35 | rev: 1.8.7 36 | hooks: 37 | - id: nbqa-ruff 38 | args: ["--fix"] 39 | - id: nbqa-isort 40 | args: ["--profile=black"] 41 | additional_dependencies: [isort==5.6.4] 42 | - id: nbqa-black 43 | - id: nbqa-pyupgrade 44 | args: ["--py37-plus"] 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Development Seed 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeoZarr examples 2 | 3 | ## About 4 | 5 | This repository contains in-progress work towards GeoZarr examples. If useful, the contents will eventually be migrated to a different repository, such as the 6 | [Cloud Optimized Geospatial Formats Guide](https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide) or the [GeoZarr spec](https://github.com/zarr-developers/geozarr-spec) 7 | repository. 8 | 9 | More information is available in the [GeoZarr examples website](https://developmentseed.org/geozarr-examples/). 10 | 11 | ## License 12 | 13 | Content in this repository is licensed under the [MIT License](LICENSE.txt). 14 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /.quarto/ 2 | -------------------------------------------------------------------------------- /docs/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | output-dir: _site 4 | 5 | website: 6 | sidebar: 7 | style: "docked" 8 | search: true 9 | collapse-level: 2 10 | title: "GeoZarr" 11 | tools: 12 | - icon: github 13 | href: https://github.com/developmentseed/geozarr-examples 14 | text: "Repo" 15 | contents: 16 | - href: index.qmd 17 | text: Home 18 | - href: spec-diagram.qmd 19 | text: Visual explainer 20 | - href: web-optimized-zarr.qmd 21 | text: Web-optimized Zarr (WOZ) 22 | - href: woz-diagram.qmd 23 | text: Visual WOZ comparison 24 | - section: Presentations 25 | contents: 26 | - href: slides/2025-02.qmd 27 | text: Team week (February 2025) 28 | - href: slides/2025-04.qmd 29 | text: STAC & Zarr workshop (April 2025) 30 | - section: Pre-requisites 31 | contents: 32 | - href: examples/00_download_data.ipynb 33 | text: Download dataset for examples 34 | - section: Examples 35 | contents: 36 | - href: examples/01_CRS_in_auxiliary_variable.ipynb 37 | text: Explicit coordinates 38 | - href: examples/02_CRS_and_geotransform_in_auxiliary_variable.ipynb 39 | text: GeoTransform 40 | - href: examples/03_multiscales_as_WebMercatorQuad_ZarrV2.ipynb 41 | text: WebMercatorQuad overviews (Zarr V2) 42 | - href: examples/04_multiscales_as_WebMercatorQuad_ZarrV3.ipynb 43 | text: WebMercatorQuad overviews (Zarr V3) 44 | 45 | format: 46 | 47 | html: 48 | theme: 49 | light: [cosmo, theme.scss] # from https://github.com/sta210-s22/website/blob/main/_quarto.yml 50 | dark: [cosmo, theme-dark.scss] 51 | code-copy: true 52 | code-overflow: wrap 53 | css: styles.css 54 | filters: 55 | - quarto 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/custom.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | 3 | // fonts 4 | $presentation-font-size-root: 27px !default; 5 | 6 | // colors 7 | $body-color: #000 !default; 8 | $selection-bg: #26351c !default; 9 | 10 | // headings 11 | $presentation-heading-color: #333 !default; 12 | 13 | .wrap { 14 | width: 400px; 15 | height: 600px; 16 | padding: 0; 17 | overflow: hidden; 18 | } 19 | 20 | .frame { 21 | width: 1200px; 22 | height: 1800px; 23 | border: 0; 24 | -ms-transform: scale(0.25); 25 | -moz-transform: scale(0.25); 26 | -o-transform: scale(0.25); 27 | -webkit-transform: scale(0.25); 28 | transform: scale(0.25); 29 | 30 | -ms-transform-origin: 0 0; 31 | -moz-transform-origin: 0 0; 32 | -o-transform-origin: 0 0; 33 | -webkit-transform-origin: 0 0; 34 | transform-origin: 0 0; 35 | } 36 | 37 | /*-- scss:rules --*/ 38 | -------------------------------------------------------------------------------- /docs/examples/00_download_data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Download data\n", 8 | "\n", 9 | "We'll use the earthaccess library to download a single file for the [MUR-SST](https://podaac.jpl.nasa.gov/MEaSUREs-MUR) dataset to use for these examples." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import earthaccess" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "application/vnd.jupyter.widget-view+json": { 29 | "model_id": "35236c689f244982a96bc3e9bd8debd2", 30 | "version_major": 2, 31 | "version_minor": 0 32 | }, 33 | "text/plain": [ 34 | "QUEUEING TASKS | : 0%| | 0/1 [00:00" 144 | ] 145 | }, 146 | "execution_count": 7, 147 | "metadata": {}, 148 | "output_type": "execute_result" 149 | } 150 | ], 151 | "source": [ 152 | "spatial_chunk = 4096\n", 153 | "compressor = Zstd(level=1)\n", 154 | "encoding = {\n", 155 | " \"analysed_sst\": {\n", 156 | " \"chunks\": (1, spatial_chunk, spatial_chunk),\n", 157 | " \"compressor\": compressor,\n", 158 | " },\n", 159 | " \"sea_ice_fraction\": {\n", 160 | " \"chunks\": (1, spatial_chunk, spatial_chunk),\n", 161 | " \"compressor\": compressor,\n", 162 | " },\n", 163 | "}\n", 164 | "ds.to_zarr(v2_output, mode=\"w\", consolidated=True, zarr_format=2, encoding=encoding)" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "## Inspect Zarr V2 store" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "First, let's look at the structure of Zarr arrays using zarr's `Group.tree()` method" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 8, 184 | "metadata": {}, 185 | "outputs": [ 186 | { 187 | "data": { 188 | "text/html": [ 189 | "
/\n",
190 |        "├── analysed_sst (1, 17999, 36000) float64\n",
191 |        "├── lat (17999,) float32\n",
192 |        "├── lon (36000,) float32\n",
193 |        "├── sea_ice_fraction (1, 17999, 36000) float64\n",
194 |        "├── spatial_ref () int64\n",
195 |        "└── time (1,) int32\n",
196 |        "
\n" 197 | ], 198 | "text/plain": [ 199 | "\u001b[1m/\u001b[0m\n", 200 | "├── \u001b[1manalysed_sst\u001b[0m (1, 17999, 36000) float64\n", 201 | "├── \u001b[1mlat\u001b[0m (17999,) float32\n", 202 | "├── \u001b[1mlon\u001b[0m (36000,) float32\n", 203 | "├── \u001b[1msea_ice_fraction\u001b[0m (1, 17999, 36000) float64\n", 204 | "├── \u001b[1mspatial_ref\u001b[0m () int64\n", 205 | "└── \u001b[1mtime\u001b[0m (1,) int32\n" 206 | ] 207 | }, 208 | "execution_count": 8, 209 | "metadata": {}, 210 | "output_type": "execute_result" 211 | } 212 | ], 213 | "source": [ 214 | "root = zarr.open_group(v2_output)\n", 215 | "root.tree()" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "Second, let's look at what's actually recorded in the Zarr metadata using the consolidated metadata at the root of the Zarr store.\n", 223 | "\n", 224 | "In order to match valid JSON, we convert the nan fill_value entries to \"nan\".\n", 225 | "\n", 226 | "### Key observations\n", 227 | "\n", 228 | "- For each array, metadata is stored under '.zattrs'\n", 229 | "- All arrays contain a `.zattrs/standard_name`\n", 230 | "- The root group specifies that the metadata follows CF conventions, which should be validated.\n", 231 | "- `.zattrs/_ARRAY_DIMENSIONS` for `lat`, `lon`, and `time` contains a list with only the the name of the array, indicating that they are coordinates variables.\n", 232 | "- `.zattrs/_ARRAY_DIMENSIONS` for `spatial_ref` contains an empty list, indicating that it is an auxiliary coordinate.\n", 233 | "- `.zattrs/_ARRAY_DIMENSIONS` for `analysed_sst`, `sea_ice_fraction` contain a list referring to other arrays, indicating that they are data variables rather than coordinate variables.\n", 234 | "- `.zattrs/grid_mapping` for `analysed_sst`, `sea_ice_fraction` is `\"spatial_ref\"` indicating that CRS information is included in that auxiliary variable's metadata.\n", 235 | "- `spatial_ref/.zattrs` contains the OGC WKT for the CRS.\n" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 9, 241 | "metadata": {}, 242 | "outputs": [ 243 | { 244 | "data": { 245 | "text/html": [ 246 | "" 262 | ] 263 | }, 264 | "metadata": {}, 265 | "output_type": "display_data" 266 | }, 267 | { 268 | "data": { 269 | "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.6.3'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.6.1/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.6.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.6.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.6.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.6.3.min.js\", \"https://cdn.holoviz.org/panel/1.6.1/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", 270 | "application/vnd.holoviews_load.v0+json": "" 271 | }, 272 | "metadata": {}, 273 | "output_type": "display_data" 274 | }, 275 | { 276 | "data": { 277 | "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", 278 | "application/vnd.holoviews_load.v0+json": "" 279 | }, 280 | "metadata": {}, 281 | "output_type": "display_data" 282 | }, 283 | { 284 | "data": { 285 | "application/vnd.holoviews_exec.v0+json": "", 286 | "text/html": [ 287 | "
\n", 288 | "
\n", 289 | "
\n", 290 | "" 357 | ] 358 | }, 359 | "metadata": { 360 | "application/vnd.holoviews_exec.v0+json": { 361 | "id": "d5765014-2f46-4e0d-aedd-b3a2791f5dd5" 362 | } 363 | }, 364 | "output_type": "display_data" 365 | }, 366 | { 367 | "data": { 368 | "application/vnd.jupyter.widget-view+json": { 369 | "model_id": "895109a059e1445585576f90d1df9cc5", 370 | "version_major": 2, 371 | "version_minor": 0 372 | }, 373 | "text/plain": [ 374 | "BokehModel(combine_events=True, render_bundle={'docs_json': {'b8707c36-4222-4429-9ac7-abaa84db5819': {'version…" 375 | ] 376 | }, 377 | "execution_count": 9, 378 | "metadata": {}, 379 | "output_type": "execute_result" 380 | } 381 | ], 382 | "source": [ 383 | "panel.extension()\n", 384 | "consolidated_metadata_file = f\"{v2_output}/.zmetadata\"\n", 385 | "with open(consolidated_metadata_file) as f:\n", 386 | " metadata = json.load(f)[\"metadata\"]\n", 387 | "metadata[\"sea_ice_fraction/.zarray\"][\"fill_value\"] = str(\n", 388 | " metadata[\"sea_ice_fraction/.zarray\"][\"fill_value\"]\n", 389 | ")\n", 390 | "metadata[\"analysed_sst/.zarray\"][\"fill_value\"] = str(\n", 391 | " metadata[\"sea_ice_fraction/.zarray\"][\"fill_value\"]\n", 392 | ")\n", 393 | "panel.pane.JSON(metadata, name=\"JSON\")" 394 | ] 395 | }, 396 | { 397 | "cell_type": "markdown", 398 | "metadata": {}, 399 | "source": [ 400 | "## Specify encoding and write to Zarr V3 format\n", 401 | "\n", 402 | "While GeoZarr v0.4 is Zarr V2 specific, let's write a Zarr V3 store to get an idea about how GeoZarr could be adapted for Zarr format 3." 403 | ] 404 | }, 405 | { 406 | "cell_type": "code", 407 | "execution_count": 10, 408 | "metadata": {}, 409 | "outputs": [ 410 | { 411 | "name": "stderr", 412 | "output_type": "stream", 413 | "text": [ 414 | "/Users/max/Documents/Code/developmentseed/geozarr-examples/.pixi/envs/test/lib/python3.13/site-packages/zarr/api/asynchronous.py:203: UserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.\n", 415 | " warnings.warn(\n" 416 | ] 417 | }, 418 | { 419 | "data": { 420 | "text/plain": [ 421 | "" 422 | ] 423 | }, 424 | "execution_count": 10, 425 | "metadata": {}, 426 | "output_type": "execute_result" 427 | } 428 | ], 429 | "source": [ 430 | "spatial_chunk = 4096\n", 431 | "compressor = zarr.codecs.ZstdCodec(level=1)\n", 432 | "encoding = {\n", 433 | " \"analysed_sst\": {\n", 434 | " \"chunks\": (1, spatial_chunk, spatial_chunk),\n", 435 | " \"compressors\": compressor,\n", 436 | " },\n", 437 | " \"sea_ice_fraction\": {\n", 438 | " \"chunks\": (1, spatial_chunk, spatial_chunk),\n", 439 | " \"compressors\": compressor,\n", 440 | " },\n", 441 | "}\n", 442 | "ds.to_zarr(v3_output, mode=\"w\", consolidated=True, zarr_format=3, encoding=encoding)" 443 | ] 444 | }, 445 | { 446 | "cell_type": "markdown", 447 | "metadata": {}, 448 | "source": [ 449 | "### Key observations\n", 450 | "\n", 451 | "- For each group and array, metadata is stored under the 'attributes' key in 'zarr.json'.\n", 452 | "- All arrays contain a `attributes/standard_name`.\n", 453 | "- The dimensions associated with an array are stored under `zarr.json/dimension_names` (separately from the `attributes`) rather than `_ARRAY_DIMENSIONS`.\n", 454 | "- the `node_type` specifies whether the key holds a Zarr Array or a Zarr Group.\n", 455 | "- The coordinates associated with an array are still specified within the array metadata. Currently this is an Xarray implementation detail rather than a part of the GeoZarr specification.\n", 456 | "- The `fill_value` for `sea_ice_fraction` and `analysed_sst` is 0 instead of nan. This is likely an error with the fill value not being explicitly specified." 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": 11, 462 | "metadata": {}, 463 | "outputs": [ 464 | { 465 | "data": { 466 | "text/html": [ 467 | "" 483 | ] 484 | }, 485 | "metadata": {}, 486 | "output_type": "display_data" 487 | }, 488 | { 489 | "data": { 490 | "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.6.3'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.6.1/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.6.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.6.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.6.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.6.3.min.js\", \"https://cdn.holoviz.org/panel/1.6.1/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", 491 | "application/vnd.holoviews_load.v0+json": "" 492 | }, 493 | "metadata": {}, 494 | "output_type": "display_data" 495 | }, 496 | { 497 | "data": { 498 | "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", 499 | "application/vnd.holoviews_load.v0+json": "" 500 | }, 501 | "metadata": {}, 502 | "output_type": "display_data" 503 | }, 504 | { 505 | "data": { 506 | "application/vnd.holoviews_exec.v0+json": "", 507 | "text/html": [ 508 | "
\n", 509 | "
\n", 510 | "
\n", 511 | "" 578 | ] 579 | }, 580 | "metadata": { 581 | "application/vnd.holoviews_exec.v0+json": { 582 | "id": "d1c1d405-df86-4d69-8a3a-2cb725fea185" 583 | } 584 | }, 585 | "output_type": "display_data" 586 | }, 587 | { 588 | "data": { 589 | "application/vnd.jupyter.widget-view+json": { 590 | "model_id": "c551a3f669684c7e95fbb87f886cfb42", 591 | "version_major": 2, 592 | "version_minor": 0 593 | }, 594 | "text/plain": [ 595 | "BokehModel(combine_events=True, render_bundle={'docs_json': {'d0742023-3bb1-4f57-aa9d-d1ddda091168': {'version…" 596 | ] 597 | }, 598 | "execution_count": 11, 599 | "metadata": {}, 600 | "output_type": "execute_result" 601 | } 602 | ], 603 | "source": [ 604 | "panel.extension()\n", 605 | "consolidated_metadata_file = f\"{v3_output}/zarr.json\"\n", 606 | "with open(consolidated_metadata_file) as f:\n", 607 | " metadata = json.load(f)[\"consolidated_metadata\"][\"metadata\"]\n", 608 | "panel.pane.JSON(metadata, name=\"JSON\")" 609 | ] 610 | } 611 | ], 612 | "metadata": { 613 | "kernelspec": { 614 | "display_name": "test", 615 | "language": "python", 616 | "name": "python3" 617 | }, 618 | "language_info": { 619 | "codemirror_mode": { 620 | "name": "ipython", 621 | "version": 3 622 | }, 623 | "file_extension": ".py", 624 | "mimetype": "text/x-python", 625 | "name": "python", 626 | "nbconvert_exporter": "python", 627 | "pygments_lexer": "ipython3", 628 | "version": "3.13.2" 629 | } 630 | }, 631 | "nbformat": 4, 632 | "nbformat_minor": 2 633 | } 634 | -------------------------------------------------------------------------------- /docs/examples/05_WIP_multiscales_as_customTMS_ZarrV2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Create a GeoZarr with multi-scales containing the WebMercatorQuad TMS" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Load example dataset from NetCDF into Xarray" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import cf_xarray # noqa\n", 24 | "import morecantile\n", 25 | "import pyproj\n", 26 | "import rioxarray # noqa\n", 27 | "import xarray as xr\n", 28 | "from matplotlib import pyplot as plt\n", 29 | "from rasterio.rio.overview import get_maximum_overview_level\n", 30 | "\n", 31 | "# For zarr_format=2 encoding\n", 32 | "from rio_tiler.io.xarray import XarrayReader" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 2, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "fp_base = \"20020601090000-JPL-L4_GHRSST-SSTfnd-MUR-GLOB-v02.0-fv04.1\"\n", 42 | "input = f\"../data/{fp_base}.nc\"\n", 43 | "v2_output = f\"../output/v2/{fp_base}_custom_multiscales.zarr\"" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "ds = xr.open_dataset(input)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "## Check that all variables have a CF-compliant standard name\n" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 4, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "name": "stdout", 69 | "output_type": "stream", 70 | "text": [ 71 | "These variables do NOT have a CF-compliant standard name: ['analysis_error', 'mask']\n", 72 | "These variables have a CF-compliant standard name: ['time', 'lat', 'lon', 'analysed_sst', 'sea_ice_fraction']\n" 73 | ] 74 | } 75 | ], 76 | "source": [ 77 | "standard_names = ds.cf.standard_names\n", 78 | "vars_with_standard_names = [v[0] for v in ds.cf.standard_names.values()]\n", 79 | "compliant_vars = []\n", 80 | "non_complaint_vars = []\n", 81 | "for var in ds.variables:\n", 82 | " if var not in vars_with_standard_names:\n", 83 | " non_complaint_vars.append(var)\n", 84 | " else:\n", 85 | " compliant_vars.append(var)\n", 86 | " assert ds[var].attrs[\"standard_name\"]\n", 87 | "\n", 88 | "print(f\"These variables do NOT have a CF-compliant standard name: {non_complaint_vars}\")\n", 89 | "print(f\"These variables have a CF-compliant standard name: {compliant_vars}\")" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "Not all the variables in this dataset have a CF-compliant standard name. See https://github.com/zarr-developers/geozarr-spec/issues/60 for a recommendation that CF-compliant standard names should be a \"SHOULD\" rather than a \"MUST\" condition in the GeoZarr spec. For now, let's subset to the variables that do use CF-compliant standard names." 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": 5, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "ds = ds[compliant_vars]" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Assign CRS information to an auxiliary variable using rioxarray" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 6, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "ds = ds.rio.write_crs(\"epsg:4326\")\n", 122 | "# Specify which variable contains CRS information using grid_mapping\n", 123 | "for var in ds.data_vars:\n", 124 | " ds[var].attrs[\"grid_mapping\"] = \"spatial_ref\"" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "## Define a custom TMS" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 7, 137 | "metadata": {}, 138 | "outputs": [ 139 | { 140 | "name": "stdout", 141 | "output_type": "stream", 142 | "text": [ 143 | "crs: EPSG:4326\n", 144 | "bounds: (-179.99500549324037, -89.99499786365084, 180.0050000000763, 89.99499786365084)\n" 145 | ] 146 | }, 147 | { 148 | "data": { 149 | "text/plain": [ 150 | "" 151 | ] 152 | }, 153 | "execution_count": 7, 154 | "metadata": {}, 155 | "output_type": "execute_result" 156 | }, 157 | { 158 | "data": { 159 | "image/png": "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OdIXLFiwgAULFhy0TDwep6mpadhlH3zwAb/61a948803ueSSSwD40Y9+xFVXXcU//dM/0dLScqRVEhGR08gJGeNatWoVDQ0NnHvuudx2223s2bOntKytrY2amppSaAHMmTMHx3FYs2bNsOvLZrP09PSUPURE5PR03INr/vz5/Ou//isvvfQS//AP/8Dq1atZsGABvu8D0NHRQUNDQ9lrIpEIdXV1dHR0DLvOZcuWkU6nS4+xY8ce72qLiEhIHHFX4aHccMMNpX9PnTqVadOmMWnSJFatWsXs2bOPap1Lly7lzjvvLP3c09Oj8BIROU2d8OnwEydOpL6+ng0bNgDQ1NTEzp07y8p4nkdnZ+enjovF43FSqVTZQ0RETk8nPLg+/vhj9uzZQ3NzMwAzZ86kq6uLtWvXlsqsXLmSIAiYMWPGia6OiIiE3BF3Ffb19ZXOngA2bdrEO++8Q11dHXV1dTzwwANcd911NDU1sXHjRr773e9y1llnMW/ePADOP/985s+fz80338xjjz1GPp9nyZIl3HDDDZpRKCIih2SstfZIXrBq1Sq++MUvHvD84sWLefTRR7nmmmt4++236erqoqWlhblz5/J3f/d3NDY2lsp2dnayZMkSXnjhBRzH4brrruPhhx+mqqrqsOrQ09NDOp2mu7tb3YYiIiF0LMfxIw6uPwYKLhGRcDuW47juVSgiIqGi4BIRkVBRcImISKgouEREJFQUXCIiEioKLhERCRUFl4iIhIqCS0REQkXBJSIioaLgEhGRUFFwiYhIqCi4REQkVBRcIiISKgouEREJFQWXiIiEioJLRERCRcElIiKhouASEZFQUXCJiEioKLhERCRUFFwiIhIqCi4REQkVBZeIiISKgktEREJFwSUiIqGi4BIRkVBRcImISKgouEREJFQUXCIiEioKLhERCRUFl4iIhIqCS0REQkXBJSIioaLgEhGRUFFwiYhIqCi4REQkVBRcIiISKgouEREJFQWXiIiEioJLRERCRcElIiKhouASEZFQUXCJiEioKLhERCRUFFwiIhIqCi4REQkVBZeIiISKgktEREJFwSUiIqGi4BIRkVBRcImISKgouEREJFQUXCIiEioKLhERCRUFl4iIhIqCS0REQkXBJSIioaLgEhGRUFFwiYhIqCi4REQkVBRcIiISKkcUXMuWLWP69OlUV1fT0NDANddcQ3t7e1mZTCZDa2sro0aNoqqqiuuuu44dO3aUldm8eTMLFy6ksrKShoYG7r77bjzPO/atERGRU94RBdfq1atpbW3l9ddfZ8WKFeTzeebOnUt/f3+pzB133MELL7zA008/zerVq9m2bRvXXnttabnv+yxcuJBcLsdrr73GT37yE5544gnuu+++47dVIiJy6rLHYOfOnRawq1evttZa29XVZaPRqH366adLZT744AML2La2Nmuttb/85S+t4zi2o6OjVObRRx+1qVTKZrPZw3rf7u5uC9ju7u5jqb6IiIyQYzmOH9MYV3d3NwB1dXUArF27lnw+z5w5c0plzjvvPMaNG0dbWxsAbW1tTJ06lcbGxlKZefPm0dPTw/r164d9n2w2S09PT9lDREROT0cdXEEQcPvttzNr1iymTJkCQEdHB7FYjJqamrKyjY2NdHR0lMrsG1pDy4eWDWfZsmWk0+nSY+zYsUdbbRERCbmjDq7W1lbWrVvHz372s+NZn2EtXbqU7u7u0mPLli0n/D1FROSPU+RoXrRkyRKWL1/OK6+8wpgxY0rPNzU1kcvl6OrqKjvr2rFjB01NTaUyb7zxRtn6hmYdDpXZXzweJx6PH01VRUTkFHNEZ1zWWpYsWcKzzz7LypUrmTBhQtnyiy++mGg0yksvvVR6rr29nc2bNzNz5kwAZs6cyXvvvcfOnTtLZVasWEEqlWLy5MnHsi0iInIaOKIzrtbWVp588kmef/55qqurS2NS6XSaRCJBOp3mpptu4s4776Suro5UKsW3v/1tZs6cyWWXXQbA3LlzmTx5Ml//+td56KGH6Ojo4N5776W1tVVnVSIickjGWmsPu7Axwz7/+OOPc+ONNwKFC5DvuusunnrqKbLZLPPmzePHP/5xWTfgRx99xG233caqVatIJpMsXryYBx98kEjk8HK0p6eHdDpNd3c3qVTqcKsvIiJ/JI7lOH5EwfXHQsElIhJux3Ic170KRUQkVBRcIiISKgouEREJFQWXiIiEioJLRERCRcElIiKhouASEZFQUXCJiEioKLhERCRUFFwiIhIqCi4REQkVBZeIiISKgktEREJFwSUiIqGi4BIRkVBRcImISKgouEREJFQUXCIiEioKLhERCRUFl4iIhIqCS0REQkXBJSIioaLgEhGRUFFwiYhIqCi4REQkVBRcIiISKgouEREJFQWXiIiEioJLRERCRcElIiKhouASEZFQUXCJiEioKLhERCRUFFwiIhIqCi4REQkVBZeIiISKgktEREJFwSUiIqGi4BIRkVBRcImISKgouEREJFQUXCIiEioKLhERCRUFl4iIhIqCS0REQkXBJSIioaLgEhGRUFFwiYhIqCi4REQkVBRcIiISKgouEREJFQWXiIiEioJLRERCRcElIiKhouASEZFQUXCJiEioKLhERCRUFFwiIhIqCi4REQkVBZeIiISKgktERELliIJr2bJlTJ8+nerqahoaGrjmmmtob28vK3PllVdijCl73HrrrWVlNm/ezMKFC6msrKShoYG7774bz/OOfWtEROSUFzmSwqtXr6a1tZXp06fjeR7f+973mDt3Lu+//z7JZLJU7uabb+b73/9+6efKysrSv33fZ+HChTQ1NfHaa6+xfft2vvGNbxCNRvnBD35wHDZJREROZcZaa4/2xbt27aKhoYHVq1dz+eWXA4Uzrs985jP88z//87CvefHFF/nTP/1Ttm3bRmNjIwCPPfYY99xzD7t27SIWix3yfXt6ekin03R3d5NKpY62+iIiMkKO5Th+TGNc3d3dANTV1ZU9/9Of/pT6+nqmTJnC0qVLGRgYKC1ra2tj6tSppdACmDdvHj09Paxfv37Y98lms/T09JQ9RETk9HREXYX7CoKA22+/nVmzZjFlypTS81/72tcYP348LS0tvPvuu9xzzz20t7fzzDPPANDR0VEWWkDp546OjmHfa9myZTzwwANHW1URETmFHHVwtba2sm7dOl599dWy52+55ZbSv6dOnUpzczOzZ89m48aNTJo06ajea+nSpdx5552ln3t6ehg7duzRVVxERELtqLoKlyxZwvLly3n55ZcZM2bMQcvOmDEDgA0bNgDQ1NTEjh07ysoM/dzU1DTsOuLxOKlUquwhIiKnpyMKLmstS5Ys4dlnn2XlypVMmDDhkK955513AGhubgZg5syZvPfee+zcubNUZsWKFaRSKSZPnnwk1RERkdPQEXUVtra28uSTT/L8889TXV1dGpNKp9MkEgk2btzIk08+yVVXXcWoUaN49913ueOOO7j88suZNm0aAHPnzmXy5Ml8/etf56GHHqKjo4N7772X1tZW4vH48d9CERE5pRzRdHhjzLDPP/7449x4441s2bKFP//zP2fdunX09/czduxYvvKVr3DvvfeWde999NFH3HbbbaxatYpkMsnixYt58MEHiUQOL0c1HV5EJNyO5Th+TNdxjRQFl4hIuB3LcfyoZxWOpKGs1fVcIiLhNHT8Pppzp1AGV29vL4CmxIuIhFxvby/pdPqIXhPKrsIgCGhvb2fy5Mls2bJF3YXDGLrWTe0zPLXPwal9Dk1tdHCHah9rLb29vbS0tOA4R3ZlVijPuBzH4YwzzgDQdV2HoPY5OLXPwal9Dk1tdHAHa58jPdMaou/jEhGRUFFwiYhIqIQ2uOLxOPfff78uWv4Uap+DU/scnNrn0NRGB3ci2yeUkzNEROT0FdozLhEROT0puEREJFQUXCIiEioKLhERCZVQBtcjjzzCmWeeSUVFBTNmzOCNN94Y6SqNiL/927/FGFP2OO+880rLM5kMra2tjBo1iqqqKq677roDvsTzVPPKK6/wpS99iZaWFowxPPfcc2XLrbXcd999NDc3k0gkmDNnDh9++GFZmc7OThYtWkQqlaKmpoabbrqJvr6+k7gVJ86h2ufGG288YJ+aP39+WZlTtX2WLVvG9OnTqa6upqGhgWuuuYb29vayMofzN7V582YWLlxIZWUlDQ0N3H333XiedzI35YQ5nDa68sorD9iHbr311rIyx9pGoQuun//859x5553cf//9/OY3v+HCCy9k3rx5ZV9MeTq54IIL2L59e+nx6quvlpbdcccdvPDCCzz99NOsXr2abdu2ce21145gbU+8/v5+LrzwQh555JFhlz/00EM8/PDDPPbYY6xZs4ZkMsm8efPIZDKlMosWLWL9+vWsWLGC5cuX88orr3DLLbecrE04oQ7VPgDz588v26eeeuqpsuWnavusXr2a1tZWXn/9dVasWEE+n2fu3Ln09/eXyhzqb8r3fRYuXEgul+O1117jJz/5CU888QT33XffSGzScXc4bQRw8803l+1DDz30UGnZcWkjGzKXXnqpbW1tLf3s+75taWmxy5YtG8FajYz777/fXnjhhcMu6+rqstFo1D799NOl5z744AML2La2tpNUw5EF2Geffbb0cxAEtqmpyf7jP/5j6bmuri4bj8ftU089Za219v3337eAffPNN0tlXnzxRWuMsVu3bj1pdT8Z9m8fa61dvHixvfrqqz/1NadT++zcudMCdvXq1dbaw/ub+uUvf2kdx7EdHR2lMo8++qhNpVI2m82e3A04CfZvI2utveKKK+xf/dVffeprjkcbheqMK5fLsXbtWubMmVN6znEc5syZQ1tb2wjWbOR8+OGHtLS0MHHiRBYtWsTmzZsBWLt2Lfl8vqytzjvvPMaNG3fattWmTZvo6Ogoa5N0Os2MGTNKbdLW1kZNTQ2XXHJJqcycOXNwHIc1a9ac9DqPhFWrVtHQ0MC5557Lbbfdxp49e0rLTqf26e7uBqCurg44vL+ptrY2pk6dSmNjY6nMvHnz6OnpYf369Sex9ifH/m005Kc//Sn19fVMmTKFpUuXMjAwUFp2PNooVDfZ3b17N77vl20wQGNjI7/73e9GqFYjZ8aMGTzxxBOce+65bN++nQceeIAvfOELrFu3jo6ODmKxGDU1NWWvaWxspKOjY2QqPMKGtnu4/WdoWUdHBw0NDWXLI5EIdXV1p0W7zZ8/n2uvvZYJEyawceNGvve977FgwQLa2tpwXfe0aZ8gCLj99tuZNWsWU6ZMATisv6mOjo5h96+hZaeS4doI4Gtf+xrjx4+npaWFd999l3vuuYf29naeeeYZ4Pi0UaiCS8otWLCg9O9p06YxY8YMxo8fzy9+8QsSicQI1kzC6oYbbij9e+rUqUybNo1JkyaxatUqZs+ePYI1O7laW1tZt25d2ZixlPu0Ntp3vHPq1Kk0Nzcze/ZsNm7cyKRJk47Le4eqq7C+vh7XdQ+YxbNjxw6amppGqFZ/PGpqajjnnHPYsGEDTU1N5HI5urq6ysqczm01tN0H23+ampoOmOjjeR6dnZ2nZbtNnDiR+vp6NmzYAJwe7bNkyRKWL1/Oyy+/zJgxY0rPH87fVFNT07D719CyU8WntdFwZsyYAVC2Dx1rG4UquGKxGBdffDEvvfRS6bkgCHjppZeYOXPmCNbsj0NfXx8bN26kubmZiy++mGg0WtZW7e3tbN68+bRtqwkTJtDU1FTWJj09PaxZs6bUJjNnzqSrq4u1a9eWyqxcuZIgCEp/gKeTjz/+mD179tDc3Ayc2u1jrWXJkiU8++yzrFy5kgkTJpQtP5y/qZkzZ/Lee++VhfuKFStIpVJMnjz55GzICXSoNhrOO++8A1C2Dx1zGx3lZJIR87Of/czG43H7xBNP2Pfff9/ecssttqampmyGyunirrvusqtWrbKbNm2yv/71r+2cOXNsfX293blzp7XW2ltvvdWOGzfOrly50r711lt25syZdubMmSNc6xOrt7fXvv322/btt9+2gP3hD39o3377bfvRRx9Za6198MEHbU1NjX3++eftu+++a6+++mo7YcIEOzg4WFrH/Pnz7Wc/+1m7Zs0a++qrr9qzzz7bfvWrXx2pTTquDtY+vb299jvf+Y5ta2uzmzZtsv/xH/9hL7roInv22WfbTCZTWsep2j633XabTafTdtWqVXb79u2lx8DAQKnMof6mPM+zU6ZMsXPnzrXvvPOO/dWvfmVHjx5tly5dOhKbdNwdqo02bNhgv//979u33nrLbtq0yT7//PN24sSJ9vLLLy+t43i0UeiCy1prf/SjH9lx48bZWCxmL730Uvv666+PdJVGxPXXX2+bm5ttLBazZ5xxhr3++uvthg0bSssHBwftt771LVtbW2srKyvtV77yFbt9+/YRrPGJ9/LLL1vggMfixYuttYUp8X/zN39jGxsbbTwet7Nnz7bt7e1l69izZ4/96le/aquqqmwqlbLf/OY3bW9v7whszfF3sPYZGBiwc+fOtaNHj7bRaNSOHz/e3nzzzQd8KDxV22e4dgHs448/XipzOH9Tf/jDH+yCBQtsIpGw9fX19q677rL5fP4kb82Jcag22rx5s7388sttXV2djcfj9qyzzrJ333237e7uLlvPsbaRvtZERERCJVRjXCIiIgouEREJFQWXiIiEioJLRERCRcElIiKhouASEZFQUXCJiEioKLhERCRUFFwiIhIqCi4REQkVBZeIiISKgktERELl/wOFWeoU+nn8zgAAAABJRU5ErkJggg==", 160 | "text/plain": [ 161 | "
" 162 | ] 163 | }, 164 | "metadata": {}, 165 | "output_type": "display_data" 166 | } 167 | ], 168 | "source": [ 169 | "from morecantile.models import TileMatrix, TileMatrixSet, crs_axis_inverted, TMSBoundingBox, CRS_to_uri\n", 170 | "from morecantile.utils import meters_per_unit\n", 171 | "import math\n", 172 | "\n", 173 | "x_chunk_size = 512\n", 174 | "y_chunk_size = 512\n", 175 | "\n", 176 | "dst_height = ds[var].shape[1]\n", 177 | "dst_width = ds[var].shape[2]\n", 178 | "\n", 179 | "decimation_base = 2\n", 180 | "\n", 181 | "pyproj_crs = pyproj.CRS.from_epsg(4326)\n", 182 | "print(f\"crs: {pyproj_crs}\")\n", 183 | "\n", 184 | "bounds = tuple(ds[var].rio.bounds())\n", 185 | "print(f\"bounds: {bounds}\")\n", 186 | "\n", 187 | "is_inverted = crs_axis_inverted(pyproj_crs)\n", 188 | "\n", 189 | "overview_level = get_maximum_overview_level(\n", 190 | " dst_width,\n", 191 | " dst_height,\n", 192 | " minsize=min(x_chunk_size, y_chunk_size),\n", 193 | ")\n", 194 | "mpu = meters_per_unit(pyproj_crs)\n", 195 | "\n", 196 | "# Rendering pixel size. 0.28 mm was the actual pixel size of a common display from 2005 and considered as standard by OGC.\n", 197 | "screen_pixel_size = 0.28e-3\n", 198 | "\n", 199 | "x_origin = bounds[0] if not is_inverted else bounds[3]\n", 200 | "y_origin = bounds[3] if not is_inverted else bounds[0]\n", 201 | "\n", 202 | "width = abs(bounds[2] - bounds[0])\n", 203 | "height = abs(bounds[3] - bounds[1])\n", 204 | "res = max(width / dst_width, height / dst_height)\n", 205 | "\n", 206 | "matrices = [\n", 207 | " TileMatrix(\n", 208 | " description=\"TileMatrix for the high resolution data\",\n", 209 | " id=str(overview_level),\n", 210 | " scaleDenominator=res * mpu / screen_pixel_size,\n", 211 | " cellSize=res,\n", 212 | " pointOfOrigin=[x_origin, y_origin],\n", 213 | " tileWidth=x_chunk_size,\n", 214 | " tileHeight=y_chunk_size,\n", 215 | " matrixWidth=math.ceil(dst_width / x_chunk_size),\n", 216 | " matrixHeight=math.ceil(dst_height / y_chunk_size),\n", 217 | " )\n", 218 | "]\n", 219 | "\n", 220 | "for ovr in range(1, overview_level + 1):\n", 221 | " decimation = decimation_base ** ovr\n", 222 | " new_res = res * decimation\n", 223 | " matrices.append(\n", 224 | " TileMatrix(\n", 225 | " description=f\"TileMatrix for overview {ovr}\",\n", 226 | " id=str(overview_level - ovr),\n", 227 | " scaleDenominator=new_res * mpu / screen_pixel_size,\n", 228 | " cellSize=new_res,\n", 229 | " pointOfOrigin=[x_origin, y_origin],\n", 230 | " tileWidth=x_chunk_size,\n", 231 | " tileHeight=y_chunk_size,\n", 232 | " matrixWidth=math.ceil(dst_width / decimation / x_chunk_size),\n", 233 | " matrixHeight=math.ceil(dst_height / decimation / y_chunk_size),\n", 234 | " )\n", 235 | " )\n", 236 | "\n", 237 | "matrices = reversed(matrices)\n", 238 | "\n", 239 | "custom_tms = TileMatrixSet(\n", 240 | " id=\"custom_tms\",\n", 241 | " title=f\"Custom TMS for {var} variable\",\n", 242 | " tileMatrices=matrices,\n", 243 | " orderedAxes=[\"Lat\", \"Lon\"],\n", 244 | " crs=\"http://www.opengis.net/def/crs/EPSG/0/4326\",\n", 245 | ")\n", 246 | "\n", 247 | "with XarrayReader(ds[var], tms=custom_tms) as dst:\n", 248 | " tile = dst.tile(0, 0, 0).data\n", 249 | "\n", 250 | "plt.imshow(tile.squeeze())" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [] 259 | } 260 | ], 261 | "metadata": { 262 | "kernelspec": { 263 | "display_name": "test", 264 | "language": "python", 265 | "name": "python3" 266 | }, 267 | "language_info": { 268 | "codemirror_mode": { 269 | "name": "ipython", 270 | "version": 3 271 | }, 272 | "file_extension": ".py", 273 | "mimetype": "text/x-python", 274 | "name": "python", 275 | "nbconvert_exporter": "python", 276 | "pygments_lexer": "ipython3", 277 | "version": "3.12.3" 278 | } 279 | }, 280 | "nbformat": 4, 281 | "nbformat_minor": 2 282 | } 283 | -------------------------------------------------------------------------------- /docs/index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "GeoZarr examples" 3 | --- 4 | 5 | ## Overview 6 | 7 | [GeoZarr examples](https://github.com/developmentseed/geozarr-examples) contains in-progress work towards GeoZarr examples. If useful, the contents will eventually be migrated to a different repository, such as the 8 | [Cloud Optimized Geospatial Formats Guide](https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide) or the [GeoZarr spec](https://github.com/zarr-developers/geozarr-spec) 9 | repository. 10 | 11 | ## Goals 12 | 13 | - Demonstrate how to write GeoZarr compliant data. 14 | - Provide a demonstration of writing only the MUST include metadata (most importantly grid_mapping). 15 | - Provide a demonstration of writing data with a WebMercatorQuad TMS. 16 | - Provide a demonstration of writing data with a Custom TMS that maps to simple downsampled version of the raw data, without any change in extent or CRS. 17 | - Provide a demonstration of storing raw data in NetCDF and overviews in native Zarr, with a virtual GeoZarr compliant entrypoint. 18 | - Demonstrate how to read GeoZarr compliant data. 19 | - Provide a demonstration of reading in GeoZarr data with only MUST include metadata. 20 | - Provide a demonstration of reading in GeoZarr data with raw data and overviews in "native" zarr. 21 | - Provide a demonstration of reading in GeoZarr data with raw data in archival formats and overviews in "native" zarr via a single virtual GeoZarr compliant entrypoint. 22 | - Demonstrate how to work with GeoZarr data in Xarray using the prototypes for flexible coordinates and the xproj extension. 23 | - Demonstrate whether the GeoZarr v0.4 and flexible coordinates solve the limitations highlighted in https://discourse.pangeo.io/t/example-which-highlights-the-limitations-of-netcdf-style-coordinates-for-large-geospatial-rasters/4140. 24 | - Demonstrate how GeoZarr would need to be adapted for Zarr specification v3. 25 | 26 | ## Feedback cadence 27 | 28 | We will provide progress and solicit community feedback during the following events: 29 | 30 | - March 05, 2025 GeoZarr Monthly Community Meeting 31 | - March 05, 2025 Pangeo Community Meeting 32 | - April 02, 2025 GeoZarr Monthly Community Meeting 33 | - April 02, 2025 Pangeo Community Meeting 34 | - April 02, 2025 posts on Pangeo and CNG forums 35 | - EGU 2025 Conference 36 | - CNG 2025 Conference 37 | 38 | ## FAQ 39 | 40 | ### What's the status of GeoZarr? 41 | 42 | GeoZarr is currently being developed as a [Open Geospatial Consortium Standard](https://www.ogc.org/announcement/ogc-forms-new-geozarr-standards-working-group-to-establish-a-zarr-encoding-for-geospatial-data/). 43 | There is a [GeoZarr Standards Working Group](https://portal.ogc.org/index.php?m=public&orderby=default&tab=7) that meets once a month. We now have 44 | experimental prototypes of [all](https://github.com/pydata/xarray/pull/9543) [of](https://github.com/zarr-developers/VirtualiZarr/pull/271) [the](https://xproj.readthedocs.io/en/latest/usage.html) 45 | [pieces](https://zarr.dev/blog/zarr-python-3-release/) to move GeoZarr out of a discussion phase and into a demonstration phase. This repository stems from the hope that building demonstrations will lead to 46 | adoption, iteration, and formalization of a stable GeoZarr specification. 47 | 48 | ### How does Zarr Specification V3 spec influence GeoZarr? 49 | 50 | The GeoZarr spec is designed for Zarr specification version 2. This repository will demonstrate how the differences between Zarr format 2 and Zarr format 3 would 51 | influence GeoZarr. My expectation is that there is not much difference in the metadata requirements between the two formats. However, best practices will likely be impacted by new features available 52 | in Zarr specification version 3 (e.g., sharding). 53 | 54 | ### How does the release of Zarr-Python 3 influence GeoZarr? 55 | 56 | The Zarr-Python 3 release will help GeoZarr users through its increased performance, modernized codebase, and support for extensions. But, it only really interacts with the GeoZarr spec in-so-far 57 | as Zarr-Python 3 supports Zarr specification V3 (see prior question on "How does Zarr Specification V3 spec influence GeoZarr"). 58 | 59 | ### What is the relationship between GeoZarr and Web-Optimized-Zarr? 60 | 61 | Please see the [web-optimized Zarr overview page](web-optimized-zarr.qmd). 62 | 63 | ## References/Acknowledgements 64 | 65 | - [GeoZarr validator](https://github.com/briannapagan/geozarr-validator) by [@briannapagan](https://github.com/briannapagan) [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/) 66 | - [GeoZarr spec](https://github.com/zarr-developers/geozarr-spec) [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/) 67 | - Quarto configuration based on [Cloud Native Geospatial Formats Guide](https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide) and [Tile Benchmarking](https://developmentseed.org/tile-benchmarking/). 68 | 69 | ## License 70 | 71 | Content in this repository is licensed under the [MIT License](LICENSE.txt). -------------------------------------------------------------------------------- /docs/slides/2025-02.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: GeoZarr 3 | subtitle: February 2025 (subjective) status report 4 | footer: "Thanks: Brianna Pagán, Joe Hamman, GeoZarr WG, Zarr refactor WG" 5 | format: 6 | revealjs: 7 | incremental: false 8 | auto-stretch: false 9 | theme: solarized 10 | width: 1600 11 | height: 900 12 | slideNumber: true 13 | --- 14 | 15 | ## Goals {footer=false} 16 | 17 | - Reinvigorate Building It Out! 18 | - 🙅‍♂️💭 All benefits from discussion are exhausted 19 | - 🚀 GeoZarr can be successful if we act now 20 | - 💔 GeoZarr can fail if we don't 21 | 22 | ::: footer 23 | ::: 24 | 25 | ## What is Zarr? {footer=false} 26 | 27 | An community-developed, open-source format for the storage of chunked, compressed, N-dimensional arrays 28 | 29 | - Cloud optimized (concurrent read/writes, "infinitely" scalable) 30 | - Cross-disciplinary (originally developed for bio-imaging) 31 | - *Extremely* extensible (supports any key-value store) 32 | 33 | ::: footer 34 | Find out more at [https://zarr.dev](https://zarr.dev) 35 | ::: 36 | 37 | ## What's going on today? 38 | 39 | - Zarr is growing in popularity 40 | - Icechunk is improving consistency and performance 41 | - VirtualiZarr is simplifying virtualization 42 | - Progressing towards Zarr specification V3 across languages 43 | 44 | ::: footer 45 | Read more about [Zarr-Python](https://zarr.readthedocs.io/en/stable/index.html), [Icechunk](https://icechunk.io/en/latest/) (from Earthmover), and [VirtualiZarr](https://virtualizarr.readthedocs.io/en/stable/index.html) (community developed, created by Tom Nicholas). 46 | ::: 47 | 48 | ## Zarr implementation landscape 49 | 50 | - Zarr Python 3.0 [released in January](https://zarr.dev/blog/zarr-python-3-release/) 51 | - Even Rouault is updating [GDAL's Zarr driver](https://gdal.org/en/stable/drivers/raster/zarr.html) 52 | - Lachlan Deakin is [updating the Zarrs Rust library](https://github.com/LDeakin/zarrs) 53 | - OpenScapes is coordinating R support (will rely on GDAL driver) 54 | - Unidata has build [Zarr support into the NetCDF C library](https://docs.unidata.ucar.edu/nug/current/nczarr_head.html) 55 | - Trevor Manz was (is?) trying to consolidate the JavaScript implementations 56 | 57 | ::: footer 58 | Zarr is [independently implemented across languages](https://zarr.dev/implementations/) 59 | ::: 60 | 61 | ::: footer 62 | ::: 63 | 64 | ## What is GeoZarr? 65 | 66 | A geospatial extension for the Zarr specification, formalizing metadata expectations. 67 | 68 | ![](_static/geozarr-repo.png){fig-alt="GeoZarr repository" fig-align="center" width="60%"} 69 | 70 | ::: footer 71 | Read [the spec](https://github.com/zarr-developers/geozarr-spec/)! 72 | ::: 73 | 74 | ## Some history 75 | 76 | ![](_static/geozarr-activity.png){fig-alt="GeoZarr commit activity" fig-align="center" width="95%"} 77 | 78 | ::: footer 79 | ::: 80 | 81 | ## Why is this an exciting time? 82 | 83 | Multi-dimensional geospatial solutions can bring real impact across humanitarian, scientific, and industrial needs 84 | 85 | ::: footer 86 | ::: 87 | 88 | ## Why is this an exciting time? 89 | 90 | Experimental CRS handling across the Python ecosystem 91 | 92 | ![](_static/xproj-docs.png){fig-alt="XProj repository" fig-align="center" width="60%"} 93 | 94 | ::: footer 95 | Read more in the [xproj docs](https://xproj.readthedocs.io/en/latest/index.html) 96 | ::: 97 | 98 | 99 | ## Why is this an exciting time? 100 | 101 | Generic support for analytic/functional coordinates in Xarray 102 | 103 | ![](_static/flexible-coordinates.png){fig-alt="Flexible coordinates PR" fig-align="center" width="60%"} 104 | 105 | ::: footer 106 | Check out the [pull request](https://github.com/pydata/xarray/pull/9543) 107 | ::: 108 | 109 | ## Why is this an exciting time? 110 | 111 | Draft ZEP for breaking the stalemate on building Zarr extensions 112 | 113 | ![](_static/zep9.png){fig-alt="Zarr Extension Naming ZEP" fig-align="center" width="60%"} 114 | 115 | ::: footer 116 | Check out the [draft ZEP](https://zarr.dev/zeps/draft/ZEP0009.html) 117 | ::: 118 | 119 | 120 | ## Why is this an exciting time? 121 | 122 | We have a deadline! 123 | 124 | ![](_static/CNG-conference.png){fig-alt="Cloud Native Geospatial Conference Home Page" fig-align="center" width="60%"} 125 | 126 | ::: footer 127 | Check out the [conference](https://2025-ut.cloudnativegeo.org/) 128 | ::: 129 | 130 | ## What do we need? 131 | 132 | To prove whether OGC TMS 2.0 is sufficient for performant and reliable rendering and analysis 133 | 134 | ![](_static/OGC-TMS.png){fig-alt="OGC TMS 2.0 Standard" fig-align="center" width="60%"} 135 | 136 | ::: footer 137 | Check out the [standard](https://www.ogc.org/standards/tms) 138 | 139 | ::: 140 | 141 | ## What do we need? 142 | 143 | To push forward Xarray functional coordinates for geospatial 144 | 145 | ![](_static/Pangeo-NetCDF-limitations.png){fig-alt="Pangeo issue on )NetCDF limitations" fig-align="center" width="60%"} 146 | 147 | ::: footer 148 | Check out the [Discourse post](https://discourse.pangeo.io/t/example-which-highlights-the-limitations-of-netcdf-style-coordinates-for-large-geospatial-rasters/4140) 149 | ::: 150 | 151 | 152 | 153 | ## What do we need? 154 | 155 | To demonstrate how XPublish and Titiler's Xarray extension fit together (or not) 156 | 157 | :::: {.columns} 158 | 159 | ::: {.column width="50%"} 160 | ![](_static/xpublish-community.png){fig-alt="XPublish on GitHub" fig-align="center" width="80%"} 161 | ::: 162 | 163 | 164 | ::: {.column width="50%"} 165 | ![](_static/titiler-multidim-repo.png){fig-alt="Titiler-multidim on GitHub" fig-align="center" width="80%"} 166 | ::: 167 | 168 | :::: 169 | 170 | ::: footer 171 | Check out [XPublish](https://github.com/xpublish-community/xpublish) and [Titiler-Xarray](https://github.com/developmentseed/titiler/tree/main/src/titiler/xarray) 172 | ::: 173 | 174 | ## What do we need? 175 | 176 | To show how to get the most out of Zarr and STAC 177 | 178 | ![](_static/zarr-stac-issue.png){fig-alt="Issue on Zarr + STAC best practices" fig-align="center" width="60%"} 179 | 180 | ::: footer 181 | Help us [tackle this issue](https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/issues/134) 182 | ::: 183 | 184 | ## Let's get going! 🚀 185 | 186 | ::: footer 187 | Build code in [GeoZarr examples](https://github.com/developmentseed/geozarr-examples)! 188 | ::: 189 | -------------------------------------------------------------------------------- /docs/slides/2025-04.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: GeoZarr 3 | subtitle: STAC and Zarr Workshop Update (April 2025) 4 | footer: "Thanks: Brianna Pagán, Christophe Noël, GeoZarr SWG, Zarr refactor WG" 5 | format: 6 | revealjs: 7 | incremental: false 8 | auto-stretch: false 9 | theme: solarized 10 | width: 1600 11 | height: 900 12 | slideNumber: true 13 | pdf-max-pages-per-slide: 1 14 | --- 15 | 16 | ## Goals {footer=false} 17 | 18 | - 📍 Provide a status update on the GeoZarr specification 19 | - 🧭 Provide entry-points for contributing to GeoZarr development 20 | 21 | ::: footer 22 | ::: 23 | 24 | ## What is GeoZarr? 25 | 26 | A geospatial extension for the Zarr specification, formalizing metadata expectations. 27 | 28 | ![](_static/geozarr-repo.png){fig-alt="GeoZarr repository" fig-align="center" width="60%"} 29 | 30 | ::: footer 31 | Read [the spec](https://github.com/zarr-developers/geozarr-spec/)! 32 | ::: 33 | 34 | ## Some history 35 | 36 | ![](_static/geozarr-activity.png){fig-alt="GeoZarr commit activity" fig-align="center" width="95%"} 37 | 38 | ::: footer 39 | Figure generated February 2025 40 | ::: 41 | 42 | ## Recent activity 43 | 44 | Christophe Noël proposes structuring GeoZarr around the Unified Abstract Data Model 45 | 46 | ![](_static/CDM-issue.png){fig-alt="Unified data model presentation" fig-align="center" width="60%"} 47 | 48 | ::: footer 49 | See [the GitHub issue](https://github.com/zarr-developers/geozarr-spec/issues/63)! 50 | ::: 51 | 52 | ## (Very) recent activity 53 | 54 | Christophe Noël structures the spec based on the Unified Data Model and Conformance Classes 55 | 56 | ![](_static/CDM-PR.png){fig-alt="Unified data model proposal" fig-align="center" width="60%"} 57 | 58 | 59 | ::: footer 60 | See [the GitHub pull request](https://github.com/zarr-developers/geozarr-spec/pull/64)! 61 | ::: 62 | 63 | ## Background 64 | 65 | Unidata's Common Data Model is an abstract data model that merges the NetCDF, OPenDAP, and HDF5 data models to create a common API for many types of scientific data. 66 | 67 | ![](_static/CDM.png){fig-alt="Common data model" fig-align="center" width="60%"} 68 | 69 | ::: footer 70 | Based on [Christophe Noël's ideas](https://github.com/zarr-developers/geozarr-spec/pull/64) 71 | ::: 72 | 73 | ## Unified Abstract Data Model 74 | 75 | A Unified Abstract Data Model would encapsulate traditional models from the modelling (NetCDF, CF) plus models from the earth observation (GeoTIFF, GDAL) communities 76 | 77 | ![](_static/UADM.png){fig-alt="Unified data model proposal" fig-align="center" width="60%"} 78 | 79 | ::: footer 80 | Based on [Christophe Noël's ideas](https://github.com/zarr-developers/geozarr-spec/pull/64) 81 | ::: 82 | 83 | ## N-D functionality with E-O optimizations 84 | 85 | ![](_static/UADM_example.png){fig-alt="Unified data model proposal" fig-align="center" width="60%"} 86 | 87 | ::: footer 88 | ::: 89 | 90 | ## Unified Abstract Data Model Implementation 91 | 92 | The VirtualiZarr library could be an implementation of the Unified Abstract Data Model 93 | 94 | ![](_static/virtualizarr.png){fig-alt="Virtualizarr diagram" fig-align="center" width="60%"} 95 | 96 | ::: footer 97 | Check out VirtualiZarr on [GitHub](https://github.com/zarr-developers/VirtualiZarr) 98 | ::: 99 | 100 | ## Upcoming activity 101 | 102 | Discussion about conformance classes April 17 103 | 104 | ![](_static/conformance-class-meeting.png){fig-alt="Calendar event" fig-align="center" width="60%"} 105 | 106 | ::: footer 107 | Copy event from the [Pangeo calendar](https://pangeo.io/calendar) 108 | ::: 109 | 110 | ## Upcoming activity 111 | 112 | Monthly GeoZarr meeting on May 7 113 | 114 | ![](_static/recurring-meeting.png){fig-alt="Calendar event" fig-align="center" width="60%"} 115 | 116 | ::: footer 117 | Copy event from the [Pangeo calendar](https://pangeo.io/calendar) 118 | ::: 119 | 120 | ## Upcoming activity 121 | 122 | Cloud Native Geospatial conference including a STAC + Zarr presentation by Julia Signell 123 | 124 | ![](_static/CNG-conference.png){fig-alt="Calendar event" fig-align="center" width="60%"} 125 | 126 | ::: footer 127 | [Register online](https://conference.cloudnativegeo.org/CNGConference2025#/?lang=en) 128 | ::: 129 | 130 | ## Upcoming activity 131 | 132 | EGU General Assembly including a EOPF Zarr discussion at the Pangeo session 133 | 134 | ![](_static/EGU.png){fig-alt="Calendar event" fig-align="center" width="60%"} 135 | 136 | ::: footer 137 | [Attend the session](https://meetingorganizer.copernicus.org/EGU25/session/52079) 138 | ::: 139 | 140 | ## Shameless plug 141 | 142 | Pangeo showcase in 3 hours will include a presentation about new Zarr optimizations! 143 | 144 | ![](_static/pangeo-showcase.png){fig-alt="Calendar event" fig-align="center" width="60%"} 145 | 146 | ::: footer 147 | [Attend the showcase](https://discourse.pangeo.io/t/spring-showcase-close-out-lightning-talks-april-16-2025-at-12-pm-et/5047) 148 | ::: 149 | 150 | ## Shameless plug 151 | 152 | Pangeo showcase in 3 hours will include a presentation about new Zarr optimizations! 153 | 154 | ![](_static/zarr-obstore.png){fig-alt="Performance difference" fig-align="center" width="60%"} 155 | 156 | ::: footer 157 | [Attend the showcase](https://discourse.pangeo.io/t/spring-showcase-close-out-lightning-talks-april-16-2025-at-12-pm-et/5047) 158 | ::: 159 | ## Thanks! 🚀 160 | 161 | ![](_static/geozarr-examples-request.png){fig-alt="GitHub source" fig-align="center" width="60%"} 162 | 163 | ::: footer 164 | [Slides](https://developmentseed.org/geozarr-examples/slides/2025-04.html#/title-slide) and [source](https://github.com/developmentseed/geozarr-examples) available online 165 | ::: 166 | -------------------------------------------------------------------------------- /docs/slides/_static/CDM-PR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/CDM-PR.png -------------------------------------------------------------------------------- /docs/slides/_static/CDM-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/CDM-issue.png -------------------------------------------------------------------------------- /docs/slides/_static/CDM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/CDM.png -------------------------------------------------------------------------------- /docs/slides/_static/CNG-conference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/CNG-conference.png -------------------------------------------------------------------------------- /docs/slides/_static/EGU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/EGU.png -------------------------------------------------------------------------------- /docs/slides/_static/OGC-TMS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/OGC-TMS.png -------------------------------------------------------------------------------- /docs/slides/_static/Pangeo-NetCDF-limitations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/Pangeo-NetCDF-limitations.png -------------------------------------------------------------------------------- /docs/slides/_static/UADM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/UADM.png -------------------------------------------------------------------------------- /docs/slides/_static/UADM_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/UADM_example.png -------------------------------------------------------------------------------- /docs/slides/_static/conformance-class-meeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/conformance-class-meeting.png -------------------------------------------------------------------------------- /docs/slides/_static/flexible-coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/flexible-coordinates.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/geozarr-activity.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-examples-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/geozarr-examples-repo.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-examples-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/geozarr-examples-request.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/geozarr-repo.png -------------------------------------------------------------------------------- /docs/slides/_static/pangeo-showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/pangeo-showcase.png -------------------------------------------------------------------------------- /docs/slides/_static/recurring-meeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/recurring-meeting.png -------------------------------------------------------------------------------- /docs/slides/_static/titiler-multidim-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/titiler-multidim-repo.png -------------------------------------------------------------------------------- /docs/slides/_static/virtualizarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/virtualizarr.png -------------------------------------------------------------------------------- /docs/slides/_static/xproj-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/xproj-docs.png -------------------------------------------------------------------------------- /docs/slides/_static/xpublish-community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/xpublish-community.png -------------------------------------------------------------------------------- /docs/slides/_static/zarr-obstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/zarr-obstore.png -------------------------------------------------------------------------------- /docs/slides/_static/zarr-stac-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/zarr-stac-issue.png -------------------------------------------------------------------------------- /docs/slides/_static/zep9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/docs/slides/_static/zep9.png -------------------------------------------------------------------------------- /docs/spec-diagram.qmd: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/styles.css: -------------------------------------------------------------------------------- 1 | /* css styles */ 2 | 3 | .sidebar-logo { 4 | max-width: 150px; 5 | } 6 | 7 | .sidebar-title { 8 | font-size: 1.7rem; 9 | } 10 | 11 | .platform-table td { 12 | vertical-align: middle; 13 | } 14 | 15 | .platform-table td > div.sourceCode { 16 | margin-top: 0.3rem; 17 | margin-bottom: 0.3rem; 18 | } 19 | -------------------------------------------------------------------------------- /docs/theme-dark.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | 3 | @import "https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible&display=swap"; 4 | 5 | $font-family: "Atkinson Hyperlegible", sans-serif; 6 | 7 | // Base document colors 8 | $body-bg: #181818; 9 | $body-color: white; 10 | $link-color: #75aadb; 11 | 12 | $light: #525252; 13 | 14 | // Navigation element colors 15 | $footer-bg: #181818; 16 | $navbar-bg: #303030; 17 | $sidebar-bg: #303030; 18 | 19 | // Code blocks 20 | $code-block-bg-alpha: -0.8; 21 | 22 | // Bootstrap popovers 23 | $popover-bg: #242424; 24 | 25 | // Bootstrap inputs 26 | $input-bg: #242424; 27 | 28 | /*-- scss:rules --*/ 29 | 30 | .layout-example { 31 | background: $gray-700; 32 | } 33 | -------------------------------------------------------------------------------- /docs/web-optimized-zarr.qmd: -------------------------------------------------------------------------------- 1 | # Web-optimized Zarr 2 | 3 | Web-optimized zarr provides a set of additional recommendations on top of the GeoZarr specification for optimal browser-based analysis and visualization. The specific recommendations are still 4 | under development. We anticipate the following to be included as web-optimized Zarr recommendations: 5 | 6 | 1. The WOZ MUST be chunked in the spatial and/or temporal dimensions. The WOZ guide will include a reference to a separate document for recommended chunking schemes and compression algorithms for different use-cases, which will be updated as browsers and infrastructures change. 7 | 2. The dimension order for 2-dimensional data MUST be (y, x) (or the equivalent spatial dimension names) for maximum interoperability. 8 | 3. The dimension order for 3-dimensional data MUST be (time, y, x) (or the equivalent spatial dimension names) for maximum interoperability. 9 | 4. The WOZ MUST include multi-scales. 10 | 5. The WOZ may contain full-resolution "archival" versions in other file formats and reduced resolution versions in "native" zarr. 11 | 12 | The following criteria may be included after further evaluation: 13 | 14 | 1. The multi-scales must align with a well-known TMS. 15 | 2. The WOZ should include sharding to allow clients to request smaller individual chunks or larger shards. 16 | 3. The multi-scales should contain rendering metadata (e.g., min and max values and preferred color mapping representations). 17 | 18 | These recommendations will be expanded or updated after additional experimentation. 19 | 20 | For a graphical depiction of how WOZ compares to GeoZarr, please see [the excalidraw diagram](woz-diagram.qmd). 21 | -------------------------------------------------------------------------------- /docs/woz-diagram.qmd: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "hatch-vcs"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "geozarr-examples" 7 | description = "Simple GeoZarr examples" 8 | authors = [ 9 | {name = "Max Jones", email = "14077947+maxrjones@users.noreply.github.com"} 10 | ] 11 | requires-python = ">=3.11" 12 | dependencies = [ 13 | "zarr", 14 | ] 15 | dynamic = [ 16 | "version", 17 | ] 18 | classifiers = [ 19 | 'Development Status :: 1 - Planning', 20 | 'Intended Audience :: Developers', 21 | 'Intended Audience :: Information Technology', 22 | 'Intended Audience :: Science/Research', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Programming Language :: Python', 25 | 'Topic :: Software Development :: Libraries :: Python Modules', 26 | 'Operating System :: Unix', 27 | 'Programming Language :: Python :: 3', 28 | 'Programming Language :: Python :: 3.11', 29 | 'Programming Language :: Python :: 3.12', 30 | 'Programming Language :: Python :: 3.13', 31 | ] 32 | license = {text = "MIT License"} 33 | keywords = ["Python", "compressed", "ndimensional-arrays", "zarr"] 34 | 35 | [tool.hatch] 36 | version.source = "vcs" 37 | build.hooks.vcs.version-file = "src/geozarr_examples/_version.py" 38 | 39 | [tool.pixi.pypi-dependencies] 40 | geozarr_examples = { path = ".", editable = true } 41 | xarray = { git = "https://github.com/pydata/xarray.git" } 42 | xproj = { git = "https://github.com/benbovy/xproj.git", branch = "main" } 43 | rioxarray = { git = "https://github.com/corteva/rioxarray.git" } 44 | earthaccess = { git = "https://github.com/nsidc/earthaccess" } 45 | rio-cogeo = "*" 46 | cf_xarray = "*" 47 | rio-tiler = "*" 48 | 49 | 50 | [tool.pixi.project] 51 | channels = ["conda-forge"] 52 | platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] 53 | 54 | [tool.pixi.dependencies] 55 | proj = ">=9.5.1" 56 | cftime = ">=1.6.4,<2" 57 | dask = ">=2025.2.0,<2026" 58 | 59 | [tool.pixi.environments] 60 | default = { solve-group = "default" } 61 | test = { features = ["test", "io", "analysis"], solve-group = "default" } 62 | 63 | [tool.pixi.feature.io.dependencies] 64 | netcdf4 = "*" 65 | pooch = ">=1.8.2,<2" 66 | rasterio = ">=1.4.3,<2" 67 | libgdal-hdf5 = "*" 68 | 69 | [tool.pixi.feature.analysis.dependencies] 70 | jupyter = "*" 71 | panel = "*" 72 | matplotlib = "*" 73 | cartopy = "*" 74 | hvplot = "*" 75 | rich = "*" 76 | jupytext = "*" 77 | jupyter_bokeh = "*" 78 | 79 | [tool.pixi.feature.analysis.pypi-dependencies] 80 | geoviews = "*" 81 | 82 | [dependency-groups] 83 | test = ["pytest"] 84 | -------------------------------------------------------------------------------- /src/geozarr_examples/__init__.py: -------------------------------------------------------------------------------- 1 | from geozarr_examples._version import version as __version__ 2 | 3 | __all__ = [ 4 | "__version__", 5 | ] 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/319b1df73d6e5973de6bc7abef3aaab73d485c23/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | import geozarr_examples 2 | 3 | 4 | def test_version(): 5 | assert geozarr_examples.__version__ 6 | --------------------------------------------------------------------------------