├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── README.md
├── example.py
├── performance.py
├── pyproject.toml
├── setup.py
├── src
└── covjson_pydantic
│ ├── __init__.py
│ ├── base_models.py
│ ├── coverage.py
│ ├── domain.py
│ ├── i18n.py
│ ├── ndarray.py
│ ├── observed_property.py
│ ├── parameter.py
│ ├── py.typed
│ ├── reference_system.py
│ └── unit.py
└── tests
├── __init__.py
├── test_coverage.py
└── test_data
├── categorical-data-parameter.json
├── continuous-data-parameter.json
├── coverage-json.json
├── coverage-mixed-type-ndarray.json
├── doc-example-coverage-collection.json
├── doc-example-coverage.json
├── example_py.json
├── grid-domain-no-y.json
├── grid-domain.json
├── mixed-type-axes-2.json
├── mixed-type-axes.json
├── mixed-type-ndarray-1.json
├── mixed-type-ndarray-2.json
├── mixed-type-ndarray-3.json
├── ndarray-float.json
├── ndarray-integer.json
├── ndarray-string.json
├── parameters.json
├── point-series-domain-custom.json
├── point-series-domain-more-z.json
├── point-series-domain-no-t.json
├── polygon-series-coverage-collection.json
├── spec-axes.json
├── spec-domain-grid.json
├── spec-domain-multipoint-series.json
├── spec-domain-multipoint.json
├── spec-domain-point-compact.json
├── spec-domain-point-series.json
├── spec-domain-point.json
├── spec-domain-polygon-series.json
├── spec-domain-trajectory.json
├── spec-domain-vertical-profile.json
├── spec-ndarray.json
├── spec-parametergroup.json
├── spec-reference-system-identifierrs.json
├── spec-tiled-ndarray.json
├── spec-trajectory-coverage.json
├── spec-vertical-profile-coverage.json
├── str-axes.json
└── temporalrs-no-calendar.json
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | tags:
8 | - '*'
9 | pull_request:
10 | env:
11 | LATEST_PY_VERSION: '3.10'
12 |
13 | jobs:
14 | tests:
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Set up Python ${{ matrix.python-version }}
23 | uses: actions/setup-python@v5
24 | with:
25 | python-version: ${{ matrix.python-version }}
26 |
27 | - name: Install dependencies
28 | run: |
29 | python -m pip install --upgrade pip
30 | python -m pip install .["test"]
31 |
32 | - name: Run pre-commit
33 | if: ${{ matrix.python-version == env.LATEST_PY_VERSION }}
34 | run: |
35 | python -m pip install pre-commit
36 | pre-commit run --all-files
37 |
38 | - name: Run tests
39 | run: python -m pytest --cov covjson_pydantic --cov-report xml --cov-report term-missing
40 |
41 | - name: Upload Results
42 | if: ${{ matrix.python-version == env.LATEST_PY_VERSION }}
43 | uses: codecov/codecov-action@v4
44 | env:
45 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
46 | with:
47 | file: ./coverage.xml
48 | flags: unittests
49 | name: ${{ matrix.python-version }}
50 | fail_ci_if_error: false
51 |
52 | publish:
53 | needs: [tests]
54 | runs-on: ubuntu-latest
55 | if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
56 | steps:
57 | - uses: actions/checkout@v4
58 | - name: Set up Python
59 | uses: actions/setup-python@v5
60 | with:
61 | python-version: ${{ env.LATEST_PY_VERSION }}
62 |
63 | - name: Install dependencies
64 | run: |
65 | python -m pip install --upgrade pip
66 | python -m pip install flit
67 | python -m pip install .
68 |
69 | - name: Set tag version
70 | id: tag
71 | run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
72 |
73 | - name: Set module version
74 | id: module
75 | run: echo "version=$(python -c 'from importlib.metadata import version; print(version("covjson_pydantic"))')" >> $GITHUB_OUTPUT
76 |
77 | - name: Build and publish
78 | if: steps.tag.outputs.tag == steps.module.outputs.version
79 | env:
80 | FLIT_USERNAME: __token__
81 | FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
82 | run: flit publish
83 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Code Editors
2 | ## Jet Brains
3 | .idea/
4 | .run/
5 | __pycache__/
6 |
7 | # Python
8 | ## MyPy
9 | .mypy_cache
10 | ## Virtual Environment
11 | venv/
12 |
13 | # unit test results
14 | .coverage
15 | .pytest_cache
16 | TEST-*-*.xml
17 | coverage.json
18 | coverage.xml
19 | htmlcov/
20 | junit-report.xml
21 |
22 | # Ignore package
23 | *.egg-info/
24 | .env
25 | build/
26 | dist/
27 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v4.0.1
4 | hooks:
5 | # Formatting
6 | - id: end-of-file-fixer # Makes sure files end in a newline and only a newline.
7 | - id: pretty-format-json
8 | args: [
9 | '--autofix',
10 | '--indent=4',
11 | '--no-ensure-ascii',
12 | '--no-sort-keys'
13 | ] # Formats and sorts your JSON files.
14 | - id: trailing-whitespace # Trims trailing whitespace.
15 | # Checks
16 | - id: check-json # Attempts to load all json files to verify syntax.
17 | - id: check-merge-conflict # Check for files that contain merge conflict strings.
18 | - id: check-shebang-scripts-are-executable # Checks that scripts with shebangs are executable.
19 | - id: check-yaml
20 | # only checks syntax not load the yaml:
21 | # https://stackoverflow.com/questions/59413979/how-exclude-ref-tag-from-check-yaml-git-hook
22 | args: [ '--unsafe' ] # Parse the yaml files for syntax.
23 |
24 | # reorder-python-imports ~ sort python imports
25 | - repo: https://github.com/asottile/reorder_python_imports
26 | rev: v2.6.0
27 | hooks:
28 | - id: reorder-python-imports
29 |
30 | # black ~ Formats Python code
31 | - repo: https://github.com/psf/black
32 | rev: 22.3.0
33 | hooks:
34 | - id: black
35 | args: [
36 | '--line-length=120'
37 | ]
38 |
39 | # flake8 ~ Enforces the Python PEP8 style guide
40 | # Configure the pep8-naming flake plugin to recognise @classmethod, @validator, @root_validator as classmethod.
41 | # Ignore the unused imports (F401) for the __init__ files, the imports are not always used inside the file,
42 | # but used to setup how other files can import it in a more convenient way.
43 | - repo: https://github.com/pycqa/flake8
44 | rev: 4.0.1
45 | hooks:
46 | - id: flake8
47 | args: [
48 | '--classmethod-decorators=classmethod,validator,root_validator',
49 | '--ignore=E203,W503',
50 | '--max-line-length=120',
51 | '--per-file-ignores=__init__.py:F401'
52 | ]
53 | additional_dependencies: [ 'pep8-naming==0.12.1' ]
54 |
55 | - repo: https://github.com/pre-commit/mirrors-mypy
56 | rev: v1.5.1
57 | hooks:
58 | - id: mypy
59 | language_version: python
60 | # No reason to run if only tests have changed. They intentionally break typing.
61 | exclude: tests/.*
62 | # Pass mypy the entire folder because a change in one file can break others.
63 | args: [--config-file=pyproject.toml, src/]
64 | # Don't pass it the individual filenames because it is already doing the whole folder.
65 | pass_filenames: false
66 | additional_dependencies:
67 | - pydantic
68 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Koninklijk Nederlands Meteorologisch Instituut (KNMI)
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CoverageJSON Pydantic
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | This repository contains the coveragejson-pydantic Python package. It provides [Pydantic](https://pydantic-docs.helpmanual.io/) models for [CoverageJSON](https://covjson.org/). This can, for example, be used to develop an API using FastAPI serving or receiving CoverageJSON.
23 |
24 | ## Install
25 | ```shell
26 | pip install covjson-pydantic
27 | ```
28 |
29 | Or you can install directly from source:
30 |
31 | ```shell
32 | pip install git+https://github.com/KNMI/covjson-pydantic.git
33 | ```
34 |
35 | ## Usage
36 |
37 | ```python
38 | from datetime import datetime, timezone
39 | from pydantic import AwareDatetime
40 | from covjson_pydantic.coverage import Coverage
41 | from covjson_pydantic.domain import Domain, Axes, ValuesAxis, DomainType
42 | from covjson_pydantic.ndarray import NdArrayFloat
43 |
44 | c = Coverage(
45 | domain=Domain(
46 | domainType=DomainType.point_series,
47 | axes=Axes(
48 | x=ValuesAxis[float](values=[1.23]),
49 | y=ValuesAxis[float](values=[4.56]),
50 | t=ValuesAxis[AwareDatetime](values=[datetime(2024, 8, 1, tzinfo=timezone.utc)]),
51 | ),
52 | ),
53 | ranges={
54 | "temperature": NdArrayFloat(axisNames=["x", "y", "t"], shape=[1, 1, 1], values=[42.0])
55 | }
56 | )
57 |
58 | print(c.model_dump_json(exclude_none=True, indent=4))
59 | ```
60 | Will print
61 | ```json
62 | {
63 | "type": "Coverage",
64 | "domain": {
65 | "type": "Domain",
66 | "domainType": "PointSeries",
67 | "axes": {
68 | "x": {
69 | "values": [
70 | 1.23
71 | ]
72 | },
73 | "y": {
74 | "values": [
75 | 4.56
76 | ]
77 | },
78 | "t": {
79 | "values": [
80 | "2024-08-01T00:00:00Z"
81 | ]
82 | }
83 | }
84 | },
85 | "ranges": {
86 | "temperature": {
87 | "type": "NdArray",
88 | "dataType": "float",
89 | "axisNames": [
90 | "x",
91 | "y",
92 | "t"
93 | ],
94 | "shape": [
95 | 1,
96 | 1,
97 | 1
98 | ],
99 | "values": [
100 | 42.0
101 | ]
102 | }
103 | }
104 | }
105 | ```
106 |
107 | ## Contributing
108 |
109 | Make an editable installation from within the repository root
110 |
111 | ```shell
112 | pip install -e '.[test]'
113 | ```
114 |
115 | ### Running tests
116 |
117 | ```shell
118 | pytest tests/
119 | ```
120 |
121 | ### Linting and typing
122 |
123 | Linting and typing (mypy) is done using [pre-commit](https://pre-commit.com) hooks.
124 |
125 | ```shell
126 | pip install pre-commit
127 | pre-commit install
128 | pre-commit run
129 | ```
130 |
131 | ## Related packages
132 |
133 | * [edr-pydantic](https://github.com/KNMI/edr-pydantic) - Pydantic data models for the Environmental Data Retrieval (EDR) API
134 | * [geojson-pydantic](https://github.com/developmentseed/geojson-pydantic) - Pydantic data models for the GeoJSON spec
135 |
136 | ## Real world usage
137 |
138 | This library is used to build an OGC Environmental Data Retrieval (EDR) API, serving automatic weather data station data from The Royal Netherlands Meteorological Institute (KNMI). See the [KNMI Data Platform EDR API](https://developer.dataplatform.knmi.nl/edr-api).
139 |
140 | ## TODOs
141 | Help is wanted in the following areas to fully implement the CovJSON spec:
142 | * The `Polygon`, `MultiPolygon` and `MultiPolygonSeries` domain types are not supported.
143 | * The `Section` domain type is not supported.
144 | * Not all requirements in the spec relating different fields are implemented.
145 |
146 | ## License
147 |
148 | Apache License, Version 2.0
149 |
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from datetime import timezone
3 |
4 | from covjson_pydantic.coverage import Coverage
5 | from covjson_pydantic.domain import Axes
6 | from covjson_pydantic.domain import Domain
7 | from covjson_pydantic.domain import DomainType
8 | from covjson_pydantic.domain import ValuesAxis
9 | from covjson_pydantic.ndarray import NdArrayFloat
10 | from pydantic import AwareDatetime
11 |
12 | c = Coverage(
13 | domain=Domain(
14 | domainType=DomainType.point_series,
15 | axes=Axes(
16 | x=ValuesAxis[float](values=[1.23]),
17 | y=ValuesAxis[float](values=[4.56]),
18 | t=ValuesAxis[AwareDatetime](values=[datetime(2024, 8, 1, tzinfo=timezone.utc)]),
19 | ),
20 | ),
21 | ranges={"temperature": NdArrayFloat(axisNames=["x", "y", "t"], shape=[1, 1, 1], values=[42.0])},
22 | )
23 |
24 | print(c.model_dump_json(exclude_none=True, indent=4))
25 |
--------------------------------------------------------------------------------
/performance.py:
--------------------------------------------------------------------------------
1 | import timeit
2 | from pathlib import Path
3 |
4 | filename = Path(__file__).parent.resolve() / "tests" / "test_data" / "coverage-json.json"
5 |
6 | setup = f"""
7 | import json
8 | from covjson_pydantic.coverage import Coverage
9 |
10 | file = "{filename}"
11 | # Put JSON in default unindented format
12 | with open(file, "r") as f:
13 | data = json.load(f)
14 | json_string = json.dumps(data, separators=(",", ":"))
15 | cj = Coverage.model_validate_json(json_string)
16 | """
17 |
18 | # This can be used to quickly check performance. The first call checks JSON to Python conversion
19 | # The second call checks Python to JSON conversion
20 | # Consider generating a larger CoverageJSON file
21 | print(timeit.timeit("Coverage.model_validate_json(json_string)", setup, number=1000))
22 | print(timeit.timeit("cj.model_dump_json(exclude_none=True)", setup, number=1000))
23 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "covjson-pydantic"
3 | description = "The Pydantic models for CoverageJSON"
4 | readme = "README.md"
5 | requires-python = ">=3.8"
6 | license = {file = "LICENSE"}
7 | authors = [
8 | {name = "KNMI Data Platform Team", email = "opendata@knmi.nl"},
9 | ]
10 | keywords = ["covjson", "Pydantic"]
11 | classifiers = [
12 | "Intended Audience :: Information Technology",
13 | "Intended Audience :: Science/Research",
14 | "License :: OSI Approved :: Apache Software License",
15 | "Programming Language :: Python :: 3.8",
16 | "Programming Language :: Python :: 3.9",
17 | "Programming Language :: Python :: 3.10",
18 | "Programming Language :: Python :: 3.11",
19 | "Programming Language :: Python :: 3.12",
20 | "Programming Language :: Python :: 3.13",
21 | "Topic :: Scientific/Engineering :: GIS",
22 | "Typing :: Typed",
23 | ]
24 | version = "0.7.0"
25 | dependencies = ["pydantic>=2.3,<3"]
26 |
27 | [project.optional-dependencies]
28 | test = ["pytest", "pytest-cov"]
29 | dev = ["pre-commit"]
30 |
31 | [project.urls]
32 | Source = "https://github.com/knmi/covjson-pydantic"
33 |
34 | [build-system]
35 | requires = ["flit>=3.2,<4"]
36 | build-backend = "flit_core.buildapi"
37 |
38 | [tool.flit.module]
39 | name = "covjson_pydantic"
40 |
41 | [tool.flit.sdist]
42 | exclude = [
43 | "test/",
44 | ".github/",
45 | ]
46 |
47 | [tool.mypy]
48 | plugins = [
49 | "pydantic.mypy"
50 | ]
51 |
52 | [tool.pydantic-mypy]
53 | warn_untyped_fields = true
54 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | import setuptools
5 |
6 | logging.basicConfig()
7 | logger = logging.getLogger(__name__)
8 | logger.setLevel(os.environ.get("LOG_LEVEL", "INFO"))
9 |
10 | if __name__ == "__main__":
11 | setuptools.setup()
12 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KNMI/covjson-pydantic/932cb4700a2e530cb48b2dcd5d8f65692ed19746/src/covjson_pydantic/__init__.py
--------------------------------------------------------------------------------
/src/covjson_pydantic/base_models.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel as PydanticBaseModel
2 | from pydantic import ConfigDict
3 |
4 |
5 | class CovJsonBaseModel(PydanticBaseModel):
6 | model_config = ConfigDict(
7 | str_strip_whitespace=True,
8 | str_min_length=1,
9 | extra="forbid",
10 | validate_default=True,
11 | validate_assignment=True,
12 | strict=True,
13 | )
14 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/coverage.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | if sys.version_info < (3, 9):
4 | from typing_extensions import Annotated
5 | else:
6 | from typing import Annotated
7 |
8 | from typing import Dict
9 | from typing import List
10 | from typing import Literal
11 | from typing import Optional
12 | from typing import Union
13 |
14 | from pydantic import AnyUrl
15 | from pydantic import Field
16 |
17 | from .base_models import CovJsonBaseModel
18 | from .domain import Domain
19 | from .domain import DomainType
20 | from .ndarray import NdArrayFloat
21 | from .ndarray import NdArrayInt
22 | from .ndarray import NdArrayStr
23 | from .ndarray import TiledNdArrayFloat
24 | from .parameter import Parameters
25 | from .parameter import ParameterGroup
26 | from .reference_system import ReferenceSystemConnectionObject
27 |
28 | NdArrayTypes = Annotated[Union[NdArrayFloat, NdArrayInt, NdArrayStr], Field(discriminator="dataType")]
29 |
30 |
31 | class Coverage(CovJsonBaseModel, extra="allow"):
32 | id: Optional[str] = None
33 | type: Literal["Coverage"] = "Coverage"
34 | domain: Domain
35 | parameters: Optional[Parameters] = None
36 | parameterGroups: Optional[List[ParameterGroup]] = None # noqa: N815
37 | ranges: Dict[str, Union[NdArrayTypes, TiledNdArrayFloat, AnyUrl]]
38 |
39 |
40 | class CoverageCollection(CovJsonBaseModel, extra="allow"):
41 | type: Literal["CoverageCollection"] = "CoverageCollection"
42 | domainType: Optional[DomainType] = None # noqa: N815
43 | coverages: List[Coverage]
44 | parameters: Optional[Parameters] = None
45 | parameterGroups: Optional[List[ParameterGroup]] = None # noqa: N815
46 | referencing: Optional[List[ReferenceSystemConnectionObject]] = None
47 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/domain.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Generic
3 | from typing import List
4 | from typing import Literal
5 | from typing import Optional
6 | from typing import Tuple
7 | from typing import TypeVar
8 | from typing import Union
9 |
10 | from pydantic import AwareDatetime
11 | from pydantic import field_validator
12 | from pydantic import model_validator
13 | from pydantic import PositiveInt
14 |
15 | from .base_models import CovJsonBaseModel
16 | from .reference_system import ReferenceSystemConnectionObject
17 |
18 |
19 | class CompactAxis(CovJsonBaseModel):
20 | start: float
21 | stop: float
22 | num: PositiveInt
23 |
24 | @model_validator(mode="after")
25 | def single_value_case(self):
26 | if self.num == 1 and self.start != self.stop:
27 | raise ValueError("If the value of 'num' is 1, then 'start' and 'stop' MUST have identical values.")
28 | return self
29 |
30 |
31 | ValuesT = TypeVar("ValuesT")
32 |
33 |
34 | # Combination between Generics (ValuesT) and datetime and strict mode causes issues between JSON <-> Pydantic
35 | # conversions. Strict mode has been disabled. Issue: https://github.com/KNMI/covjson-pydantic/issues/4
36 | class ValuesAxis(CovJsonBaseModel, Generic[ValuesT], extra="allow", strict=False):
37 | dataType: Optional[str] = None # noqa: N815
38 | coordinates: Optional[List[str]] = None
39 | values: List[ValuesT]
40 | bounds: Optional[List[ValuesT]] = None
41 |
42 | @model_validator(mode="after")
43 | def bounds_length(self):
44 | if self.bounds is not None and len(self.bounds) != 2 * len(self.values):
45 | raise ValueError("If provided, the length of 'bounds' should be twice that of 'values'.")
46 | return self
47 |
48 |
49 | class DomainType(str, Enum):
50 | grid = "Grid"
51 | vertical_profile = "VerticalProfile"
52 | point_series = "PointSeries"
53 | point = "Point"
54 | multi_point_series = "MultiPointSeries"
55 | multi_point = "MultiPoint"
56 | trajectory = "Trajectory"
57 | polygon_series = "PolygonSeries"
58 |
59 |
60 | class Axes(CovJsonBaseModel):
61 | x: Optional[Union[ValuesAxis[float], ValuesAxis[str], CompactAxis]] = None
62 | y: Optional[Union[ValuesAxis[float], ValuesAxis[str], CompactAxis]] = None
63 | z: Optional[Union[ValuesAxis[float], ValuesAxis[str], CompactAxis]] = None
64 | t: Optional[ValuesAxis[AwareDatetime]] = None
65 | # TODO: Add better support for 'polygon' and 'tuple' composite axes
66 | composite: Optional[ValuesAxis[Tuple]] = None
67 |
68 | @model_validator(mode="after")
69 | def at_least_one_axes(self):
70 | if self.x is None and self.y is None and self.z is None and self.t is None and self.composite is None:
71 | raise ValueError("At least one axis of x,y,z,t or composite must be given.")
72 | return self
73 |
74 |
75 | class Domain(CovJsonBaseModel, extra="allow"):
76 | type: Literal["Domain"] = "Domain"
77 | domainType: Optional[DomainType] = None # noqa: N815
78 | axes: Axes
79 | referencing: Optional[List[ReferenceSystemConnectionObject]] = None
80 |
81 | # TODO: This is a workaround to allow domainType to work in strict mode, in combination with FastAPI.
82 | # See: https://github.com/tiangolo/fastapi/discussions/9868
83 | # And: https://github.com/KNMI/covjson-pydantic/issues/5
84 | @field_validator("domainType", mode="before")
85 | @classmethod
86 | def value_to_enum(cls, v):
87 | if isinstance(v, str):
88 | return DomainType(v)
89 | return v
90 |
91 | @staticmethod
92 | def check_axis(domain_type, axes, required_axes, allowed_axes, single_value_axes):
93 | # Check required axes
94 | for axis_name in required_axes:
95 | axis = getattr(axes, axis_name)
96 | if axis is None:
97 | raise ValueError(f"A '{domain_type.value}' must have a '{axis_name}'-axis.")
98 | if axis_name in single_value_axes:
99 | if isinstance(axis, ValuesAxis) and len(axis.values) != 1:
100 | raise ValueError(
101 | f"The 'values' field of the ValuesAxis '{axis_name}'-axis "
102 | f"of a '{domain_type.value}' domain must contain a single value."
103 | )
104 | if isinstance(axis, CompactAxis) and axis.num != 1:
105 | raise ValueError(
106 | f"The 'num' field of the CompactAxis '{axis_name}'-axis "
107 | f"of a '{domain_type.value}' domain must be 1."
108 | )
109 |
110 | # Check allowed axes
111 | all_axis = {"x", "y", "z", "t", "composite"}
112 | for axis_name in all_axis - required_axes - allowed_axes:
113 | axis = getattr(axes, axis_name)
114 | if axis is not None:
115 | raise ValueError(f"A '{domain_type.value}' domain can not have a '{axis_name}'-axis.")
116 |
117 | # Check for single value of allowed axes
118 | for axis_name in allowed_axes:
119 | axis = getattr(axes, axis_name)
120 | if axis is not None and axis_name in single_value_axes:
121 | if isinstance(axis, ValuesAxis) and len(axis.values) != 1:
122 | raise ValueError(
123 | f"If provided, the 'values' field of the ValuesAxis '{axis_name}'-axis "
124 | f"of a '{domain_type.value}' domain must contain a single value."
125 | )
126 | if isinstance(axis, CompactAxis) and axis.num != 1:
127 | raise ValueError(
128 | f"If provided, the 'num' field of the CompactAxis '{axis_name}'-axis "
129 | f"of a '{domain_type.value}' domain must be 1."
130 | )
131 |
132 | @model_validator(mode="after")
133 | def check_domain_consistent(self):
134 | domain_type = self.domainType
135 | axes = self.axes
136 |
137 | if domain_type == DomainType.grid:
138 | Domain.check_axis(
139 | domain_type, axes, required_axes={"x", "y"}, allowed_axes={"z", "t"}, single_value_axes=set()
140 | )
141 |
142 | if domain_type == DomainType.vertical_profile:
143 | Domain.check_axis(
144 | domain_type, axes, required_axes={"x", "y", "z"}, allowed_axes={"t"}, single_value_axes={"x", "y", "t"}
145 | )
146 |
147 | if domain_type == DomainType.point_series:
148 | Domain.check_axis(
149 | domain_type, axes, required_axes={"x", "y", "t"}, allowed_axes={"z"}, single_value_axes={"x", "y", "z"}
150 | )
151 |
152 | if domain_type == DomainType.polygon_series:
153 | Domain.check_axis(
154 | domain_type,
155 | axes,
156 | required_axes={"composite", "t"},
157 | allowed_axes={"z"},
158 | single_value_axes={"z", "composite"},
159 | )
160 |
161 | if domain_type == DomainType.point:
162 | Domain.check_axis(
163 | domain_type,
164 | axes,
165 | required_axes={"x", "y"},
166 | allowed_axes={"z", "t"},
167 | single_value_axes={"x", "y", "z", "t"},
168 | )
169 |
170 | if domain_type == DomainType.multi_point_series:
171 | Domain.check_axis(
172 | domain_type, axes, required_axes={"composite", "t"}, allowed_axes=set(), single_value_axes=set()
173 | )
174 |
175 | if domain_type == DomainType.multi_point:
176 | Domain.check_axis(
177 | domain_type, axes, required_axes={"composite"}, allowed_axes={"t"}, single_value_axes={"t"}
178 | )
179 |
180 | if domain_type == DomainType.trajectory:
181 | Domain.check_axis(
182 | domain_type, axes, required_axes={"composite"}, allowed_axes={"z"}, single_value_axes={"z"}
183 | )
184 |
185 | return self
186 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/i18n.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Dict
3 |
4 |
5 | class LanguageTag(str, Enum):
6 | dutch = "nl"
7 | english = "en"
8 | german = "de"
9 | undefined = "und"
10 |
11 |
12 | # TODO: This was throwing warning:
13 | # Expected `definition-ref` but got `LanguageTag` - serialized value may not be as expected
14 | # This may be a bug in Pydantic: https://github.com/pydantic/pydantic/issues/6467
15 | # or: https://github.com/pydantic/pydantic/issues/6422
16 | # So, for now, reverted to a less strict type
17 | # See issue: https://github.com/KNMI/covjson-pydantic/issues/3
18 | # i18n = Dict[LanguageTag, str]
19 | i18n = Dict[str, str]
20 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/ndarray.py:
--------------------------------------------------------------------------------
1 | import math
2 | from typing import List
3 | from typing import Literal
4 | from typing import Optional
5 |
6 | from pydantic import model_validator
7 |
8 | from .base_models import CovJsonBaseModel
9 |
10 |
11 | class NdArray(CovJsonBaseModel, extra="allow"):
12 | type: Literal["NdArray"] = "NdArray"
13 | dataType: str # Kept here to ensure order of output in JSON # noqa: N815
14 | axisNames: Optional[List[str]] = None # noqa: N815
15 | shape: Optional[List[int]] = None
16 |
17 | @model_validator(mode="before")
18 | @classmethod
19 | def validate_is_sub_class(cls, values):
20 | if cls is NdArray:
21 | raise TypeError(
22 | "NdArray cannot be instantiated directly, please use a NdArrayFloat, NdArrayInt or NdArrayStr"
23 | )
24 | return values
25 |
26 | @model_validator(mode="after")
27 | def check_field_dependencies(self):
28 | if len(self.values) > 1 and (self.axisNames is None or len(self.axisNames) == 0):
29 | raise ValueError("'axisNames' must to be provided if array is not 0D")
30 |
31 | if len(self.values) > 1 and (self.shape is None or len(self.shape) == 0):
32 | raise ValueError("'shape' must to be provided if array is not 0D")
33 |
34 | if self.axisNames is not None and self.shape is not None and len(self.axisNames) != len(self.shape):
35 | raise ValueError("'axisNames' and 'shape' should have equal length")
36 |
37 | if self.shape is not None and len(self.shape) >= 1:
38 | prod = math.prod(self.shape)
39 | if len(self.values) != prod:
40 | raise ValueError(
41 | "Where 'shape' is present and non-empty, the product of its values MUST equal "
42 | "the number of elements in the 'values' array."
43 | )
44 |
45 | return self
46 |
47 |
48 | class NdArrayFloat(NdArray):
49 | dataType: Literal["float"] = "float" # noqa: N815
50 | values: List[Optional[float]]
51 |
52 |
53 | class NdArrayInt(NdArray):
54 | dataType: Literal["integer"] = "integer" # noqa: N815
55 | values: List[Optional[int]]
56 |
57 |
58 | class NdArrayStr(NdArray):
59 | dataType: Literal["string"] = "string" # noqa: N815
60 | values: List[Optional[str]]
61 |
62 |
63 | class TileSet(CovJsonBaseModel):
64 | tileShape: List[Optional[int]] # noqa: N815
65 | urlTemplate: str # noqa: N815
66 |
67 |
68 | # TODO: Validation of field dependencies
69 | # TODO: Support string and integer type TiledNdArray
70 | class TiledNdArrayFloat(CovJsonBaseModel, extra="allow"):
71 | type: Literal["TiledNdArray"] = "TiledNdArray"
72 | dataType: Literal["float"] = "float" # noqa: N815
73 | axisNames: List[str] # noqa: N815
74 | shape: List[int]
75 | tileSets: List[TileSet] # noqa: N815
76 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/observed_property.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from typing import Optional
3 |
4 | from .base_models import CovJsonBaseModel
5 | from .i18n import i18n
6 |
7 |
8 | class Category(CovJsonBaseModel):
9 | id: str
10 | label: i18n
11 | description: Optional[i18n] = None
12 |
13 |
14 | class ObservedProperty(CovJsonBaseModel):
15 | id: Optional[str] = None
16 | label: i18n
17 | description: Optional[i18n] = None
18 | categories: Optional[List[Category]] = None
19 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/parameter.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 | from typing import List
3 | from typing import Literal
4 | from typing import Optional
5 | from typing import Union
6 |
7 | from pydantic import model_validator
8 | from pydantic import RootModel
9 |
10 | from .base_models import CovJsonBaseModel
11 | from .i18n import i18n
12 | from .observed_property import ObservedProperty
13 | from .unit import Unit
14 |
15 |
16 | class Parameter(CovJsonBaseModel, extra="allow"):
17 | type: Literal["Parameter"] = "Parameter"
18 | id: Optional[str] = None
19 | label: Optional[i18n] = None
20 | description: Optional[i18n] = None
21 | observedProperty: ObservedProperty # noqa: N815
22 | categoryEncoding: Optional[Dict[str, Union[int, List[int]]]] = None # noqa: N815
23 | unit: Optional[Unit] = None
24 |
25 | @model_validator(mode="after")
26 | def must_not_have_unit_if_observed_property_has_categories(self):
27 | if self.unit is not None and self.observedProperty is not None and self.observedProperty.categories is not None:
28 | raise ValueError(
29 | "A parameter object MUST NOT have a 'unit' member "
30 | "if the 'observedProperty' member has a 'categories' member."
31 | )
32 |
33 | return self
34 |
35 |
36 | class Parameters(RootModel):
37 | root: Dict[str, Parameter]
38 |
39 | def __iter__(self):
40 | return iter(self.root)
41 |
42 | def __getitem__(self, key):
43 | return self.root[key]
44 |
45 | def get(self, key, default=None):
46 | return self.root.get(key, default)
47 |
48 |
49 | class ParameterGroup(CovJsonBaseModel, extra="allow"):
50 | type: Literal["ParameterGroup"] = "ParameterGroup"
51 | id: Optional[str] = None
52 | label: Optional[i18n] = None
53 | description: Optional[i18n] = None
54 | observedProperty: Optional[ObservedProperty] = None # noqa: N815
55 | members: List[str]
56 |
57 | @model_validator(mode="after")
58 | def must_have_label_and_or_observed_property(self):
59 | if self.label is None and self.observedProperty is None:
60 | raise ValueError(
61 | "A parameter group object MUST have either or both the members 'label' or/and 'observedProperty'"
62 | )
63 | return self
64 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KNMI/covjson-pydantic/932cb4700a2e530cb48b2dcd5d8f65692ed19746/src/covjson_pydantic/py.typed
--------------------------------------------------------------------------------
/src/covjson_pydantic/reference_system.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 | from typing import List
3 | from typing import Literal
4 | from typing import Optional
5 | from typing import Union
6 |
7 | from pydantic import AnyUrl
8 | from pydantic import model_validator
9 |
10 | from .base_models import CovJsonBaseModel
11 | from .i18n import i18n
12 |
13 |
14 | class TargetConcept(CovJsonBaseModel):
15 | id: Optional[str] = None # Not in spec, but needed for example in spec for 'Identifier-based Reference Systems'
16 | label: i18n
17 | description: Optional[i18n] = None
18 |
19 |
20 | class ReferenceSystem(CovJsonBaseModel, extra="allow"):
21 | type: Literal["GeographicCRS", "ProjectedCRS", "VerticalCRS", "TemporalRS", "IdentifierRS"]
22 | id: Optional[str] = None
23 | description: Optional[i18n] = None
24 |
25 | # Only for TemporalRS
26 | calendar: Optional[Union[Literal["Gregorian"], AnyUrl]] = None
27 | timeScale: Optional[AnyUrl] = None # noqa: N815
28 |
29 | # Only for IdentifierRS
30 | label: Optional[i18n] = None
31 | targetConcept: Optional[TargetConcept] = None # noqa: N815
32 | identifiers: Optional[Dict[str, TargetConcept]] = None
33 |
34 | @model_validator(mode="after")
35 | def check_type_specific_fields(self):
36 | if self.type != "TemporalRS" and (self.calendar is not None or self.timeScale is not None):
37 | raise ValueError("'calendar' and 'timeScale' fields can only be used for type 'TemporalRS'")
38 |
39 | if self.type == "TemporalRS" and self.calendar is None:
40 | raise ValueError("A temporal RS object MUST have a member 'calendar' with value 'Gregorian' or a URI")
41 |
42 | if self.type != "IdentifierRS" and (
43 | self.label is not None or self.targetConcept is not None or self.identifiers is not None
44 | ):
45 | raise ValueError(
46 | "'label', 'targetConcept' and 'identifiers' fields can only be used for type 'IdentifierRS'"
47 | )
48 |
49 | if self.type == "IdentifierRS" and self.targetConcept is None:
50 | raise ValueError("An identifier RS object MUST have a member 'targetConcept'")
51 |
52 | return self
53 |
54 |
55 | class ReferenceSystemConnectionObject(CovJsonBaseModel):
56 | coordinates: List[str]
57 | system: ReferenceSystem
58 |
--------------------------------------------------------------------------------
/src/covjson_pydantic/unit.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 | from typing import Union
3 |
4 | from pydantic import model_validator
5 |
6 | from .base_models import CovJsonBaseModel
7 | from .i18n import i18n
8 |
9 |
10 | class Symbol(CovJsonBaseModel):
11 | value: str
12 | type: str
13 |
14 |
15 | class Unit(CovJsonBaseModel):
16 | id: Optional[str] = None
17 | label: Optional[i18n] = None
18 | symbol: Optional[Union[str, Symbol]] = None
19 |
20 | @model_validator(mode="after")
21 | def check_either_label_or_symbol(self):
22 | if self.label is None and self.symbol is None:
23 | raise ValueError("Either 'label' or 'symbol' should be set")
24 |
25 | return self
26 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KNMI/covjson-pydantic/932cb4700a2e530cb48b2dcd5d8f65692ed19746/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_coverage.py:
--------------------------------------------------------------------------------
1 | import json
2 | import sys
3 | from io import StringIO
4 | from pathlib import Path
5 |
6 | import pytest
7 | from covjson_pydantic.coverage import Coverage
8 | from covjson_pydantic.coverage import CoverageCollection
9 | from covjson_pydantic.domain import Axes
10 | from covjson_pydantic.domain import Domain
11 | from covjson_pydantic.ndarray import NdArray
12 | from covjson_pydantic.ndarray import NdArrayFloat
13 | from covjson_pydantic.ndarray import NdArrayInt
14 | from covjson_pydantic.ndarray import NdArrayStr
15 | from covjson_pydantic.ndarray import TiledNdArrayFloat
16 | from covjson_pydantic.parameter import Parameter
17 | from covjson_pydantic.parameter import ParameterGroup
18 | from covjson_pydantic.parameter import Parameters
19 | from covjson_pydantic.reference_system import ReferenceSystem
20 | from covjson_pydantic.reference_system import ReferenceSystemConnectionObject
21 | from pydantic import ValidationError
22 |
23 |
24 | happy_cases = [
25 | ("spec-axes.json", Axes),
26 | ("str-axes.json", Axes),
27 | ("coverage-json.json", Coverage),
28 | ("coverage-mixed-type-ndarray.json", Coverage),
29 | ("doc-example-coverage.json", Coverage),
30 | ("example_py.json", Coverage),
31 | ("spec-vertical-profile-coverage.json", Coverage),
32 | ("spec-trajectory-coverage.json", Coverage),
33 | ("doc-example-coverage-collection.json", CoverageCollection),
34 | ("polygon-series-coverage-collection.json", CoverageCollection),
35 | ("grid-domain.json", Domain),
36 | ("point-series-domain-custom.json", Domain),
37 | ("spec-domain-grid.json", Domain),
38 | ("spec-domain-vertical-profile.json", Domain),
39 | ("spec-domain-point-series.json", Domain),
40 | ("spec-domain-point.json", Domain),
41 | ("spec-domain-point-compact.json", Domain),
42 | ("spec-domain-multipoint-series.json", Domain),
43 | ("spec-domain-multipoint.json", Domain),
44 | ("spec-domain-trajectory.json", Domain),
45 | ("spec-domain-polygon-series.json", Domain),
46 | ("ndarray-float.json", NdArrayFloat),
47 | ("ndarray-string.json", NdArrayStr),
48 | ("ndarray-integer.json", NdArrayInt),
49 | ("spec-ndarray.json", NdArrayFloat),
50 | ("spec-tiled-ndarray.json", TiledNdArrayFloat),
51 | ("continuous-data-parameter.json", Parameter),
52 | ("categorical-data-parameter.json", Parameter),
53 | ("parameters.json", Parameters),
54 | ("spec-parametergroup.json", ParameterGroup),
55 | ("spec-reference-system-identifierrs.json", ReferenceSystem),
56 | ]
57 |
58 |
59 | @pytest.mark.parametrize("file_name, object_type", happy_cases)
60 | def test_happy_cases(file_name, object_type):
61 | file = Path(__file__).parent.resolve() / "test_data" / file_name
62 | # Put JSON in default unindented format
63 | with open(file, "r") as f:
64 | data = json.load(f)
65 | json_string = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
66 |
67 | # Round-trip
68 | assert object_type.model_validate_json(json_string).model_dump_json(exclude_none=True) == json_string
69 |
70 |
71 | error_cases = [
72 | ("grid-domain-no-y.json", Domain, r"A 'Grid' must have a 'y'-axis"),
73 | (
74 | "point-series-domain-more-z.json",
75 | Domain,
76 | r"If provided, the 'values' field of the ValuesAxis 'z'-axis of a 'PointSeries' "
77 | + "domain must contain a single value.",
78 | ),
79 | ("point-series-domain-no-t.json", Domain, r"A 'PointSeries' must have a 't'-axis."),
80 | ("mixed-type-axes.json", Axes, r"Input should be a valid number"),
81 | ("mixed-type-axes-2.json", Axes, r"Input should be a valid string"),
82 | ("mixed-type-ndarray-1.json", NdArrayFloat, r"Input should be a valid number"),
83 | ("mixed-type-ndarray-1.json", NdArrayStr, r"Input should be 'string'"),
84 | ("mixed-type-ndarray-2.json", NdArrayFloat, r"Input should be a valid number"),
85 | ("mixed-type-ndarray-2.json", NdArrayStr, r"Input should be 'string'"),
86 | ("mixed-type-ndarray-3.json", NdArrayInt, r"Input should be a valid integer"),
87 | ("mixed-type-ndarray-3.json", NdArrayFloat, r"Input should be 'float'"),
88 | (
89 | "temporalrs-no-calendar.json",
90 | ReferenceSystemConnectionObject,
91 | r"A temporal RS object MUST have a member 'calendar'",
92 | ),
93 | ]
94 |
95 |
96 | @pytest.mark.parametrize("file_name, object_type, error_message", error_cases)
97 | def test_error_cases(file_name, object_type, error_message):
98 | file = Path(__file__).parent.resolve() / "test_data" / file_name
99 | # Put JSON in default unindented format
100 | with open(file, "r") as f:
101 | data = json.load(f)
102 | json_string = json.dumps(data, separators=(",", ":"))
103 |
104 | with pytest.raises(ValidationError, match=error_message):
105 | object_type.model_validate_json(json_string)
106 |
107 |
108 | def test_ndarray_directly():
109 | with pytest.raises(TypeError, match="NdArray cannot be instantiated directly"):
110 | NdArray(axisNames=["x", "y", "t"], shape=[1, 1, 1], values=[42.0])
111 |
112 |
113 | def test_example_py():
114 | file = Path(__file__).parent.parent.resolve() / "example.py"
115 |
116 | with open(file, "r") as f:
117 | code = f.read()
118 |
119 | old_stdout = sys.stdout
120 | sys.stdout = my_stdout = StringIO()
121 | exec(code)
122 | sys.stdout = old_stdout
123 |
124 | file = Path(__file__).parent.resolve() / "test_data" / "example_py.json"
125 | with open(file, "r") as f:
126 | assert my_stdout.getvalue() == f.read()
127 |
128 |
129 | def test_parameters_root_model():
130 | file = Path(__file__).parent.resolve() / "test_data" / "parameters.json"
131 | with open(file, "r") as f:
132 | parameters = Parameters.model_validate_json(f.read())
133 |
134 | assert parameters["PSAL"].observedProperty.label["en"] == "Sea Water Salinity"
135 | assert parameters.get("POTM").observedProperty.label["en"] == "Sea Water Potential Temperature"
136 | assert len([p for p in parameters]) == 2
137 |
--------------------------------------------------------------------------------
/tests/test_data/categorical-data-parameter.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Parameter",
3 | "description": {
4 | "en": "The land cover category."
5 | },
6 | "observedProperty": {
7 | "id": "http://example.com/land_cover",
8 | "label": {
9 | "en": "Land Cover"
10 | },
11 | "description": {
12 | "en": "longer description..."
13 | },
14 | "categories": [
15 | {
16 | "id": "http://example.com/land_cover/categories/grass",
17 | "label": {
18 | "en": "Grass"
19 | },
20 | "description": {
21 | "en": "Very green grass."
22 | }
23 | },
24 | {
25 | "id": "http://example.com/land_cover/categories/forest",
26 | "label": {
27 | "en": "Forest"
28 | }
29 | }
30 | ]
31 | },
32 | "categoryEncoding": {
33 | "http://example.com/land_cover/categories/grass": 1,
34 | "http://example.com/land_cover/categories/forest": [
35 | 2,
36 | 3
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/test_data/continuous-data-parameter.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Parameter",
3 | "description": {
4 | "en": "The sea surface temperature in degrees Celsius."
5 | },
6 | "observedProperty": {
7 | "id": "http://vocab.nerc.ac.uk/standard_name/sea_surface_temperature/",
8 | "label": {
9 | "en": "Sea Surface Temperature"
10 | },
11 | "description": {
12 | "en": "The temperature of sea water near the surface (including the part under sea-ice, if any), and not the skin temperature."
13 | }
14 | },
15 | "unit": {
16 | "label": {
17 | "en": "Degree Celsius"
18 | },
19 | "symbol": {
20 | "value": "Cel",
21 | "type": "http://www.opengis.net/def/uom/UCUM/"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/test_data/coverage-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Coverage",
3 | "domain": {
4 | "type": "Domain",
5 | "domainType": "PointSeries",
6 | "axes": {
7 | "x": {
8 | "values": [
9 | 5.3
10 | ]
11 | },
12 | "y": {
13 | "values": [
14 | 53.2
15 | ]
16 | },
17 | "t": {
18 | "values": [
19 | "2022-01-01T04:10:00Z",
20 | "2022-01-01T04:20:00Z",
21 | "2022-01-01T04:30:00Z",
22 | "2022-01-01T04:40:00Z",
23 | "2022-01-01T04:50:00Z",
24 | "2022-01-01T05:00:00Z"
25 | ]
26 | }
27 | }
28 | },
29 | "parameters": {
30 | "temperature": {
31 | "type": "Parameter",
32 | "description": {
33 | "en": "This is the air temperature"
34 | },
35 | "observedProperty": {
36 | "label": {
37 | "en": "temperature"
38 | }
39 | },
40 | "unit": {
41 | "label": {
42 | "en": "Degree Celsius"
43 | },
44 | "symbol": {
45 | "value": "Cel",
46 | "type": "http://www.opengis.net/def/uom/UCUM"
47 | }
48 | }
49 | },
50 | "dewpoint": {
51 | "type": "Parameter",
52 | "description": {
53 | "en": "This is the air dewpoint"
54 | },
55 | "observedProperty": {
56 | "label": {
57 | "en": "dewpoint"
58 | }
59 | },
60 | "unit": {
61 | "label": {
62 | "en": "Degree Celsius"
63 | },
64 | "symbol": {
65 | "value": "Cel",
66 | "type": "http://www.opengis.net/def/uom/UCUM"
67 | }
68 | }
69 | }
70 | },
71 | "ranges": {
72 | "temperature": {
73 | "type": "NdArray",
74 | "dataType": "float",
75 | "axisNames": [
76 | "x",
77 | "y",
78 | "t"
79 | ],
80 | "shape": [
81 | 1,
82 | 1,
83 | 6
84 | ],
85 | "values": [
86 | 64.27437704538298,
87 | 64.70702358086481,
88 | 65.13680141101983,
89 | 65.56289242989419,
90 | 65.98448554858814,
91 | 66.40077824091921
92 | ]
93 | },
94 | "dewpoint": {
95 | "type": "NdArray",
96 | "dataType": "float",
97 | "axisNames": [
98 | "x",
99 | "y",
100 | "t"
101 | ],
102 | "shape": [
103 | 1,
104 | 1,
105 | 6
106 | ],
107 | "values": [
108 | 62.27437704538298,
109 | 62.707023580864806,
110 | 63.136801411019825,
111 | 63.56289242989419,
112 | 63.98448554858814,
113 | 64.40077824091921
114 | ]
115 | }
116 | },
117 | "extra:extra": "extra fields allowed"
118 | }
119 |
--------------------------------------------------------------------------------
/tests/test_data/coverage-mixed-type-ndarray.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Coverage",
3 | "domain": {
4 | "type": "Domain",
5 | "domainType": "PointSeries",
6 | "axes": {
7 | "x": {
8 | "values": [
9 | 5.3
10 | ]
11 | },
12 | "y": {
13 | "values": [
14 | 53.2
15 | ]
16 | },
17 | "t": {
18 | "values": [
19 | "2022-01-01T04:10:00Z",
20 | "2022-01-01T04:20:00Z",
21 | "2022-01-01T04:30:00Z"
22 | ]
23 | }
24 | }
25 | },
26 | "parameters": {
27 | "float-parameter": {
28 | "type": "Parameter",
29 | "observedProperty": {
30 | "label": {
31 | "en": "float"
32 | }
33 | }
34 | },
35 | "string-parameter": {
36 | "type": "Parameter",
37 | "observedProperty": {
38 | "label": {
39 | "en": "string"
40 | }
41 | }
42 | },
43 | "integer-parameter": {
44 | "type": "Parameter",
45 | "observedProperty": {
46 | "label": {
47 | "en": "integer"
48 | }
49 | }
50 | },
51 | "null-parameter": {
52 | "type": "Parameter",
53 | "observedProperty": {
54 | "label": {
55 | "en": "null"
56 | }
57 | }
58 | }
59 | },
60 | "ranges": {
61 | "string-parameter": {
62 | "type": "NdArray",
63 | "dataType": "string",
64 | "axisNames": [
65 | "x",
66 | "y",
67 | "t"
68 | ],
69 | "shape": [
70 | 1,
71 | 1,
72 | 3
73 | ],
74 | "values": [
75 | null,
76 | "foo",
77 | "bar"
78 | ]
79 | },
80 | "float-parameter": {
81 | "type": "NdArray",
82 | "dataType": "float",
83 | "axisNames": [
84 | "x",
85 | "y",
86 | "t"
87 | ],
88 | "shape": [
89 | 1,
90 | 1,
91 | 3
92 | ],
93 | "values": [
94 | 62.0,
95 | null,
96 | 63.136801411019825
97 | ]
98 | },
99 | "integer-parameter": {
100 | "type": "NdArray",
101 | "dataType": "integer",
102 | "axisNames": [
103 | "x",
104 | "y",
105 | "t"
106 | ],
107 | "shape": [
108 | 1,
109 | 1,
110 | 3
111 | ],
112 | "values": [
113 | 1,
114 | null,
115 | 3
116 | ]
117 | },
118 | "null-parameter": {
119 | "type": "NdArray",
120 | "dataType": "integer",
121 | "axisNames": [
122 | "x",
123 | "y",
124 | "t"
125 | ],
126 | "shape": [
127 | 1,
128 | 1,
129 | 3
130 | ],
131 | "values": [
132 | null,
133 | null,
134 | null
135 | ]
136 | }
137 | },
138 | "extra:extra": "extra fields allowed"
139 | }
140 |
--------------------------------------------------------------------------------
/tests/test_data/doc-example-coverage-collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "CoverageCollection",
3 | "domainType": "VerticalProfile",
4 | "coverages": [
5 | {
6 | "type": "Coverage",
7 | "domain": {
8 | "type": "Domain",
9 | "axes": {
10 | "x": {
11 | "values": [
12 | -10.1
13 | ]
14 | },
15 | "y": {
16 | "values": [
17 | -40.2
18 | ]
19 | },
20 | "z": {
21 | "values": [
22 | 5.0,
23 | 8.0,
24 | 14.0
25 | ]
26 | },
27 | "t": {
28 | "values": [
29 | "2013-01-13T11:12:20Z"
30 | ]
31 | }
32 | }
33 | },
34 | "ranges": {
35 | "PSAL": {
36 | "type": "NdArray",
37 | "dataType": "float",
38 | "axisNames": [
39 | "z"
40 | ],
41 | "shape": [
42 | 3
43 | ],
44 | "values": [
45 | 43.7,
46 | 43.8,
47 | 43.9
48 | ]
49 | }
50 | }
51 | },
52 | {
53 | "type": "Coverage",
54 | "domain": {
55 | "type": "Domain",
56 | "axes": {
57 | "x": {
58 | "values": [
59 | -11.1
60 | ]
61 | },
62 | "y": {
63 | "values": [
64 | -45.2
65 | ]
66 | },
67 | "z": {
68 | "values": [
69 | 4.0,
70 | 7.0,
71 | 9.0
72 | ]
73 | },
74 | "t": {
75 | "values": [
76 | "2013-01-13T12:12:20Z"
77 | ]
78 | }
79 | }
80 | },
81 | "ranges": {
82 | "PSAL": {
83 | "type": "NdArray",
84 | "dataType": "float",
85 | "axisNames": [
86 | "z"
87 | ],
88 | "shape": [
89 | 3
90 | ],
91 | "values": [
92 | 42.7,
93 | 41.8,
94 | 40.9
95 | ]
96 | }
97 | }
98 | }
99 | ],
100 | "parameters": {
101 | "PSAL": {
102 | "type": "Parameter",
103 | "description": {
104 | "en": "The measured salinity, in practical salinity units (psu) of the sea water"
105 | },
106 | "observedProperty": {
107 | "id": "http://vocab.nerc.ac.uk/standard_name/sea_water_salinity/",
108 | "label": {
109 | "en": "Sea Water Salinity"
110 | }
111 | },
112 | "unit": {
113 | "symbol": "psu"
114 | }
115 | }
116 | },
117 | "referencing": [
118 | {
119 | "coordinates": [
120 | "x",
121 | "y"
122 | ],
123 | "system": {
124 | "type": "GeographicCRS",
125 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
126 | }
127 | },
128 | {
129 | "coordinates": [
130 | "z"
131 | ],
132 | "system": {
133 | "type": "VerticalCRS",
134 | "cs": {
135 | "csAxes": [
136 | {
137 | "name": {
138 | "en": "Pressure"
139 | },
140 | "direction": "down",
141 | "unit": {
142 | "symbol": "Pa"
143 | }
144 | }
145 | ]
146 | }
147 | }
148 | },
149 | {
150 | "coordinates": [
151 | "t"
152 | ],
153 | "system": {
154 | "type": "TemporalRS",
155 | "calendar": "Gregorian"
156 | }
157 | }
158 | ]
159 | }
160 |
--------------------------------------------------------------------------------
/tests/test_data/doc-example-coverage.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Coverage",
3 | "domain": {
4 | "type": "Domain",
5 | "domainType": "Grid",
6 | "axes": {
7 | "x": {
8 | "start": -179.5,
9 | "stop": 179.5,
10 | "num": 360
11 | },
12 | "y": {
13 | "start": -89.5,
14 | "stop": 89.5,
15 | "num": 180
16 | },
17 | "t": {
18 | "values": [
19 | "2013-01-13T00:00:00Z"
20 | ]
21 | }
22 | },
23 | "referencing": [
24 | {
25 | "coordinates": [
26 | "x",
27 | "y"
28 | ],
29 | "system": {
30 | "type": "GeographicCRS",
31 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
32 | }
33 | },
34 | {
35 | "coordinates": [
36 | "t"
37 | ],
38 | "system": {
39 | "type": "TemporalRS",
40 | "calendar": "Gregorian"
41 | }
42 | }
43 | ]
44 | },
45 | "parameters": {
46 | "TEMP": {
47 | "type": "Parameter",
48 | "description": {
49 | "en": "The air temperature measured in degrees Celsius."
50 | },
51 | "observedProperty": {
52 | "id": "http://vocab.nerc.ac.uk/standard_name/air_temperature/",
53 | "label": {
54 | "en": "Air temperature",
55 | "de": "Lufttemperatur"
56 | }
57 | },
58 | "unit": {
59 | "label": {
60 | "en": "Degree Celsius"
61 | },
62 | "symbol": {
63 | "value": "Cel",
64 | "type": "http://www.opengis.net/def/uom/UCUM/"
65 | }
66 | }
67 | }
68 | },
69 | "ranges": {
70 | "TEMP": "http://example.com/coverages/123/TEMP"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/test_data/example_py.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Coverage",
3 | "domain": {
4 | "type": "Domain",
5 | "domainType": "PointSeries",
6 | "axes": {
7 | "x": {
8 | "values": [
9 | 1.23
10 | ]
11 | },
12 | "y": {
13 | "values": [
14 | 4.56
15 | ]
16 | },
17 | "t": {
18 | "values": [
19 | "2024-08-01T00:00:00Z"
20 | ]
21 | }
22 | }
23 | },
24 | "ranges": {
25 | "temperature": {
26 | "type": "NdArray",
27 | "dataType": "float",
28 | "axisNames": [
29 | "x",
30 | "y",
31 | "t"
32 | ],
33 | "shape": [
34 | 1,
35 | 1,
36 | 1
37 | ],
38 | "values": [
39 | 42.0
40 | ]
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/test_data/grid-domain-no-y.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "Grid",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 1.0,
8 | 2.0,
9 | 3.0
10 | ]
11 | },
12 | "z": {
13 | "values": [
14 | 1.0
15 | ]
16 | },
17 | "t": {
18 | "values": [
19 | "2008-01-01T04:00:00Z"
20 | ]
21 | }
22 | },
23 | "referencing": [
24 | {
25 | "coordinates": [
26 | "t"
27 | ],
28 | "system": {
29 | "type": "TemporalRS",
30 | "calendar": "Gregorian"
31 | }
32 | },
33 | {
34 | "coordinates": [
35 | "x",
36 | "z"
37 | ],
38 | "system": {
39 | "type": "GeographicCRS",
40 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
41 | }
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/tests/test_data/grid-domain.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "Grid",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 1.0,
8 | 2.0,
9 | 3.0
10 | ]
11 | },
12 | "y": {
13 | "values": [
14 | 20.0,
15 | 21.0
16 | ]
17 | },
18 | "z": {
19 | "values": [
20 | 1.0
21 | ]
22 | },
23 | "t": {
24 | "values": [
25 | "2008-01-01T04:00:00Z",
26 | "2008-01-01T05:00:00Z",
27 | "2008-01-01T06:00:00Z"
28 | ]
29 | }
30 | },
31 | "referencing": [
32 | {
33 | "coordinates": [
34 | "t"
35 | ],
36 | "system": {
37 | "type": "TemporalRS",
38 | "calendar": "Gregorian"
39 | }
40 | },
41 | {
42 | "coordinates": [
43 | "y",
44 | "x",
45 | "z"
46 | ],
47 | "system": {
48 | "type": "GeographicCRS",
49 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
50 | }
51 | }
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/tests/test_data/mixed-type-axes-2.json:
--------------------------------------------------------------------------------
1 | {
2 | "x": {
3 | "values": [
4 | "foo",
5 | 42.0
6 | ]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/test_data/mixed-type-axes.json:
--------------------------------------------------------------------------------
1 | {
2 | "x": {
3 | "values": [
4 | 42.0,
5 | "foo"
6 | ]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/test_data/mixed-type-ndarray-1.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "NdArray",
3 | "dataType": "float",
4 | "axisNames": [
5 | "y",
6 | "x"
7 | ],
8 | "shape": [
9 | 2
10 | ],
11 | "values": [
12 | "42.0",
13 | 123
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tests/test_data/mixed-type-ndarray-2.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "NdArray",
3 | "dataType": "float",
4 | "axisNames": [
5 | "y",
6 | "x"
7 | ],
8 | "shape": [
9 | 2
10 | ],
11 | "values": [
12 | "foo",
13 | "bar"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tests/test_data/mixed-type-ndarray-3.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "NdArray",
3 | "dataType": "integer",
4 | "axisNames": [
5 | "y",
6 | "x"
7 | ],
8 | "shape": [
9 | 2
10 | ],
11 | "values": [
12 | 1,
13 | 1.42
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tests/test_data/ndarray-float.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "NdArray",
3 | "dataType": "float",
4 | "axisNames": [
5 | "t",
6 | "y",
7 | "x"
8 | ],
9 | "shape": [
10 | 1,
11 | 2,
12 | 3
13 | ],
14 | "values": [
15 | 27.1,
16 | 24.1,
17 | null,
18 | 25.1,
19 | 26.7,
20 | 23.2
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/tests/test_data/ndarray-integer.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "NdArray",
3 | "dataType": "integer",
4 | "axisNames": [
5 | "t",
6 | "y",
7 | "x"
8 | ],
9 | "shape": [
10 | 1,
11 | 1,
12 | 3
13 | ],
14 | "values": [
15 | 1,
16 | 2,
17 | 42
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/tests/test_data/ndarray-string.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "NdArray",
3 | "dataType": "string",
4 | "axisNames": [
5 | "t",
6 | "y",
7 | "x"
8 | ],
9 | "shape": [
10 | 1,
11 | 2,
12 | 3
13 | ],
14 | "values": [
15 | "ABC",
16 | "DEF",
17 | null,
18 | "XYZ",
19 | "a123",
20 | "qwerty"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/tests/test_data/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "PSAL": {
3 | "type": "Parameter",
4 | "description": {
5 | "en": "The measured salinity, in practical salinity units (psu) of the sea water"
6 | },
7 | "observedProperty": {
8 | "id": "https://vocab.nerc.ac.uk/standard_name/sea_water_salinity/",
9 | "label": {
10 | "en": "Sea Water Salinity"
11 | }
12 | },
13 | "unit": {
14 | "symbol": "psu"
15 | }
16 | },
17 | "POTM": {
18 | "type": "Parameter",
19 | "description": {
20 | "en": "The potential temperature, in degrees Celsius, of the sea water"
21 | },
22 | "observedProperty": {
23 | "id": "https://vocab.nerc.ac.uk/standard_name/sea_water_potential_temperature/",
24 | "label": {
25 | "en": "Sea Water Potential Temperature"
26 | }
27 | },
28 | "unit": {
29 | "symbol": "°C"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/test_data/point-series-domain-custom.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "PointSeries",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 5.3
8 | ]
9 | },
10 | "y": {
11 | "values": [
12 | 53.2
13 | ]
14 | },
15 | "t": {
16 | "dataType": "knmi:range",
17 | "values": [
18 | "2022-01-01T04:03:00Z",
19 | "2022-01-01T05:09:00Z"
20 | ],
21 | "knmi:num": 10
22 | }
23 | },
24 | "referencing": [
25 | {
26 | "coordinates": [
27 | "t"
28 | ],
29 | "system": {
30 | "type": "TemporalRS",
31 | "calendar": "Gregorian"
32 | }
33 | },
34 | {
35 | "coordinates": [
36 | "y",
37 | "x"
38 | ],
39 | "system": {
40 | "type": "GeographicCRS",
41 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
42 | }
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/tests/test_data/point-series-domain-more-z.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "PointSeries",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 5.3
8 | ]
9 | },
10 | "y": {
11 | "values": [
12 | 53.2
13 | ]
14 | },
15 | "z": {
16 | "values": [
17 | 53.2,
18 | 54.2
19 | ]
20 | },
21 | "t": {
22 | "values": [
23 | "2022-01-01T04:03:00Z",
24 | "2022-01-01T05:09:00Z"
25 | ]
26 | }
27 | },
28 | "referencing": [
29 | {
30 | "coordinates": [
31 | "t"
32 | ],
33 | "system": {
34 | "type": "TemporalRS",
35 | "calendar": "Gregorian"
36 | }
37 | },
38 | {
39 | "coordinates": [
40 | "y",
41 | "x",
42 | "z"
43 | ],
44 | "system": {
45 | "type": "GeographicCRS",
46 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
47 | }
48 | }
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/tests/test_data/point-series-domain-no-t.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "PointSeries",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 5.3
8 | ]
9 | },
10 | "y": {
11 | "values": [
12 | 53.2
13 | ]
14 | }
15 | },
16 | "referencing": [
17 | {
18 | "coordinates": [
19 | "y",
20 | "x"
21 | ],
22 | "system": {
23 | "type": "GeographicCRS",
24 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
25 | }
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/tests/test_data/polygon-series-coverage-collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "CoverageCollection",
3 | "coverages": [
4 | {
5 | "type": "Coverage",
6 | "domain": {
7 | "type": "Domain",
8 | "domainType": "PolygonSeries",
9 | "axes": {
10 | "t": {
11 | "values": [
12 | "2016-01-01T00:00:00Z",
13 | "2016-02-01T00:00:00Z",
14 | "2016-03-01T00:00:00Z"
15 | ]
16 | },
17 | "composite": {
18 | "dataType": "polygon",
19 | "coordinates": [
20 | "x",
21 | "y"
22 | ],
23 | "values": [
24 | [
25 | [
26 | [
27 | -105.67217,
28 | 36.02425
29 | ],
30 | [
31 | -105.88091,
32 | 35.24744
33 | ],
34 | [
35 | -105.01299,
36 | 32.0286
37 | ],
38 | [
39 | -103.54082,
40 | 32.07516
41 | ],
42 | [
43 | -104.06816,
44 | 34.75247
45 | ],
46 | [
47 | -105.67217,
48 | 36.02425
49 | ]
50 | ]
51 | ]
52 | ]
53 | }
54 | }
55 | },
56 | "ranges": {
57 | "Lake/Reservoir Storage End of Month": {
58 | "type": "NdArray",
59 | "dataType": "float",
60 | "axisNames": [
61 | "t"
62 | ],
63 | "shape": [
64 | 3
65 | ],
66 | "values": [
67 | 1.0,
68 | 2.0,
69 | 42.0
70 | ]
71 | }
72 | }
73 | }
74 | ],
75 | "parameters": {
76 | "Lake/Reservoir Storage End of Month": {
77 | "type": "Parameter",
78 | "description": {
79 | "en": "Instant daily lake/reservoir storage volume in acre-feet. Monthly refers to one measurement on the last day of each month."
80 | },
81 | "observedProperty": {
82 | "id": "1470",
83 | "label": {
84 | "en": "Lake/Reservoir Storage End of Month"
85 | }
86 | },
87 | "unit": {
88 | "symbol": "af"
89 | }
90 | }
91 | },
92 | "referencing": [
93 | {
94 | "coordinates": [
95 | "x",
96 | "y"
97 | ],
98 | "system": {
99 | "type": "GeographicCRS",
100 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
101 | }
102 | },
103 | {
104 | "coordinates": [
105 | "z"
106 | ],
107 | "system": {
108 | "type": "VerticalCRS",
109 | "cs": {
110 | "csAxes": [
111 | {
112 | "name": {
113 | "en": "time"
114 | },
115 | "direction": "down",
116 | "unit": {
117 | "symbol": "time"
118 | }
119 | }
120 | ]
121 | }
122 | }
123 | },
124 | {
125 | "coordinates": [
126 | "t"
127 | ],
128 | "system": {
129 | "type": "TemporalRS",
130 | "calendar": "Gregorian"
131 | }
132 | }
133 | ]
134 | }
135 |
--------------------------------------------------------------------------------
/tests/test_data/spec-axes.json:
--------------------------------------------------------------------------------
1 | {
2 | "x": {
3 | "values": [
4 | 20.0,
5 | 21.0
6 | ],
7 | "bounds": [
8 | 19.5,
9 | 20.5,
10 | 20.5,
11 | 21.5
12 | ]
13 | },
14 | "y": {
15 | "start": 0.0,
16 | "stop": 5.0,
17 | "num": 6
18 | },
19 | "t": {
20 | "values": [
21 | "2008-01-01T04:00:00Z",
22 | "2008-01-02T04:00:00Z"
23 | ]
24 | },
25 | "composite": {
26 | "dataType": "tuple",
27 | "coordinates": [
28 | "t",
29 | "x",
30 | "y"
31 | ],
32 | "values": [
33 | [
34 | "2008-01-01T04:00:00Z",
35 | 1,
36 | 20
37 | ],
38 | [
39 | "2008-01-01T04:30:00Z",
40 | 2,
41 | 21
42 | ]
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-grid.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "Grid",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 1.0,
8 | 2.0,
9 | 3.0
10 | ]
11 | },
12 | "y": {
13 | "values": [
14 | 20.0,
15 | 21.0
16 | ]
17 | },
18 | "z": {
19 | "values": [
20 | 1.0
21 | ]
22 | },
23 | "t": {
24 | "values": [
25 | "2008-01-01T04:00:00Z"
26 | ]
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-multipoint-series.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "MultiPointSeries",
4 | "axes": {
5 | "t": {
6 | "values": [
7 | "2008-01-01T04:00:00Z",
8 | "2008-01-01T05:00:00Z"
9 | ]
10 | },
11 | "composite": {
12 | "dataType": "tuple",
13 | "coordinates": [
14 | "x",
15 | "y",
16 | "z"
17 | ],
18 | "values": [
19 | [
20 | 1,
21 | 20,
22 | 1
23 | ],
24 | [
25 | 2,
26 | 21,
27 | 3
28 | ]
29 | ]
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-multipoint.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "MultiPoint",
4 | "axes": {
5 | "t": {
6 | "values": [
7 | "2008-01-01T04:00:00Z"
8 | ]
9 | },
10 | "composite": {
11 | "dataType": "tuple",
12 | "coordinates": [
13 | "x",
14 | "y",
15 | "z"
16 | ],
17 | "values": [
18 | [
19 | 1.0,
20 | 20.0,
21 | 1.0
22 | ],
23 | [
24 | 2.0,
25 | 21.0,
26 | 3.0
27 | ]
28 | ]
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-point-compact.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "Point",
4 | "axes": {
5 | "x": {
6 | "start": 1.0,
7 | "stop": 1.0,
8 | "num": 1
9 | },
10 | "y": {
11 | "start": 20.0,
12 | "stop": 20.0,
13 | "num": 1
14 | },
15 | "z": {
16 | "start": 1.8,
17 | "stop": 1.8,
18 | "num": 1
19 | },
20 | "t": {
21 | "values": [
22 | "2008-01-01T04:00:00Z"
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-point-series.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "PointSeries",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 1.0
8 | ]
9 | },
10 | "y": {
11 | "values": [
12 | 20.0
13 | ]
14 | },
15 | "z": {
16 | "values": [
17 | 1.0
18 | ]
19 | },
20 | "t": {
21 | "values": [
22 | "2008-01-01T04:00:00Z",
23 | "2008-01-01T05:00:00Z"
24 | ]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-point.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "Point",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 1.0
8 | ]
9 | },
10 | "y": {
11 | "values": [
12 | 20.0
13 | ]
14 | },
15 | "z": {
16 | "values": [
17 | 1.8
18 | ]
19 | },
20 | "t": {
21 | "values": [
22 | "2008-01-01T04:00:00Z"
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-polygon-series.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "PolygonSeries",
4 | "axes": {
5 | "z": {
6 | "values": [
7 | 2.0
8 | ]
9 | },
10 | "t": {
11 | "values": [
12 | "2008-01-01T04:00:00Z",
13 | "2008-01-01T05:00:00Z"
14 | ]
15 | },
16 | "composite": {
17 | "dataType": "polygon",
18 | "coordinates": [
19 | "x",
20 | "y"
21 | ],
22 | "values": [
23 | [
24 | [
25 | [
26 | 100.0,
27 | 0.0
28 | ],
29 | [
30 | 101.0,
31 | 0.0
32 | ],
33 | [
34 | 101.0,
35 | 1.0
36 | ],
37 | [
38 | 100.0,
39 | 1.0
40 | ],
41 | [
42 | 100.0,
43 | 0.0
44 | ]
45 | ]
46 | ]
47 | ]
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-trajectory.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "Trajectory",
4 | "axes": {
5 | "composite": {
6 | "dataType": "tuple",
7 | "coordinates": [
8 | "t",
9 | "x",
10 | "y"
11 | ],
12 | "values": [
13 | [
14 | "2008-01-01T04:00:00Z",
15 | 1,
16 | 20
17 | ],
18 | [
19 | "2008-01-01T04:30:00Z",
20 | 2,
21 | 21
22 | ]
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/test_data/spec-domain-vertical-profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Domain",
3 | "domainType": "VerticalProfile",
4 | "axes": {
5 | "x": {
6 | "values": [
7 | 1.0
8 | ]
9 | },
10 | "y": {
11 | "values": [
12 | 21.0
13 | ]
14 | },
15 | "z": {
16 | "values": [
17 | 1.0,
18 | 5.0,
19 | 20.0
20 | ]
21 | },
22 | "t": {
23 | "values": [
24 | "2008-01-01T04:00:00Z"
25 | ]
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/test_data/spec-ndarray.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "NdArray",
3 | "dataType": "float",
4 | "axisNames": [
5 | "y",
6 | "x"
7 | ],
8 | "shape": [
9 | 4,
10 | 2
11 | ],
12 | "values": [
13 | 12.3,
14 | 12.5,
15 | 11.5,
16 | 23.1,
17 | null,
18 | null,
19 | 10.1,
20 | 9.1
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/tests/test_data/spec-parametergroup.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ParameterGroup",
3 | "label": {
4 | "en": "Daily sea surface temperature with uncertainty information"
5 | },
6 | "observedProperty": {
7 | "id": "http://vocab.nerc.ac.uk/standard_name/sea_surface_temperature/",
8 | "label": {
9 | "en": "Sea surface temperature"
10 | }
11 | },
12 | "members": [
13 | "SST_mean",
14 | "SST_stddev"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/test_data/spec-reference-system-identifierrs.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "IdentifierRS",
3 | "id": "https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2",
4 | "label": {
5 | "en": "ISO 3166-1 alpha-2 codes"
6 | },
7 | "targetConcept": {
8 | "id": "http://dbpedia.org/resource/Country",
9 | "label": {
10 | "en": "Country",
11 | "de": "Land"
12 | }
13 | },
14 | "identifiers": {
15 | "de": {
16 | "id": "http://dbpedia.org/resource/Germany",
17 | "label": {
18 | "de": "Deutschland",
19 | "en": "Germany"
20 | }
21 | },
22 | "gb": {
23 | "id": "http://dbpedia.org/resource/United_Kingdom",
24 | "label": {
25 | "de": "Vereinigtes Konigreich",
26 | "en": "United Kingdom"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/test_data/spec-tiled-ndarray.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "TiledNdArray",
3 | "dataType": "float",
4 | "axisNames": [
5 | "t",
6 | "y",
7 | "x"
8 | ],
9 | "shape": [
10 | 2,
11 | 5,
12 | 10
13 | ],
14 | "tileSets": [
15 | {
16 | "tileShape": [
17 | null,
18 | null,
19 | null
20 | ],
21 | "urlTemplate": "http://example.com/a/all.covjson"
22 | },
23 | {
24 | "tileShape": [
25 | 1,
26 | null,
27 | null
28 | ],
29 | "urlTemplate": "http://example.com/b/{t}.covjson"
30 | },
31 | {
32 | "tileShape": [
33 | null,
34 | 2,
35 | 3
36 | ],
37 | "urlTemplate": "http://example.com/c/{y}-{x}.covjson"
38 | }
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/tests/test_data/spec-trajectory-coverage.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Coverage",
3 | "domain": {
4 | "type": "Domain",
5 | "domainType": "Trajectory",
6 | "axes": {
7 | "composite": {
8 | "dataType": "tuple",
9 | "coordinates": [
10 | "t",
11 | "x",
12 | "y",
13 | "z"
14 | ],
15 | "values": [
16 | [
17 | "2008-01-01T04:00:00Z",
18 | 1,
19 | 20,
20 | 1
21 | ],
22 | [
23 | "2008-01-01T04:30:00Z",
24 | 2,
25 | 21,
26 | 3
27 | ]
28 | ]
29 | }
30 | },
31 | "referencing": [
32 | {
33 | "coordinates": [
34 | "x",
35 | "y"
36 | ],
37 | "system": {
38 | "type": "GeographicCRS",
39 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
40 | }
41 | },
42 | {
43 | "coordinates": [
44 | "z"
45 | ],
46 | "system": {
47 | "type": "VerticalCRS",
48 | "cs": {
49 | "csAxes": [
50 | {
51 | "name": {
52 | "en": "Pressure"
53 | },
54 | "direction": "down",
55 | "unit": {
56 | "symbol": "Pa"
57 | }
58 | }
59 | ]
60 | }
61 | }
62 | },
63 | {
64 | "coordinates": [
65 | "t"
66 | ],
67 | "system": {
68 | "type": "TemporalRS",
69 | "calendar": "Gregorian"
70 | }
71 | }
72 | ]
73 | },
74 | "parameters": {
75 | "temperature": {
76 | "type": "Parameter",
77 | "description": {
78 | "en": "This is the air temperature"
79 | },
80 | "observedProperty": {
81 | "label": {
82 | "en": "temperature"
83 | }
84 | },
85 | "unit": {
86 | "label": {
87 | "en": "Degree Celsius"
88 | },
89 | "symbol": {
90 | "value": "Cel",
91 | "type": "http://www.opengis.net/def/uom/UCUM"
92 | }
93 | }
94 | }
95 | },
96 | "ranges": {
97 | "temperature": {
98 | "type": "NdArray",
99 | "dataType": "float",
100 | "axisNames": [
101 | "composite"
102 | ],
103 | "shape": [
104 | 2
105 | ],
106 | "values": [
107 | 10.1,
108 | 11.3
109 | ]
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/tests/test_data/spec-vertical-profile-coverage.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Coverage",
3 | "domain": {
4 | "type": "Domain",
5 | "domainType": "VerticalProfile",
6 | "axes": {
7 | "x": {
8 | "values": [
9 | -10.1
10 | ]
11 | },
12 | "y": {
13 | "values": [
14 | -40.2
15 | ]
16 | },
17 | "z": {
18 | "values": [
19 | 5.4562,
20 | 8.9282,
21 | 14.8802,
22 | 20.832,
23 | 26.7836,
24 | 32.735,
25 | 38.6863,
26 | 44.6374,
27 | 50.5883,
28 | 56.5391,
29 | 62.4897,
30 | 68.4401,
31 | 74.3903,
32 | 80.3404,
33 | 86.2902,
34 | 92.24,
35 | 98.1895,
36 | 104.1389,
37 | 110.0881,
38 | 116.0371,
39 | 121.9859
40 | ]
41 | },
42 | "t": {
43 | "values": [
44 | "2013-01-13T11:12:20Z"
45 | ]
46 | }
47 | },
48 | "referencing": [
49 | {
50 | "coordinates": [
51 | "x",
52 | "y"
53 | ],
54 | "system": {
55 | "type": "GeographicCRS",
56 | "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
57 | }
58 | },
59 | {
60 | "coordinates": [
61 | "z"
62 | ],
63 | "system": {
64 | "type": "VerticalCRS",
65 | "cs": {
66 | "csAxes": [
67 | {
68 | "name": {
69 | "en": "Pressure"
70 | },
71 | "direction": "down",
72 | "unit": {
73 | "symbol": "Pa"
74 | }
75 | }
76 | ]
77 | }
78 | }
79 | },
80 | {
81 | "coordinates": [
82 | "t"
83 | ],
84 | "system": {
85 | "type": "TemporalRS",
86 | "calendar": "Gregorian"
87 | }
88 | }
89 | ]
90 | },
91 | "parameters": {
92 | "PSAL": {
93 | "type": "Parameter",
94 | "description": {
95 | "en": "The measured salinity, in practical salinity units (psu) of the sea water"
96 | },
97 | "observedProperty": {
98 | "id": "http://vocab.nerc.ac.uk/standard_name/sea_water_salinity/",
99 | "label": {
100 | "en": "Sea Water Salinity"
101 | }
102 | },
103 | "unit": {
104 | "symbol": "psu"
105 | }
106 | },
107 | "POTM": {
108 | "type": "Parameter",
109 | "description": {
110 | "en": "The potential temperature, in degrees celcius, of the sea water"
111 | },
112 | "observedProperty": {
113 | "id": "http://vocab.nerc.ac.uk/standard_name/sea_water_potential_temperature/",
114 | "label": {
115 | "en": "Sea Water Potential Temperature"
116 | }
117 | },
118 | "unit": {
119 | "symbol": "deg C"
120 | }
121 | }
122 | },
123 | "ranges": {
124 | "PSAL": {
125 | "type": "NdArray",
126 | "dataType": "float",
127 | "axisNames": [
128 | "z"
129 | ],
130 | "shape": [
131 | 21
132 | ],
133 | "values": [
134 | 43.9599,
135 | 43.9599,
136 | 43.964,
137 | 43.964,
138 | 43.9679,
139 | 43.9879,
140 | 44.004,
141 | 44.012,
142 | 44.012,
143 | 44.0159,
144 | 44.032,
145 | 44.032,
146 | 44.048,
147 | 44.0559,
148 | 44.0559,
149 | 44.0579,
150 | 44.068,
151 | 44.074,
152 | 44.0779,
153 | 44.088,
154 | 44.094
155 | ]
156 | },
157 | "POTM": {
158 | "type": "NdArray",
159 | "dataType": "float",
160 | "axisNames": [
161 | "z"
162 | ],
163 | "shape": [
164 | 21
165 | ],
166 | "values": [
167 | 23.8,
168 | 23.7,
169 | 23.5,
170 | 23.4,
171 | 23.2,
172 | 22.4,
173 | 21.8,
174 | 21.7,
175 | 21.5,
176 | 21.3,
177 | 21.0,
178 | 20.6,
179 | 20.1,
180 | 19.7,
181 | 19.4,
182 | 19.1,
183 | 18.9,
184 | 18.8,
185 | 18.7,
186 | 18.6,
187 | 18.5
188 | ]
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/tests/test_data/str-axes.json:
--------------------------------------------------------------------------------
1 | {
2 | "x": {
3 | "values": [
4 | "foo",
5 | "bar"
6 | ]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/test_data/temporalrs-no-calendar.json:
--------------------------------------------------------------------------------
1 | {
2 | "coordinates": [
3 | "t"
4 | ],
5 | "system": {
6 | "type": "TemporalRS",
7 | "id": "http://www.opengis.net/def/crs/OGC/1.3/"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------