├── tests ├── __init__.py └── test_version.py ├── docs ├── .gitignore ├── slides │ ├── _static │ │ ├── CDM.png │ │ ├── EGU.png │ │ ├── CDM-PR.png │ │ ├── UADM.png │ │ ├── zep9.png │ │ ├── OGC-TMS.png │ │ ├── CDM-issue.png │ │ ├── xproj-docs.png │ │ ├── UADM_example.png │ │ ├── geozarr-repo.png │ │ ├── virtualizarr.png │ │ ├── zarr-obstore.png │ │ ├── CNG-conference.png │ │ ├── geozarr-activity.png │ │ ├── pangeo-showcase.png │ │ ├── zarr-stac-issue.png │ │ ├── recurring-meeting.png │ │ ├── xpublish-community.png │ │ ├── flexible-coordinates.png │ │ ├── geozarr-examples-repo.png │ │ ├── titiler-multidim-repo.png │ │ ├── Pangeo-NetCDF-limitations.png │ │ ├── conformance-class-meeting.png │ │ └── geozarr-examples-request.png │ ├── 2025-04.qmd │ └── 2025-02.qmd ├── spec-diagram.qmd ├── woz-diagram.qmd ├── styles.css ├── theme-dark.scss ├── custom.scss ├── web-optimized-zarr.qmd ├── _quarto.yml ├── examples │ ├── 00_download_data.ipynb │ ├── 05_WIP_multiscales_as_customTMS_ZarrV2.ipynb │ └── 01_CRS_in_auxiliary_variable.ipynb └── index.qmd ├── src └── geozarr_examples │ └── __init__.py ├── README.md ├── .github └── workflows │ └── quarto-publish.yaml ├── LICENSE.txt ├── .pre-commit-config.yaml ├── .gitignore └── pyproject.toml /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /.quarto/ 2 | -------------------------------------------------------------------------------- /docs/slides/_static/CDM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/CDM.png -------------------------------------------------------------------------------- /docs/slides/_static/EGU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/EGU.png -------------------------------------------------------------------------------- /docs/slides/_static/CDM-PR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/CDM-PR.png -------------------------------------------------------------------------------- /docs/slides/_static/UADM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/UADM.png -------------------------------------------------------------------------------- /docs/slides/_static/zep9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/zep9.png -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | import geozarr_examples 2 | 3 | 4 | def test_version(): 5 | assert geozarr_examples.__version__ 6 | -------------------------------------------------------------------------------- /docs/slides/_static/OGC-TMS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/OGC-TMS.png -------------------------------------------------------------------------------- /docs/slides/_static/CDM-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/CDM-issue.png -------------------------------------------------------------------------------- /docs/slides/_static/xproj-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/xproj-docs.png -------------------------------------------------------------------------------- /docs/slides/_static/UADM_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/UADM_example.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/geozarr-repo.png -------------------------------------------------------------------------------- /docs/slides/_static/virtualizarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/virtualizarr.png -------------------------------------------------------------------------------- /docs/slides/_static/zarr-obstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/zarr-obstore.png -------------------------------------------------------------------------------- /docs/slides/_static/CNG-conference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/CNG-conference.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/geozarr-activity.png -------------------------------------------------------------------------------- /docs/slides/_static/pangeo-showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/pangeo-showcase.png -------------------------------------------------------------------------------- /docs/slides/_static/zarr-stac-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/zarr-stac-issue.png -------------------------------------------------------------------------------- /src/geozarr_examples/__init__.py: -------------------------------------------------------------------------------- 1 | from geozarr_examples._version import version as __version__ 2 | 3 | __all__ = [ 4 | "__version__", 5 | ] 6 | -------------------------------------------------------------------------------- /docs/slides/_static/recurring-meeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/recurring-meeting.png -------------------------------------------------------------------------------- /docs/slides/_static/xpublish-community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/xpublish-community.png -------------------------------------------------------------------------------- /docs/slides/_static/flexible-coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/flexible-coordinates.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-examples-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/geozarr-examples-repo.png -------------------------------------------------------------------------------- /docs/slides/_static/titiler-multidim-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/titiler-multidim-repo.png -------------------------------------------------------------------------------- /docs/spec-diagram.qmd: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/woz-diagram.qmd: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/slides/_static/Pangeo-NetCDF-limitations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/Pangeo-NetCDF-limitations.png -------------------------------------------------------------------------------- /docs/slides/_static/conformance-class-meeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/conformance-class-meeting.png -------------------------------------------------------------------------------- /docs/slides/_static/geozarr-examples-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/geozarr-examples/HEAD/docs/slides/_static/geozarr-examples-request.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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|.*\.ipynb)$' 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | - href: examples/06_embedded_STAC_block.ipynb 45 | text: STAC metadata 46 | 47 | format: 48 | 49 | html: 50 | theme: 51 | light: [cosmo, theme.scss] # from https://github.com/sta210-s22/website/blob/main/_quarto.yml 52 | dark: [cosmo, theme-dark.scss] 53 | code-copy: true 54 | code-overflow: wrap 55 | css: styles.css 56 | filters: 57 | - quarto 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /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>=3.0.8", 14 | "pystac-client", 15 | "xarray", 16 | "xproj", 17 | "rioxarray", 18 | "earthaccess", 19 | "rio-cogeo", 20 | "rio-tiler", 21 | "cf-xarray", 22 | "cftime", 23 | "dask", 24 | ] 25 | dynamic = [ 26 | "version", 27 | ] 28 | classifiers = [ 29 | 'Development Status :: 1 - Planning', 30 | 'Intended Audience :: Developers', 31 | 'Intended Audience :: Information Technology', 32 | 'Intended Audience :: Science/Research', 33 | 'License :: OSI Approved :: MIT License', 34 | 'Programming Language :: Python', 35 | 'Topic :: Software Development :: Libraries :: Python Modules', 36 | 'Operating System :: Unix', 37 | 'Programming Language :: Python :: 3', 38 | 'Programming Language :: Python :: 3.11', 39 | 'Programming Language :: Python :: 3.12', 40 | 'Programming Language :: Python :: 3.13', 41 | ] 42 | license = {text = "MIT License"} 43 | keywords = ["Python", "compressed", "ndimensional-arrays", "zarr"] 44 | 45 | [tool.hatch] 46 | version.source = "vcs" 47 | build.hooks.vcs.version-file = "src/geozarr_examples/_version.py" 48 | 49 | [tool.pixi.pypi-dependencies] 50 | geozarr_examples = { path = ".", editable = true } 51 | 52 | [tool.pixi.project] 53 | channels = ["conda-forge"] 54 | platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] 55 | 56 | [tool.pixi.environments] 57 | default = { solve-group = "default" } 58 | test = { features = ["test", "io", "analysis"], solve-group = "default" } 59 | 60 | [tool.pixi.feature.io.dependencies] 61 | netcdf4 = "*" 62 | pooch = ">=1.8.2,<2" 63 | rasterio = ">=1.4.3,<2" 64 | libgdal-hdf5 = "*" 65 | 66 | [tool.pixi.feature.analysis.dependencies] 67 | jupyter = "*" 68 | panel = "*" 69 | matplotlib = "*" 70 | cartopy = "*" 71 | hvplot = "*" 72 | rich = "*" 73 | jupytext = "*" 74 | jupyter_bokeh = "*" 75 | 76 | [tool.pixi.feature.analysis.pypi-dependencies] 77 | geoviews = "*" 78 | 79 | [dependency-groups] 80 | test = ["pytest"] 81 | -------------------------------------------------------------------------------- /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" 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/examples/01_CRS_in_auxiliary_variable.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Store CRS information in a auxiliary variable specified in grid_mapping" 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 json\n", 24 | "\n", 25 | "import cf_xarray # noqa\n", 26 | "import panel\n", 27 | "import rioxarray # noqa\n", 28 | "import xarray as xr\n", 29 | "import zarr\n", 30 | "\n", 31 | "# For zarr_format=2 encoding\n", 32 | "from numcodecs import Zstd" 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}.zarr\"\n", 44 | "v3_output = f\"../output/v3/{fp_base}.zarr\"" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 3, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "ds = xr.open_dataset(input)" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "## Check that all variables have a CF-compliant standard name\n" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 4, 66 | "metadata": {}, 67 | "outputs": [ 68 | { 69 | "name": "stdout", 70 | "output_type": "stream", 71 | "text": [ 72 | "These variables do NOT have a CF-compliant standard name: ['analysis_error', 'mask']\n", 73 | "These variables have a CF-compliant standard name: ['time', 'lat', 'lon', 'analysed_sst', 'sea_ice_fraction']\n" 74 | ] 75 | } 76 | ], 77 | "source": [ 78 | "standard_names = ds.cf.standard_names\n", 79 | "vars_with_standard_names = [v[0] for v in ds.cf.standard_names.values()]\n", 80 | "compliant_vars = []\n", 81 | "non_complaint_vars = []\n", 82 | "for var in ds.variables:\n", 83 | " if var not in vars_with_standard_names:\n", 84 | " non_complaint_vars.append(var)\n", 85 | " else:\n", 86 | " compliant_vars.append(var)\n", 87 | " assert ds[var].attrs[\"standard_name\"]\n", 88 | "\n", 89 | "print(f\"These variables do NOT have a CF-compliant standard name: {non_complaint_vars}\")\n", 90 | "print(f\"These variables have a CF-compliant standard name: {compliant_vars}\")" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "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." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 5, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "ds = ds[compliant_vars]" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "## Assign CRS information to an auxiliary variable using rioxarray" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 6, 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "ds = ds.rio.write_crs(\"epsg:4326\")\n", 123 | "# Specify which variable contains CRS information using grid_mapping\n", 124 | "for var in ds.data_vars:\n", 125 | " ds[var].attrs[\"grid_mapping\"] = \"spatial_ref\"" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "## Specify encoding and write to Zarr V2 format" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 7, 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/plain": [ 143 | "" 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 | --------------------------------------------------------------------------------